From 7427e432911d1e0d98c89b9f96eac1ae0152676e Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 8 Dec 2023 14:59:21 -0500 Subject: [PATCH 01/10] upgrade sdk to latest stable version (#1049) (#1051) --- @shared/api/package.json | 2 +- @shared/constants/package.json | 2 +- @shared/helpers/package.json | 2 +- extension/package.json | 2 +- yarn.lock | 85 ++++++++++++---------------------- 5 files changed, 33 insertions(+), 60 deletions(-) diff --git a/@shared/api/package.json b/@shared/api/package.json index e0dd03a7fe..7c9df5b074 100644 --- a/@shared/api/package.json +++ b/@shared/api/package.json @@ -7,7 +7,7 @@ "@stellar/wallet-sdk": "v0.11.0-beta.1", "bignumber.js": "^9.1.1", "prettier": "^2.0.5", - "stellar-sdk": "11.0.0-beta.5", + "stellar-sdk": "^11.0.1", "typescript": "~3.7.2", "webextension-polyfill": "^0.10.0" }, diff --git a/@shared/constants/package.json b/@shared/constants/package.json index 2d9d02602a..2e5745f87a 100644 --- a/@shared/constants/package.json +++ b/@shared/constants/package.json @@ -3,7 +3,7 @@ "prettier": "@stellar/prettier-config", "version": "1.0.0", "dependencies": { - "stellar-sdk": "^11.0.0-beta.4", + "stellar-sdk": "^11.0.1", "typescript": "~3.7.2" }, "devDependencies": { diff --git a/@shared/helpers/package.json b/@shared/helpers/package.json index 7f217275f8..1fb11efee6 100644 --- a/@shared/helpers/package.json +++ b/@shared/helpers/package.json @@ -3,7 +3,7 @@ "prettier": "@stellar/prettier-config", "version": "1.0.0", "dependencies": { - "stellar-sdk": "11.0.0-beta.5", + "stellar-sdk": "^11.0.1", "typescript": "~3.7.2" }, "devDependencies": { diff --git a/extension/package.json b/extension/package.json index 54e0c1c22b..211066df9e 100644 --- a/extension/package.json +++ b/extension/package.json @@ -76,7 +76,7 @@ "simplebar-react": "^2.3.6", "stellar-hd-wallet": "^0.0.10", "stellar-identicon-js": "^1.0.0", - "stellar-sdk": "11.0.0-beta.5", + "stellar-sdk": "^11.0.1", "svg-url-loader": "^5.0.0", "tsconfig-paths-webpack-plugin": "^3.2.0", "tslib": "2.0.0", diff --git a/yarn.lock b/yarn.lock index 2770073aad..3ed5504e7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3802,11 +3802,30 @@ resolved "https://registry.npmjs.org/@stellar/eslint-config/-/eslint-config-1.0.5.tgz" integrity sha512-RpkhdJffBU1oEsdyAJgfKHDSRJCWbyNOp08F1UP0yGp1tBLp0FopURl7m1Nx2Xd1o1PkN6h01wV6lpdiCbZWYQ== +"@stellar/js-xdr@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@stellar/js-xdr/-/js-xdr-3.0.1.tgz#d500f1e1332210cd56e0ef95e44c54506d9f48f3" + integrity sha512-dp5Eh7Nr1YjiIeqpdkj2cQYxfoPudDAH3ck8MWggp48Htw66Z/hUssNYUQG/OftLjEmHT90Z/dtey2Y77DOxIw== + "@stellar/prettier-config@^1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@stellar/prettier-config/-/prettier-config-1.0.1.tgz" integrity sha512-w9OPycQp1XGfmHC2VUHe5shpZjNFRlmsRBaK7IHvOvVpglzV2QNJsVFh8RdLREWA0mzF59AWvQbyUCCJLPfdWw== +"@stellar/stellar-base@10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@stellar/stellar-base/-/stellar-base-10.0.0.tgz#b9ef7e84e644d06ec08b8ba88ad6b38da7b80494" + integrity sha512-zHlGzWefiB2gkt13l+I8Dvo2k7+n6+vxizay4lDEc5si0zjSZbCUUzMIdVIQ86dao7+TvZ3aNw8CdncOWLDu2A== + dependencies: + "@stellar/js-xdr" "^3.0.1" + base32.js "^0.1.0" + bignumber.js "^9.1.2" + buffer "^6.0.3" + sha.js "^2.3.6" + tweetnacl "^1.0.3" + optionalDependencies: + sodium-native "^4.0.1" + "@stellar/tsconfig@^1.0.2": version "1.0.2" resolved "https://registry.npmjs.org/@stellar/tsconfig/-/tsconfig-1.0.2.tgz" @@ -5438,10 +5457,10 @@ axios@^0.25.0: dependencies: follow-redirects "^1.14.7" -axios@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.5.1.tgz#11fbaa11fc35f431193a9564109c88c1f27b585f" - integrity sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A== +axios@^1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" + integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" @@ -10610,11 +10629,6 @@ js-xdr@^1.1.1: lodash "^4.17.5" long "^2.2.3" -js-xdr@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/js-xdr/-/js-xdr-3.0.0.tgz" - integrity sha512-tSt6UKJ2L7t+yaQURGkHo9kop9qnVbChTlCu62zNiDbDZQoZb/YjUj2iFJ3lgelhfg9p5bhO2o/QX+g36TPsSQ== - js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" @@ -14702,34 +14716,6 @@ std-env@^3.0.1: resolved "https://registry.npmjs.org/std-env/-/std-env-3.3.2.tgz" integrity sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA== -stellar-base@10.0.0-beta.2: - version "10.0.0-beta.2" - resolved "https://registry.yarnpkg.com/stellar-base/-/stellar-base-10.0.0-beta.2.tgz#36452bb14a7a9e66175e6231feb64de3d2fe3627" - integrity sha512-fgVcNzlGuXTte4gEg3fDA/pJB5VbqfE8+gkbjsphVecDELR8t3K+QalbDhcafi3g54nRYUaHnN7LcGeoschuIA== - dependencies: - base32.js "^0.1.0" - bignumber.js "^9.1.2" - buffer "^6.0.3" - js-xdr "^3.0.0" - sha.js "^2.3.6" - tweetnacl "^1.0.3" - optionalDependencies: - sodium-native "^4.0.1" - -stellar-base@10.0.0-beta.3: - version "10.0.0-beta.3" - resolved "https://registry.yarnpkg.com/stellar-base/-/stellar-base-10.0.0-beta.3.tgz#c90e261945c58e2176b0a50b9a7b96003ecb0768" - integrity sha512-+B1fOdsDWJEnYSYkKSAVHVngzaqDtD8wdDMT/FC+11MrohP3uGY1OmrEeVn34jiBmUlpYZVudDnpDMSXD4RqDA== - dependencies: - base32.js "^0.1.0" - bignumber.js "^9.1.2" - buffer "^6.0.3" - js-xdr "^3.0.0" - sha.js "^2.3.6" - tweetnacl "^1.0.3" - optionalDependencies: - sodium-native "^4.0.1" - stellar-base@^0.13.1: version "0.13.2" resolved "https://registry.npmjs.org/stellar-base/-/stellar-base-0.13.2.tgz" @@ -14762,29 +14748,16 @@ stellar-identicon-js@^1.0.0: dependencies: html-webpack-plugin "^3.2.0" -stellar-sdk@11.0.0-beta.5: - version "11.0.0-beta.5" - resolved "https://registry.yarnpkg.com/stellar-sdk/-/stellar-sdk-11.0.0-beta.5.tgz#5c45c6db0b0f1528532d4e2496e7b38d912b8913" - integrity sha512-jJTY7GbLoYo1x7nX67fTgjwCvhRsn8EeLvHqfh0GKGBddDNn2X4Y3Zy8slMWV1yDLr3Ueh5snfWyKtD7gx8xgA== - dependencies: - axios "^1.5.1" - bignumber.js "^9.1.2" - eventsource "^2.0.2" - randombytes "^2.1.0" - stellar-base "10.0.0-beta.3" - toml "^3.0.0" - urijs "^1.19.1" - -stellar-sdk@^11.0.0-beta.4: - version "11.0.0-beta.4" - resolved "https://registry.yarnpkg.com/stellar-sdk/-/stellar-sdk-11.0.0-beta.4.tgz#aa79beb9a636bc4c8f4069c35248e8b1a46526df" - integrity sha512-Okq3LzpTOOLxYMyAhkdhzo1OwsPO6kVZGtxkqYApqOpSjzCDtXje9b1YGO7NycRH4ICJftxQ6TF+VZEaBvXRlQ== +stellar-sdk@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/stellar-sdk/-/stellar-sdk-11.0.1.tgz#de53748d648e732d0d8a3fdcb292b18e30a217cc" + integrity sha512-uRXK9NcsJNoo7F2P3JQRY9GC9+LFVQQjz9N5nmsLdUDrOT9cM8bb3MoUt9jdY5+nBsrEVnuJTZzLG29GyowBew== dependencies: - axios "^1.5.1" + "@stellar/stellar-base" "10.0.0" + axios "^1.6.0" bignumber.js "^9.1.2" eventsource "^2.0.2" randombytes "^2.1.0" - stellar-base "10.0.0-beta.2" toml "^3.0.0" urijs "^1.19.1" From ada06f0cbe5336d2a1b6f12266ddcde0e9912aaf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:37:57 -0500 Subject: [PATCH 02/10] Bump versions to 5.9.0 (#1053) * docs(): bumping release to 5.9.0 * Empty-Commit --------- Co-authored-by: GitHub Action Co-authored-by: Piyal Basu --- extension/package.json | 2 +- extension/public/static/manifest/v2.json | 4 ++-- extension/public/static/manifest/v3.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extension/package.json b/extension/package.json index 211066df9e..fc4dffd99a 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "extension", - "version": "5.8.0", + "version": "5.9.0", "license": "Apache-2.0", "prettier": "@stellar/prettier-config", "scripts": { diff --git a/extension/public/static/manifest/v2.json b/extension/public/static/manifest/v2.json index 677e2814b1..a926b533a4 100644 --- a/extension/public/static/manifest/v2.json +++ b/extension/public/static/manifest/v2.json @@ -1,7 +1,7 @@ { "name": "Freighter", - "version": "5.8.0", - "version_name": "5.8.0", + "version": "5.9.0", + "version_name": "5.9.0", "description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.", "browser_specific_settings": { "gecko": { diff --git a/extension/public/static/manifest/v3.json b/extension/public/static/manifest/v3.json index 86414444ca..3f24175e33 100644 --- a/extension/public/static/manifest/v3.json +++ b/extension/public/static/manifest/v3.json @@ -1,7 +1,7 @@ { "name": "Freighter", - "version": "5.8.0", - "version_name": "5.8.0", + "version": "5.9.0", + "version_name": "5.9.0", "description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.", "background": { "service_worker": "background.min.js" From c4502789cb7bb6987c3bd49993d8da1a62ce67f7 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Mon, 11 Dec 2023 17:33:08 -0500 Subject: [PATCH 03/10] Release/5.10.0 (#1050) * fixes a check for network for the undefined case * Added translations * Feature/account migration pt 1 (#1026) * setup account migration routing * Leave feedback screen (#1023) * upgrades to new stellar-sdk beta, refactors to new sdk structure and removes soroban-sdk * use imports from namespaces for stellar-sdk, update test mocks for namespaces * removes stray test logs * Upgrade SDS to v1.0.1 (#1024) * find migratable accounts and nav to mnemonic phrase * navigate to mnemonic phrase after submission * Added translations * update sdk calls * Bump versions to 5.8.0 (#1029) * docs(): bumping release to 5.8.0 * Empty-Commit --------- Co-authored-by: GitHub Action Co-authored-by: Piyal Basu * copy fix * Added translations --------- Co-authored-by: Iveta Co-authored-by: Aristides Staffieri Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action * fixes faulty check for showing soroban rpc url field in network settings * Feature/account migration submit tx (#1038) * confirm migration with happy path * streamline migration process for merge * Added translations * rm logs * add migrate trustline comment * replaceAccount comments * add comment and simplify return * fix types * update comment * rm unneeded param * simplify trustline transfer and account for removing trustlines before merge (#1039) * simplify trustline transfer and account for removing trustlines before merge * calculate minimum sender balance * polish review-migration to use dynamic fees (#1040) * Update layout (#1033) * Onboarding views * Added translations * Unlock account view * Account view * Account history * Settings views * Manage assets views * ViewPublicKey view * Send views * Added translations * Fix failing test * Popover messages updated * Welcome screen * Connect wallet + Send flow fixes * External sign views * Added translations * Remove JS scrolling + layout tweaks * Cleanup * Fix Loader layout * Added translations * Added translations * Feature/migration complete (#1041) * add completed migration page * route to correct view * add some error safeguards and fix a UI issue with unfunded accounts * Added translations * copy fix * Added translations * update migration with new sds layout (#1042) * add completed migration page * route to correct view * add some error safeguards and fix a UI issue with unfunded accounts * Added translations * copy fix * Added translations * update migration with new sds layout * Layout fixes (#1043) * Layout fixes * Adjust footer bottom padding * adjust some ui and make sure we have the user's password (#1044) * adjust some ui and make sure we have the user's password * adjust migration complete top margin * fix loader alignment * switch on Mainnet for migration and show a streamlined password prompt (#1046) * Bugfix/migration fees (#1048) * handle fees and retries properly * update copy * Added translations * slight adjustment to opCount * fix trustline error view and scroll long sc values (#1056) --------- Co-authored-by: Aristides Staffieri Co-authored-by: Iveta Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action --- @shared/api/helpers/stellarSdkServer.ts | 26 +- @shared/api/internal.ts | 144 ++++++- @shared/api/types.ts | 21 + @shared/constants/services.ts | 4 + @shared/constants/stellar.ts | 2 + @shared/helpers/migration.ts | 44 ++ @shared/helpers/package.json | 1 + extension/package.json | 2 - extension/src/background/ducks/session.ts | 40 +- .../src/background/helpers/dataStorage.ts | 2 +- extension/src/background/helpers/migration.ts | 129 ++++++ .../messageListener/popupMessageListener.ts | 345 ++++++++++++++- extension/src/popup/Router.tsx | 4 + extension/src/popup/basics/Forms/index.tsx | 14 +- extension/src/popup/basics/Forms/styles.scss | 5 + .../src/popup/basics/ListNavLink/index.tsx | 21 + .../src/popup/basics/ListNavLink/styles.scss | 4 +- extension/src/popup/basics/Modal/index.tsx | 5 +- .../popup/basics/SimpleBarWrapper/index.tsx | 13 - .../popup/basics/buttons/BackButton/index.tsx | 22 +- .../src/popup/basics/layout/Box/index.tsx | 49 +++ .../src/popup/basics/layout/Box/styles.scss | 30 ++ .../src/popup/basics/layout/View/index.tsx | 286 +++++++++++++ .../src/popup/basics/layout/View/styles.scss | 216 ++++++++++ .../src/popup/components/BottomNav/index.tsx | 31 +- .../popup/components/BottomNav/styles.scss | 15 +- .../src/popup/components/Header/index.tsx | 17 - .../src/popup/components/Header/styles.scss | 18 - .../src/popup/components/Loading/index.tsx | 7 +- .../src/popup/components/Loading/styles.scss | 21 +- .../src/popup/components/Onboarding/index.tsx | 124 ++++-- .../popup/components/Onboarding/styles.scss | 41 +- .../PasswordRequirements/styles.scss | 3 - .../popup/components/SlideupModal/styles.scss | 2 +- .../popup/components/SubviewHeader/index.tsx | 23 +- .../components/WarningMessages/index.tsx | 328 +++++++------- .../components/WarningMessages/styles.scss | 35 +- .../account/AccountHeader/index.tsx | 54 +-- .../account/AccountHeader/styles.scss | 6 +- .../account/AccountList/styles.scss | 2 +- .../components/account/AssetDetail/index.tsx | 169 ++++---- .../account/AssetDetail/styles.scss | 6 +- .../accountHistory/HistoryList/index.tsx | 5 +- .../accountHistory/HistoryList/styles.scss | 8 - .../TransactionDetail/index.tsx | 159 +++---- .../TransactionDetail/styles.scss | 5 - .../ConfirmMigration/index.tsx | 108 +++++ .../ConfirmMigration/styles.scss | 9 + .../MigrationComplete/index.tsx | 105 +++++ .../MigrationComplete/styles.scss | 32 ++ .../accountMigration/MigrationStart/index.tsx | 79 ++++ .../MigrationStart/styles.scss | 9 + .../accountMigration/MnemonicPhrase/index.tsx | 47 ++ .../MnemonicPhrase/styles.scss | 7 + .../ReviewMigration/index.tsx | 405 ++++++++++++++++++ .../ReviewMigration/styles.scss | 19 + .../accountMigration/basics/index.tsx | 139 ++++++ .../accountMigration/basics/styles.scss | 117 +++++ .../manageAssets/AddAsset/index.tsx | 115 +++-- .../manageAssets/AddAsset/styles.scss | 2 - .../manageAssets/AddToken/index.tsx | 50 +-- .../manageAssets/ChooseAsset/index.tsx | 59 ++- .../manageAssets/ChooseAsset/styles.scss | 18 +- .../manageAssets/ManageAssetRows/index.tsx | 12 +- .../manageAssets/ManageAssetRows/styles.scss | 7 - .../manageAssets/SearchAsset/index.tsx | 124 +++--- .../manageAssets/SearchAsset/styles.scss | 4 - .../manageAssets/SelectAssetRows/index.tsx | 16 +- .../manageAssets/SelectAssetRows/styles.scss | 5 - .../manageAssets/TrustlineError/index.tsx | 27 +- .../manageAssets/TrustlineError/styles.scss | 7 - .../manageNetwork/NetworkForm/index.tsx | 383 +++++++++-------- .../manageNetwork/NetworkForm/styles.scss | 12 - .../manageNetwork/NetworkSettings/index.tsx | 75 ++-- .../manageNetwork/NetworkSettings/styles.scss | 8 - .../ConfirmMnemonicPhrase/index.tsx | 152 ++++--- .../ConfirmMnemonicPhrase/styles.scss | 16 +- .../DisplayMnemonicPhrase/index.tsx | 102 +++-- .../DisplayMnemonicPhrase/styles.scss | 9 +- .../MnemonicDisplay/styles.scss | 1 - .../sendPayment/SendAmount/SendType/index.tsx | 90 ++-- .../sendPayment/SendAmount/index.tsx | 243 +++++------ .../SendConfirm/SubmitResult/index.tsx | 132 +++--- .../SendConfirm/SubmitResult/styles.scss | 21 - .../SendConfirm/TransactionDetails/index.tsx | 224 +++++----- .../TransactionDetails/styles.scss | 6 +- .../sendPayment/SendConfirm/index.tsx | 3 +- .../SendSettings/Slippage/index.tsx | 68 ++- .../SendSettings/TransactionFee/index.tsx | 69 +-- .../sendPayment/SendSettings/index.tsx | 65 ++- .../components/sendPayment/SendTo/index.tsx | 214 ++++----- .../popup/components/sendPayment/styles.scss | 48 +-- .../signAuthEntry/AuthEntry/index.tsx | 5 +- .../signTransaction/Operations/index.tsx | 11 +- .../signTransaction/Operations/styles.scss | 1 + extension/src/popup/constants/metricsNames.ts | 10 + extension/src/popup/constants/routes.ts | 5 + extension/src/popup/ducks/accountServices.ts | 132 +++++- .../src/popup/ducks/transactionSubmission.ts | 13 + .../src/popup/helpers/addStyleClasses.ts | 2 + .../src/popup/locales/en/translation.json | 50 +++ .../src/popup/locales/pt/translation.json | 50 +++ extension/src/popup/metrics/views.ts | 9 + extension/src/popup/styles/global.scss | 39 +- extension/src/popup/styles/utils.scss | 12 + extension/src/popup/views/About/index.tsx | 54 +-- extension/src/popup/views/About/styles.scss | 2 - extension/src/popup/views/Account/index.tsx | 147 ++++--- extension/src/popup/views/Account/styles.scss | 10 +- .../src/popup/views/AccountCreator/index.tsx | 105 +++-- .../src/popup/views/AccountHistory/index.tsx | 109 ++--- .../popup/views/AccountHistory/styles.scss | 8 +- .../popup/views/AccountMigration/index.tsx | 55 +++ .../popup/views/AccountMigration/styles.scss | 6 + .../views/AddAccount/AddAccount/index.tsx | 50 +-- .../views/AddAccount/ImportAccount/index.tsx | 119 ++--- .../AddAccount/ImportAccount/styles.scss | 5 - .../connect/ConnectWallet/index.tsx | 39 +- .../connect/ConnectWallet/styles.scss | 3 - .../popup/views/DisplayBackupPhrase/index.tsx | 74 ++-- .../views/DisplayBackupPhrase/styles.scss | 5 - .../views/FullscreenSuccessMessage/index.tsx | 48 +-- .../FullscreenSuccessMessage/styles.scss | 6 +- .../src/popup/views/LeaveFeedback/index.tsx | 98 +++-- .../src/popup/views/LeaveFeedback/styles.scss | 2 - .../popup/views/ManageConnectedApps/index.tsx | 53 ++- .../views/ManageConnectedApps/styles.scss | 16 - extension/src/popup/views/MnemonicPhrase.tsx | 43 +- .../src/popup/views/PinExtension/index.tsx | 81 ++-- .../src/popup/views/PinExtension/styles.scss | 10 - .../src/popup/views/Preferences/index.tsx | 192 +++++---- .../src/popup/views/Preferences/styles.scss | 10 - .../src/popup/views/RecoverAccount/index.tsx | 167 ++++---- extension/src/popup/views/Security/index.tsx | 43 +- .../src/popup/views/Security/styles.scss | 1 - extension/src/popup/views/Settings/index.tsx | 79 ++-- .../src/popup/views/Settings/styles.scss | 6 +- .../src/popup/views/SignAuthEntry/index.tsx | 82 ++-- extension/src/popup/views/SignBlob/index.tsx | 81 ++-- .../src/popup/views/SignTransaction/index.tsx | 87 ++-- .../src/popup/views/UnlockAccount/index.tsx | 104 ++--- .../src/popup/views/UnlockAccount/styles.scss | 22 +- .../src/popup/views/VerifyAccount/index.tsx | 63 +-- .../src/popup/views/ViewPublicKey/index.tsx | 96 ++--- .../src/popup/views/ViewPublicKey/styles.scss | 24 +- extension/src/popup/views/Welcome/index.tsx | 102 ++--- extension/src/popup/views/Welcome/styles.scss | 26 +- yarn.lock | 42 +- 148 files changed, 5625 insertions(+), 3060 deletions(-) create mode 100644 @shared/helpers/migration.ts create mode 100644 extension/src/background/helpers/migration.ts delete mode 100644 extension/src/popup/basics/SimpleBarWrapper/index.tsx create mode 100644 extension/src/popup/basics/layout/Box/index.tsx create mode 100644 extension/src/popup/basics/layout/Box/styles.scss create mode 100644 extension/src/popup/basics/layout/View/index.tsx create mode 100644 extension/src/popup/basics/layout/View/styles.scss delete mode 100644 extension/src/popup/components/Header/index.tsx delete mode 100644 extension/src/popup/components/Header/styles.scss create mode 100644 extension/src/popup/components/accountMigration/ConfirmMigration/index.tsx create mode 100644 extension/src/popup/components/accountMigration/ConfirmMigration/styles.scss create mode 100644 extension/src/popup/components/accountMigration/MigrationComplete/index.tsx create mode 100644 extension/src/popup/components/accountMigration/MigrationComplete/styles.scss create mode 100644 extension/src/popup/components/accountMigration/MigrationStart/index.tsx create mode 100644 extension/src/popup/components/accountMigration/MigrationStart/styles.scss create mode 100644 extension/src/popup/components/accountMigration/MnemonicPhrase/index.tsx create mode 100644 extension/src/popup/components/accountMigration/MnemonicPhrase/styles.scss create mode 100644 extension/src/popup/components/accountMigration/ReviewMigration/index.tsx create mode 100644 extension/src/popup/components/accountMigration/ReviewMigration/styles.scss create mode 100644 extension/src/popup/components/accountMigration/basics/index.tsx create mode 100644 extension/src/popup/components/accountMigration/basics/styles.scss create mode 100644 extension/src/popup/helpers/addStyleClasses.ts create mode 100644 extension/src/popup/styles/utils.scss create mode 100644 extension/src/popup/views/AccountMigration/index.tsx create mode 100644 extension/src/popup/views/AccountMigration/styles.scss diff --git a/@shared/api/helpers/stellarSdkServer.ts b/@shared/api/helpers/stellarSdkServer.ts index 0d6f6614d9..955fdcdf99 100644 --- a/@shared/api/helpers/stellarSdkServer.ts +++ b/@shared/api/helpers/stellarSdkServer.ts @@ -1,4 +1,4 @@ -import { Horizon } from "stellar-sdk"; +import { FeeBumpTransaction, Horizon, Transaction } from "stellar-sdk"; export const getIsAllowHttp = (networkUrl: string) => !networkUrl.includes("https"); @@ -7,3 +7,27 @@ export const stellarSdkServer = (networkUrl: string) => new Horizon.Server(networkUrl, { allowHttp: getIsAllowHttp(networkUrl), }); + +export const submitTx = async ({ + server, + tx, +}: { + server: Horizon.Server; + tx: Transaction | FeeBumpTransaction; +}): Promise => { + let submittedTx; + + try { + submittedTx = await server.submitTransaction(tx); + } catch (e) { + if (e.response.status === 504) { + // in case of 504, keep retrying this tx until submission succeeds or we get a different error + // https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/timeout + // https://developers.stellar.org/docs/encyclopedia/error-handling + return submitTx({ server, tx }); + } + throw e; + } + + return submittedTx; +}; diff --git a/@shared/api/internal.ts b/@shared/api/internal.ts index 9170c64028..561e0d1b29 100644 --- a/@shared/api/internal.ts +++ b/@shared/api/internal.ts @@ -1,4 +1,5 @@ import { + Horizon, TransactionBuilder, SorobanRpc, Transaction, @@ -18,8 +19,11 @@ import { AccountBalancesInterface, AccountHistoryInterface, Balances, + BalanceToMigrate, HorizonOperation, + MigratableAccount, Settings, + MigratedAccount, } from "./types"; import { MAINNET_NETWORK_DETAILS, @@ -34,7 +38,7 @@ import { WalletType } from "../constants/hardwareWallet"; import { sendMessageToBackground } from "./helpers/extensionMessaging"; import { getIconUrlFromIssuer } from "./helpers/getIconUrlFromIssuer"; import { getDomainFromIssuer } from "./helpers/getDomainFromIssuer"; -import { stellarSdkServer } from "./helpers/stellarSdkServer"; +import { stellarSdkServer, submitTx } from "./helpers/stellarSdkServer"; const TRANSACTIONS_LIMIT = 100; @@ -228,6 +232,21 @@ export const getMnemonicPhrase = async (): Promise<{ return response; }; +export const getMigratedMnemonicPhrase = async (): Promise<{ + mnemonicPhrase: string; +}> => { + let response = { mnemonicPhrase: "" }; + + try { + response = await sendMessageToBackground({ + type: SERVICE_TYPES.GET_MIGRATED_MNEMONIC_PHRASE, + }); + } catch (e) { + console.error(e); + } + return response; +}; + export const confirmMnemonicPhrase = async ( mnemonicPhraseToConfirm: string, ): Promise<{ @@ -250,6 +269,26 @@ export const confirmMnemonicPhrase = async ( return response; }; +export const confirmMigratedMnemonicPhrase = async ( + mnemonicPhraseToConfirm: string, +): Promise<{ + isCorrectPhrase: boolean; +}> => { + let response = { + isCorrectPhrase: false, + }; + + try { + response = await sendMessageToBackground({ + mnemonicPhraseToConfirm, + type: SERVICE_TYPES.CONFIRM_MIGRATED_MNEMONIC_PHRASE, + }); + } catch (e) { + console.error(e); + } + return response; +}; + export const recoverAccount = async ( password: string, recoverMnemonic: string, @@ -310,6 +349,88 @@ export const confirmPassword = async ( return response; }; +export const getAccountInfo = async ({ + publicKey, + networkDetails, +}: { + publicKey: string; + networkDetails: NetworkDetails; +}) => { + const { networkUrl } = networkDetails; + + const server = new Horizon.Server(networkUrl); + + let account; + let signerArr = { records: [] as Horizon.ServerApi.AccountRecord[] }; + + try { + account = await server.loadAccount(publicKey); + signerArr = await server.accounts().forSigner(publicKey).call(); + } catch (e) { + console.error(e); + } + + return { + account, + isSigner: signerArr.records.length > 1, + }; +}; + +export const getMigratableAccounts = async () => { + let migratableAccounts: MigratableAccount[] = []; + + try { + ({ migratableAccounts } = await sendMessageToBackground({ + type: SERVICE_TYPES.GET_MIGRATABLE_ACCOUNTS, + })); + } catch (e) { + console.error(e); + } + + return { migratableAccounts }; +}; + +export const migrateAccounts = async ({ + balancesToMigrate, + isMergeSelected, + recommendedFee, +}: { + balancesToMigrate: BalanceToMigrate[]; + isMergeSelected: boolean; + recommendedFee: string; +}): Promise<{ + publicKey: string; + migratedAccounts: Array; + allAccounts: Array; + hasPrivateKey: boolean; + error: string; +}> => { + let publicKey = ""; + let migratedAccounts = [] as Array; + let allAccounts = [] as Array; + let hasPrivateKey = false; + let error = ""; + + try { + ({ + migratedAccounts, + allAccounts, + publicKey, + hasPrivateKey, + error, + } = await sendMessageToBackground({ + balancesToMigrate, + isMergeSelected, + recommendedFee, + type: SERVICE_TYPES.MIGRATE_ACCOUNTS, + })); + } catch (e) { + console.error(e); + } + + return { migratedAccounts, allAccounts, publicKey, hasPrivateKey, error }; +}; + export const getAccountBalances = async ({ publicKey, networkDetails, @@ -337,11 +458,13 @@ export const getAccountBalances = async ({ balances = resp.balances; subentryCount = resp.subentryCount; + // eslint-disable-next-line no-plusplus for (let i = 0; i < Object.keys(resp.balances).length; i++) { const k = Object.keys(resp.balances)[i]; const v: any = resp.balances[k]; if (v.liquidity_pool_id) { const server = stellarSdkServer(networkUrl); + // eslint-disable-next-line no-await-in-loop const lp = await server .liquidityPools() .liquidityPoolId(v.liquidity_pool_id) @@ -604,25 +727,8 @@ export const submitFreighterTransaction = ({ networkDetails.networkPassphrase, ); const server = stellarSdkServer(networkDetails.networkUrl); - const submitTx = async (): Promise => { - let submittedTx; - - try { - submittedTx = await server.submitTransaction(tx); - } catch (e) { - if (e.response.status === 504) { - // in case of 504, keep retrying this tx until submission succeeds or we get a different error - // https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/timeout - // https://developers.stellar.org/docs/encyclopedia/error-handling - return submitTx(); - } - throw e; - } - - return submittedTx; - }; - return submitTx(); + return submitTx({ server, tx }); }; export const submitFreighterSorobanTransaction = async ({ diff --git a/@shared/api/types.ts b/@shared/api/types.ts index 9d7af23540..294559e063 100644 --- a/@shared/api/types.ts +++ b/@shared/api/types.ts @@ -18,6 +18,8 @@ export interface UserInfo { publicKey: string; } +export type MigratableAccount = Account & { keyIdIndex: number }; + export interface Response { error: string; messagedId: number; @@ -51,6 +53,7 @@ export interface Response { sorobanRpcUrl: string; networksList: NetworkDetails[]; allAccounts: Array; + migratedAccounts: MigratedAccount[]; accountName: string; assetCode: string; assetCanonical: string; @@ -71,6 +74,10 @@ export interface Response { isAllowed: boolean; userInfo: UserInfo; allowList: string[]; + migratableAccounts: MigratableAccount[]; + balancesToMigrate: BalanceToMigrate[]; + isMergeSelected: boolean; + recommendedFee: string; } export interface BlockedDomains { @@ -182,6 +189,20 @@ export interface ErrorMessage { response?: Horizon.HorizonApi.ErrorResponseData.TransactionFailed; } +export interface BalanceToMigrate { + publicKey: string; + name: string; + minBalance: string; + xlmBalance: string; + trustlineBalances: Horizon.HorizonApi.BalanceLine[]; + keyIdIndex: number; +} + +export type MigratedAccount = BalanceToMigrate & { + newPublicKey: string; + isMigrated: boolean; +}; + declare global { interface Window { freighter: boolean; diff --git a/@shared/constants/services.ts b/@shared/constants/services.ts index 79681f7a27..1db8c77371 100644 --- a/@shared/constants/services.ts +++ b/@shared/constants/services.ts @@ -9,6 +9,7 @@ export enum SERVICE_TYPES { UPDATE_ACCOUNT_NAME = "UPDATE_ACCOUNT_NAME", GET_MNEMONIC_PHRASE = "GET_MNEMONIC_PHRASE", CONFIRM_MNEMONIC_PHRASE = "CONFIRM_MNEMONIC_PHRASE", + CONFIRM_MIGRATED_MNEMONIC_PHRASE = "CONFIRM_MIGRATED_MNEMONIC_PHRASE", RECOVER_ACCOUNT = "RECOVER_ACCOUNT", CONFIRM_PASSWORD = "CONFIRM_PASSWORD", REJECT_ACCESS = "REJECT_ACCESS", @@ -41,6 +42,9 @@ export enum SERVICE_TYPES { ADD_TOKEN_ID = "ADD_TOKEN_ID", GET_TOKEN_IDS = "GET_TOKEN_IDS", REMOVE_TOKEN_ID = "REMOVE_TOKEN_ID", + GET_MIGRATABLE_ACCOUNTS = "GET_MIGRATABLE_ACCOUNTS", + GET_MIGRATED_MNEMONIC_PHRASE = "GET_MIGRATED_MNEMONIC_PHRASE", + MIGRATE_ACCOUNTS = "MIGRATE_ACCOUNTS", } export enum EXTERNAL_SERVICE_TYPES { diff --git a/@shared/constants/stellar.ts b/@shared/constants/stellar.ts index 9d9cc70beb..5e1437cac0 100644 --- a/@shared/constants/stellar.ts +++ b/@shared/constants/stellar.ts @@ -66,3 +66,5 @@ export const DEFAULT_NETWORKS: Array = [ MAINNET_NETWORK_DETAILS, TESTNET_NETWORK_DETAILS, ]; + +export const BASE_RESERVE = 0.5 as const; diff --git a/@shared/helpers/migration.ts b/@shared/helpers/migration.ts new file mode 100644 index 0000000000..8f68c60cf0 --- /dev/null +++ b/@shared/helpers/migration.ts @@ -0,0 +1,44 @@ +import { BigNumber } from "bignumber.js"; + +export const getMigrationFeeAmount = ({ + recommendedFee, + trustlineBalancesLength, + isMergeSelected, +}: { + recommendedFee: string; + + trustlineBalancesLength: number; + isMergeSelected: boolean; +}) => { + /* the number of operations needs to complete the migration: + - 1 op to send the balance. This is always required + - For trustline balance(s), 1 op each to send them + - If we're merging, 1 op each to remove trustlines + - Plus one more tx to merge + */ + const opCount = + 1 + + (trustlineBalancesLength * (isMergeSelected ? 2 : 1) || 0) + + (isMergeSelected ? 1 : 0); + + return new BigNumber(recommendedFee).times(opCount); +}; + +export const calculateSenderMinBalance = ({ + minBalance, + recommendedFee, + trustlineBalancesLength, + isMergeSelected, +}: { + minBalance: string; + recommendedFee: string; + trustlineBalancesLength: number; + isMergeSelected: boolean; +}) => + new BigNumber(minBalance).plus( + getMigrationFeeAmount({ + recommendedFee, + trustlineBalancesLength, + isMergeSelected, + }), + ); diff --git a/@shared/helpers/package.json b/@shared/helpers/package.json index 1fb11efee6..434045fdb8 100644 --- a/@shared/helpers/package.json +++ b/@shared/helpers/package.json @@ -3,6 +3,7 @@ "prettier": "@stellar/prettier-config", "version": "1.0.0", "dependencies": { + "bignumber.js": "^9.1.1", "stellar-sdk": "^11.0.1", "typescript": "~3.7.2" }, diff --git a/extension/package.json b/extension/package.json index fc4dffd99a..fca64d0345 100644 --- a/extension/package.json +++ b/extension/package.json @@ -73,7 +73,6 @@ "sass-loader": "8.0.0", "semver": "^7.5.4", "ses": "^0.18.5", - "simplebar-react": "^2.3.6", "stellar-hd-wallet": "^0.0.10", "stellar-identicon-js": "^1.0.0", "stellar-sdk": "^11.0.1", @@ -99,7 +98,6 @@ "allowScripts": { "@testing-library/react>@testing-library/dom>aria-query>@babel/runtime-corejs3>core-js-pure": false, "css-loader>webpack>watchpack>watchpack-chokidar2>chokidar>fsevents": false, - "simplebar-react>simplebar>core-js": false, "stellar-hd-wallet>stellar-base>sodium-native": false, "stellar-sdk>stellar-base>sodium-native": false } diff --git a/extension/src/background/ducks/session.ts b/extension/src/background/ducks/session.ts index f763ac7a62..f02f17d41f 100644 --- a/extension/src/background/ducks/session.ts +++ b/extension/src/background/ducks/session.ts @@ -8,16 +8,19 @@ const initialState = { privateKey: "", mnemonicPhrase: "", allAccounts: [] as Array, + migratedMnemonicPhrase: "", }; interface UiData { publicKey: string; mnemonicPhrase?: string; allAccounts?: Array; + migratedMnemonicPhrase?: string; } interface AppData { - privateKey: string; + privateKey?: string; + password?: string; } export const sessionSlice = createSlice({ @@ -41,7 +44,7 @@ export const sessionSlice = createSlice({ }, logOut: () => initialState, setActivePrivateKey: (state, action: { payload: AppData }) => { - const { privateKey } = action.payload; + const { privateKey = "" } = action.payload; return { ...state, @@ -57,6 +60,25 @@ export const sessionSlice = createSlice({ privateKey: "", }; }, + setPassword: (state, action: { payload: AppData }) => { + const { password } = action.payload; + + return { + ...state, + password, + }; + }, + setMigratedMnemonicPhrase: ( + state, + action: { payload: { migratedMnemonicPhrase: string } }, + ) => { + const { migratedMnemonicPhrase = "" } = action.payload; + + return { + ...state, + migratedMnemonicPhrase, + }; + }, timeoutAccountAccess: (state) => ({ ...state, privateKey: "" }), updateAllAccountsAccountName: ( state, @@ -96,6 +118,8 @@ export const { setActivePublicKey, timeoutAccountAccess, updateAllAccountsAccountName, + setPassword, + setMigratedMnemonicPhrase, }, } = sessionSlice; @@ -107,6 +131,10 @@ export const mnemonicPhraseSelector = createSelector( sessionSelector, (session) => session.mnemonicPhrase, ); +export const migratedMnemonicPhraseSelector = createSelector( + sessionSelector, + (session) => session.migratedMnemonicPhrase, +); export const allAccountsSelector = createSelector( sessionSelector, (session) => session.allAccounts || [], @@ -115,10 +143,14 @@ export const hasPrivateKeySelector = createSelector( sessionSelector, async (session) => { const isHardwareWalletActive = await getIsHardwareWalletActive(); - return isHardwareWalletActive || !!session.privateKey.length; + return isHardwareWalletActive || !!session?.privateKey?.length; }, ); export const privateKeySelector = createSelector( sessionSelector, - (session) => session.privateKey, + (session) => session.privateKey || "", +); +export const passwordSelector = createSelector( + sessionSelector, + (session) => session.password, ); diff --git a/extension/src/background/helpers/dataStorage.ts b/extension/src/background/helpers/dataStorage.ts index c1eea46ad8..94f9800f1f 100644 --- a/extension/src/background/helpers/dataStorage.ts +++ b/extension/src/background/helpers/dataStorage.ts @@ -172,7 +172,7 @@ const migrateTestnetSorobanRpcUrlNetworkDetails = async () => { const currentNetwork = await localStore.getItem(NETWORK_ID); - if (currentNetwork.network === NETWORKS.TESTNET) { + if (currentNetwork && currentNetwork.network === NETWORKS.TESTNET) { await localStore.setItem(NETWORK_ID, TESTNET_NETWORK_DETAILS); } diff --git a/extension/src/background/helpers/migration.ts b/extension/src/background/helpers/migration.ts new file mode 100644 index 0000000000..3c51d087fa --- /dev/null +++ b/extension/src/background/helpers/migration.ts @@ -0,0 +1,129 @@ +import { + Asset, + Horizon, + Keypair, + Operation, + TransactionBuilder, +} from "stellar-sdk"; +import { submitTx } from "@shared/api/helpers/stellarSdkServer"; +import BigNumber from "bignumber.js"; + +interface MigrateTrustLinesParams { + trustlineBalances: Horizon.HorizonApi.BalanceLine[]; + server: Horizon.Server; + sourceAccount: Horizon.AccountResponse; + sourceKeys: Keypair; + newKeyPair: { publicKey: string; privateKey: string }; + fee: string; + isMergeSelected: boolean; + networkPassphrase: string; +} + +/* + Migrating a trustline from one account is done in 2 separate transactions: + 1. Add the trustline to the destination account + 2. Send the entire trustline balance from the source account to the destination account + 3. (Optional) If we want to later merge the source account, we need to remove the trustline after sending the balance + + We repeat for every trustline +*/ + +export const migrateTrustlines = async ({ + trustlineBalances, + server, + newKeyPair, + fee, + sourceAccount, + sourceKeys, + isMergeSelected, + networkPassphrase, +}: MigrateTrustLinesParams) => { + if (!trustlineBalances.length) return; + + const trustlineRecipientAccount = await server.loadAccount( + newKeyPair.publicKey, + ); + const txFee = new BigNumber(fee).times(trustlineBalances.length).toString(); + + const changeTrustTx = await new TransactionBuilder( + trustlineRecipientAccount, + { + fee: txFee, + networkPassphrase, + }, + ); + + const removeTrustTx = await new TransactionBuilder(sourceAccount, { + fee: txFee, + networkPassphrase, + }); + + const sendTrustlineBalanceTx = await new TransactionBuilder(sourceAccount, { + fee: txFee, + networkPassphrase, + }); + + const recipientSourceKeys = Keypair.fromSecret(newKeyPair.privateKey); + + for (let i = 0; i < trustlineBalances.length; i += 1) { + const bal = trustlineBalances[i]; + let asset; + if ("asset_code" in bal && "asset_issuer" in bal) { + asset = new Asset(bal.asset_code, bal.asset_issuer); + + changeTrustTx.addOperation( + Operation.changeTrust({ + asset, + }), + ); + + if (new BigNumber(bal.balance).gt("0")) { + sendTrustlineBalanceTx.addOperation( + Operation.payment({ + destination: newKeyPair.publicKey, + asset, + amount: bal.balance, + }), + ); + } + + if (isMergeSelected) { + // remove the trustline from the source account + removeTrustTx.addOperation( + Operation.changeTrust({ + asset, + limit: "0", + }), + ); + } + } + } + + const builtChangeTrustTx = changeTrustTx.setTimeout(180).build(); + const builtSendTrustlineBalanceTx = sendTrustlineBalanceTx + .setTimeout(180) + .build(); + + try { + builtChangeTrustTx.sign(recipientSourceKeys); + } catch (e) { + console.error(e); + } + + await submitTx({ server, tx: builtChangeTrustTx }); + + try { + builtSendTrustlineBalanceTx.sign(sourceKeys); + } catch (e) { + console.error(e); + } + + await submitTx({ server, tx: builtSendTrustlineBalanceTx }); + + if (isMergeSelected) { + const builtRemoveTrustTx = removeTrustTx.setTimeout(180).build(); + builtRemoveTrustTx.sign(sourceKeys); + + await submitTx({ server, tx: builtRemoveTrustTx }); + } +}; diff --git a/extension/src/background/messageListener/popupMessageListener.ts b/extension/src/background/messageListener/popupMessageListener.ts index 269c831474..d9d2fdd591 100644 --- a/extension/src/background/messageListener/popupMessageListener.ts +++ b/extension/src/background/messageListener/popupMessageListener.ts @@ -1,19 +1,33 @@ import { Store } from "redux"; -import { Keypair, Transaction, TransactionBuilder, hash } from "stellar-sdk"; +import { + Keypair, + Networks, + Operation, + Transaction, + TransactionBuilder, + hash, +} from "stellar-sdk"; import { KeyManager, KeyManagerPlugins, KeyType } from "@stellar/wallet-sdk"; import browser from "webextension-polyfill"; // @ts-ignore import { fromMnemonic, generateMnemonic } from "stellar-hd-wallet"; +import { BigNumber } from "bignumber.js"; import { SERVICE_TYPES } from "@shared/constants/services"; import { APPLICATION_STATE } from "@shared/constants/applicationState"; import { WalletType } from "@shared/constants/hardwareWallet"; +import { + stellarSdkServer, + submitTx, +} from "@shared/api/helpers/stellarSdkServer"; +import { calculateSenderMinBalance } from "@shared/helpers/migration"; import { Account, Response as Request, BlockedDomains, BlockedAccount, + MigratableAccount, } from "@shared/api/types"; import { MessageResponder } from "background/types"; @@ -41,6 +55,7 @@ import { FUTURENET_NETWORK_DETAILS, MAINNET_NETWORK_DETAILS, NetworkDetails, + NETWORK_URLS, } from "@shared/constants/stellar"; import { EXPERIMENTAL } from "constants/featureFlag"; @@ -67,6 +82,8 @@ import { dataStorageAccess, browserLocalStorage, } from "background/helpers/dataStorage"; +import { migrateTrustlines } from "background/helpers/migration"; +import { xlmToStroop } from "helpers/stellar"; import { allAccountsSelector, @@ -74,19 +91,25 @@ import { privateKeySelector, logIn, logOut, + migratedMnemonicPhraseSelector, mnemonicPhraseSelector, publicKeySelector, setActivePublicKey, setActivePrivateKey, + setPassword, + setMigratedMnemonicPhrase, timeoutAccountAccess, updateAllAccountsAccountName, reset, + passwordSelector, } from "background/ducks/session"; import { STELLAR_EXPERT_BLOCKED_DOMAINS_URL, STELLAR_EXPERT_BLOCKED_ACCOUNTS_URL, } from "background/constants/apiUrls"; +// number of public keys to auto-import +const numOfPublicKeysToCheck = 5; const sessionTimer = new SessionTimer(); export const responseQueue: Array<(message?: any) => void> = []; @@ -262,6 +285,74 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { }); }; + /* + _replaceAccount is only used during the migration process. It is analagous to _storeAccount above. + 1. We login with the new account, which sets the new active public key and new allAccounts in Redux for the UI to consume + 2. We save the key store in storage + 3. We save the new account name in storage + */ + const _replaceAccount = async ({ + mnemonicPhrase, + password, + keyPair, + indexToReplace, + }: { + mnemonicPhrase: string; + password: string; + keyPair: KeyPair; + indexToReplace: number; + }) => { + const { publicKey, privateKey } = keyPair; + + const allAccounts = allAccountsSelector(sessionStore.getState()); + const accountName = `Account ${indexToReplace + 1}`; + const newAllAccounts = [...allAccounts]; + + newAllAccounts[indexToReplace] = { + publicKey, + name: accountName, + imported: false, + }; + + sessionStore.dispatch( + logIn({ + publicKey, + mnemonicPhrase, + allAccounts: newAllAccounts, + }), + ); + + const keyMetadata = { + key: { + extra: { imported: false, mnemonicPhrase }, + type: KeyType.plaintextKey, + publicKey, + privateKey, + }, + + password, + encrypterName: KeyManagerPlugins.ScryptEncrypter.name, + }; + + let keyStore = { id: "" }; + + try { + keyStore = await keyManager.storeKey(keyMetadata); + } catch (e) { + console.error(e); + } + + const keyIdListArr = await getKeyIdList(); + keyIdListArr[indexToReplace] = keyStore.id; + + await localStore.setItem(KEY_ID_LIST, keyIdListArr); + await localStore.setItem(KEY_ID, keyStore.id); + await addAccountName({ + keyId: keyStore.id, + accountName, + }); + }; + const _activatePublicKey = async ({ publicKey }: { publicKey: string }) => { const allAccounts = allAccountsSelector(sessionStore.getState()); let publicKeyIndex = allAccounts.findIndex( @@ -621,6 +712,16 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { }; }; + const confirmMigratedMnemonicPhrase = () => { + const isCorrectPhrase = + migratedMnemonicPhraseSelector(sessionStore.getState()) === + request.mnemonicPhraseToConfirm; + + return { + isCorrectPhrase, + }; + }; + const recoverAccount = async () => { const { password, recoverMnemonic } = request; let wallet; @@ -658,7 +759,6 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { ); // lets check first couple of accounts and pre-load them if funded on mainnet - const numOfPublicKeysToCheck = 5; // eslint-disable-next-line no-restricted-syntax for (let i = 1; i <= numOfPublicKeysToCheck; i += 1) { try { @@ -845,6 +945,8 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { ); } + sessionStore.dispatch(setPassword({ password })); + return { publicKey: publicKeySelector(sessionStore.getState()), hasPrivateKey: await hasPrivateKeySelector(sessionStore.getState()), @@ -1264,6 +1366,241 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { return { tokenIdList: updatedTokenIdList }; }; + const getMigratableAccounts = async () => { + const keyIdList = await getKeyIdList(); + + const mnemonicPhrase = mnemonicPhraseSelector(sessionStore.getState()); + const allAccounts = allAccountsSelector(sessionStore.getState()); + const wallet = fromMnemonic(mnemonicPhrase); + + const mnemonicPublicKeyArr: string[] = []; + + // a bit of brute force; we'll check the number of keyIds the user has plus the number of keyIds we auto-import. + const numberOfKeyIdsToCheck = keyIdList.length + numOfPublicKeysToCheck; + + for (let i = 0; i < numberOfKeyIdsToCheck; i += 1) { + mnemonicPublicKeyArr.push(wallet.getPublicKey(i)); + } + + // only use accounts that were derived from the mnemonic phrase + const migratableAccounts: MigratableAccount[] = []; + + allAccounts.forEach((acct, i) => { + if (mnemonicPublicKeyArr.includes(acct.publicKey)) { + migratableAccounts.push({ ...acct, keyIdIndex: i }); + } + }); + + return { + migratableAccounts, + }; + }; + + const getMigratedMnemonicPhrase = () => { + const migratedMnemonicPhrase = generateMnemonic({ entropyBits: 128 }); + + sessionStore.dispatch( + setMigratedMnemonicPhrase({ migratedMnemonicPhrase }), + ); + + return { mnemonicPhrase: migratedMnemonicPhrase }; + }; + + const migrateAccounts = async () => { + const { balancesToMigrate, isMergeSelected, recommendedFee } = request; + + const migratedMnemonicPhrase = migratedMnemonicPhraseSelector( + sessionStore.getState(), + ); + const migratedAccounts = []; + + const password = passwordSelector(sessionStore.getState()); + if (!password || !migratedMnemonicPhrase) + return { error: "Authentication error" }; + + const newWallet = fromMnemonic(migratedMnemonicPhrase); + const keyIdList: string = await getKeyIdList(); + const fee = xlmToStroop(recommendedFee).toFixed(); + + // we expect all migrations to be done on MAINNET + const server = stellarSdkServer(NETWORK_URLS.PUBLIC); + const networkPassphrase = Networks.PUBLIC; + + /* + For each migratable balance, we'll go through the following steps: + 1. We create a new keypair that will be the destination account + 2. We send the minimum amount of XLM needed to create the destination acct and also provide + enough funds to create necessary trustlines + 3. Replace the old source account with the destination account in redux and in local storage. + When the user refreshes the app, they will already be logged into their new accounts. + 4. Migrate the trustlines from the source account to destination + 5. Start an account session with the destination account so the user can start signing tx's with their newly migrated account + */ + + for (let i = 0; i < balancesToMigrate.length; i += 1) { + const { + publicKey, + xlmBalance, + minBalance, + trustlineBalances, + keyIdIndex, + } = balancesToMigrate[i]; + const migratedAccount = { + ...balancesToMigrate[i], + newPublicKey: "", + isMigrated: true, + }; + + const keyID = keyIdList[keyIdIndex]; + + // eslint-disable-next-line no-await-in-loop + const store = await _unlockKeystore({ password, keyID }); + + // eslint-disable-next-line no-await-in-loop + const sourceAccount = await server.loadAccount(publicKey); + + // create a new keystore and migrate while replacing the keyId in the list + const newKeyPair = { + publicKey: newWallet.getPublicKey(keyIdIndex), + privateKey: newWallet.getSecret(keyIdIndex), + }; + + // eslint-disable-next-line no-await-in-loop + const transaction = await new TransactionBuilder(sourceAccount, { + fee, + networkPassphrase, + }); + + // the amount the sender needs to hold to complete the migration + const senderAccountMinBal = calculateSenderMinBalance({ + minBalance, + recommendedFee, + trustlineBalancesLength: trustlineBalances.length, + isMergeSelected, + }); + + const startingBalance = new BigNumber(xlmBalance) + .minus(senderAccountMinBal) + .toString(); + + transaction.addOperation( + Operation.createAccount({ + destination: newKeyPair.publicKey, + startingBalance, + }), + ); + + const sourceKeys = Keypair.fromSecret(store.privateKey); + const builtTransaction = transaction.setTimeout(180).build(); + + try { + builtTransaction.sign(sourceKeys); + } catch (e) { + console.error(e); + } + + try { + // eslint-disable-next-line no-await-in-loop + await submitTx({ server, tx: builtTransaction }); + } catch (e) { + console.error(e); + migratedAccount.isMigrated = false; + } + + // if the preceding step has failed, this will fail as well. Don't bother making the API call + if (migratedAccount.isMigrated) { + try { + // now that the destination accounts are funded, we can add the trustline balances + // eslint-disable-next-line no-await-in-loop + await migrateTrustlines({ + trustlineBalances, + server, + newKeyPair, + fee, + sourceAccount, + sourceKeys, + isMergeSelected, + networkPassphrase, + }); + } catch (e) { + console.error(e); + migratedAccount.isMigrated = false; + } + } + + // if any of the preceding steps have failed, this will fail as well. Don't bother making the API call + if (isMergeSelected && migratedAccount.isMigrated) { + // since we're doing a merge, we can merge the old account into the new one, which will delete the old account + // eslint-disable-next-line no-await-in-loop + const mergeTransaction = await new TransactionBuilder(sourceAccount, { + fee, + networkPassphrase, + }); + mergeTransaction.addOperation( + Operation.accountMerge({ + destination: newKeyPair.publicKey, + }), + ); + + const builtMergeTransaction = mergeTransaction.setTimeout(180).build(); + + try { + builtMergeTransaction.sign(sourceKeys); + } catch (e) { + console.error(e); + } + + try { + // eslint-disable-next-line no-await-in-loop + await submitTx({ server, tx: builtMergeTransaction }); + } catch (e) { + console.error(e); + migratedAccount.isMigrated = false; + } + } + + if (migratedAccount.isMigrated) { + // replace the source account with the new one in `allAccounts` and store the keys + // eslint-disable-next-line no-await-in-loop + await _replaceAccount({ + mnemonicPhrase: migratedMnemonicPhrase, + password, + keyPair: newKeyPair, + indexToReplace: keyIdIndex, + }); + } + + migratedAccount.newPublicKey = newKeyPair.publicKey; + migratedAccounts.push(migratedAccount); + } + + const successfullyMigratedAccts = migratedAccounts.filter( + ({ isMigrated }) => isMigrated, + ); + + // if any of the accounts have been successfully migrated, go ahead and log in + if (successfullyMigratedAccts.length) { + // let's make the first public key the active one + await _activatePublicKey({ publicKey: newWallet.getPublicKey(0) }); + + sessionStore.dispatch(timeoutAccountAccess()); + + sessionTimer.startSession(); + sessionStore.dispatch( + setActivePrivateKey({ privateKey: newWallet.getSecret(0) }), + ); + } + + const currentState = sessionStore.getState(); + + return { + migratedAccounts, + publicKey: publicKeySelector(currentState), + allAccounts: allAccountsSelector(currentState), + hasPrivateKey: await hasPrivateKeySelector(currentState), + }; + }; + const messageResponder: MessageResponder = { [SERVICE_TYPES.CREATE_ACCOUNT]: createAccount, [SERVICE_TYPES.FUND_ACCOUNT]: fundAccount, @@ -1279,6 +1616,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { [SERVICE_TYPES.CHANGE_NETWORK]: changeNetwork, [SERVICE_TYPES.GET_MNEMONIC_PHRASE]: getMnemonicPhrase, [SERVICE_TYPES.CONFIRM_MNEMONIC_PHRASE]: confirmMnemonicPhrase, + [SERVICE_TYPES.CONFIRM_MIGRATED_MNEMONIC_PHRASE]: confirmMigratedMnemonicPhrase, [SERVICE_TYPES.RECOVER_ACCOUNT]: recoverAccount, [SERVICE_TYPES.CONFIRM_PASSWORD]: confirmPassword, [SERVICE_TYPES.GRANT_ACCESS]: grantAccess, @@ -1307,6 +1645,9 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { [SERVICE_TYPES.GET_TOKEN_IDS]: getTokenIds, [SERVICE_TYPES.REMOVE_TOKEN_ID]: removeTokenId, [SERVICE_TYPES.GET_BLOCKED_ACCOUNTS]: getBlockedAccounts, + [SERVICE_TYPES.GET_MIGRATABLE_ACCOUNTS]: getMigratableAccounts, + [SERVICE_TYPES.GET_MIGRATED_MNEMONIC_PHRASE]: getMigratedMnemonicPhrase, + [SERVICE_TYPES.MIGRATE_ACCOUNTS]: migrateAccounts, }; return messageResponder[request.type](); diff --git a/extension/src/popup/Router.tsx b/extension/src/popup/Router.tsx index 720587e030..432bcef445 100644 --- a/extension/src/popup/Router.tsx +++ b/extension/src/popup/Router.tsx @@ -64,6 +64,7 @@ import { Swap } from "popup/views/Swap"; import { ManageNetwork } from "popup/views/ManageNetwork"; import { PinExtension } from "popup/views/PinExtension"; import { LeaveFeedback } from "popup/views/LeaveFeedback"; +import { AccountMigration } from "popup/views/AccountMigration"; import "popup/metrics/views"; import { DEV_SERVER } from "@shared/constants/services"; @@ -345,6 +346,9 @@ export const Router = () => { + + + {DEV_SERVER && ( <> diff --git a/extension/src/popup/basics/Forms/index.tsx b/extension/src/popup/basics/Forms/index.tsx index 22a7e747a2..1ae119617c 100644 --- a/extension/src/popup/basics/Forms/index.tsx +++ b/extension/src/popup/basics/Forms/index.tsx @@ -11,9 +11,19 @@ export const FormRows = ({ children }: FormRowsProps) => ( interface SubmitButtonWrapperProps { children: React.ReactNode; + isCenterAligned?: boolean; } -export const SubmitButtonWrapper = ({ children }: SubmitButtonWrapperProps) => ( -
{children}
+export const SubmitButtonWrapper = ({ + children, + isCenterAligned, +}: SubmitButtonWrapperProps) => ( +
+ {children} +
); interface ErrorMessageProps { diff --git a/extension/src/popup/basics/Forms/styles.scss b/extension/src/popup/basics/Forms/styles.scss index b4e3b94ce3..bc3dd047cc 100644 --- a/extension/src/popup/basics/Forms/styles.scss +++ b/extension/src/popup/basics/Forms/styles.scss @@ -6,6 +6,11 @@ .SubmitButtonWrapper { margin-top: 1.5rem; + + &--center { + display: flex; + justify-content: center; + } } .FormError { diff --git a/extension/src/popup/basics/ListNavLink/index.tsx b/extension/src/popup/basics/ListNavLink/index.tsx index 860a00c312..eb8060e5e8 100644 --- a/extension/src/popup/basics/ListNavLink/index.tsx +++ b/extension/src/popup/basics/ListNavLink/index.tsx @@ -34,6 +34,27 @@ export const ListNavLink = ({ ); }; +interface ListNavButtonLinkProps { + children: string | React.ReactNode; + handleClick: () => void; +} + +export const ListNavButtonLink = ({ + children, + handleClick, +}: ListNavButtonLinkProps) => ( +
+ { + e.preventDefault(); + handleClick(); + }} + > + {children} + +
+); + export const ListNavLinkWrapper = ({ children, }: { diff --git a/extension/src/popup/basics/ListNavLink/styles.scss b/extension/src/popup/basics/ListNavLink/styles.scss index b520be569c..600e95177c 100644 --- a/extension/src/popup/basics/ListNavLink/styles.scss +++ b/extension/src/popup/basics/ListNavLink/styles.scss @@ -1,9 +1,11 @@ .ListNavLink { line-height: 1.5rem; - a { + a, + span { align-items: center; color: var(--color-gray-90); + cursor: pointer; display: flex; justify-content: space-between; } diff --git a/extension/src/popup/basics/Modal/index.tsx b/extension/src/popup/basics/Modal/index.tsx index f101c345d0..3247373915 100644 --- a/extension/src/popup/basics/Modal/index.tsx +++ b/extension/src/popup/basics/Modal/index.tsx @@ -1,5 +1,4 @@ import React, { useEffect } from "react"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import "./styles.scss"; @@ -25,9 +24,9 @@ export const ModalWrapper = ({ children }: ModalWrapperProps) => { }, []); return ( - +
{children}
- +
); }; diff --git a/extension/src/popup/basics/SimpleBarWrapper/index.tsx b/extension/src/popup/basics/SimpleBarWrapper/index.tsx deleted file mode 100644 index 3a2acc8f58..0000000000 --- a/extension/src/popup/basics/SimpleBarWrapper/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import SimpleBar from "simplebar-react"; -import "simplebar-react/dist/simplebar.min.css"; - -export const SimpleBarWrapper = ({ - children, - className = "", - ...props -}: React.HTMLAttributes) => ( - - {children} - -); diff --git a/extension/src/popup/basics/buttons/BackButton/index.tsx b/extension/src/popup/basics/buttons/BackButton/index.tsx index a252877ea5..5c2b5e0a61 100644 --- a/extension/src/popup/basics/buttons/BackButton/index.tsx +++ b/extension/src/popup/basics/buttons/BackButton/index.tsx @@ -8,25 +8,33 @@ interface BackButtonProps { customBackAction?: () => void; customBackIcon?: React.ReactNode; hasBackCopy?: boolean; + customButtonComponent?: React.ReactElement; } export const BackButton = ({ customBackAction, customBackIcon, hasBackCopy, + customButtonComponent, }: BackButtonProps) => { const history = useHistory(); + const handleClick = () => { + if (customBackAction) { + customBackAction(); + } else { + history.goBack(); + } + }; + + if (customButtonComponent) { + return React.cloneElement(customButtonComponent, { onClick: handleClick }); + } + return (
{ - if (customBackAction) { - customBackAction(); - } else { - history.goBack(); - } - }} + onClick={handleClick} > {customBackIcon || } {hasBackCopy ?
Back
: null} diff --git a/extension/src/popup/basics/layout/Box/index.tsx b/extension/src/popup/basics/layout/Box/index.tsx new file mode 100644 index 0000000000..1a4144fe8f --- /dev/null +++ b/extension/src/popup/basics/layout/Box/index.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import { addStyleClasses } from "popup/helpers/addStyleClasses"; + +import "./styles.scss"; + +interface BoxProps { + display?: "grid" | "flex"; + children: React.ReactElement | React.ReactElement[]; + gridCellWidth?: string; + gapHorizontal?: string; + gapVertical?: string; + isFlexRow?: boolean; +} + +export const Box: React.FC = ({ + display = "grid", + children, + gridCellWidth, + gapHorizontal, + gapVertical, + isFlexRow, + ...props +}: BoxProps) => { + const customStyle = { + ...(gridCellWidth ? { "--Box-grid-cell-width": gridCellWidth } : {}), + ...(gapHorizontal ? { "--Box-gap-horizontal": gapHorizontal } : {}), + ...(gapVertical ? { "--Box-gap-vertical": gapVertical } : {}), + ...(display === "grid" + ? { + "grid-template-columns": `repeat(auto-fit, ${ + gridCellWidth || "100%" + })`, + } + : {}), + } as React.CSSProperties; + + return ( +
+ {children} +
+ ); +}; diff --git a/extension/src/popup/basics/layout/Box/styles.scss b/extension/src/popup/basics/layout/Box/styles.scss new file mode 100644 index 0000000000..296f0bbf1e --- /dev/null +++ b/extension/src/popup/basics/layout/Box/styles.scss @@ -0,0 +1,30 @@ +@use "../../../styles/utils.scss" as *; + +.Box { + --Box-grid-cell-width: auto; + --Box-gap-horizontal: 0; + --Box-gap-vertical: 0; + + gap: var(--Box-gap-vertical) var(--Box-gap-horizontal); + + &--grid { + display: grid; + } + + &--flex { + display: flex; + flex-direction: column; + width: var(--Box-grid-cell-width); + + &--row { + flex-direction: row; + // TODO: make this configurable + justify-content: space-between; + gap: var(--Box-gap-horizontal); + + & > * { + flex: 1; + } + } + } +} diff --git a/extension/src/popup/basics/layout/View/index.tsx b/extension/src/popup/basics/layout/View/index.tsx new file mode 100644 index 0000000000..458a3b3f18 --- /dev/null +++ b/extension/src/popup/basics/layout/View/index.tsx @@ -0,0 +1,286 @@ +import React, { createContext, useContext } from "react"; +import { Title } from "@stellar/design-system"; + +import FreighterLogo from "popup/assets/logo-freighter.svg"; +import { BackButton } from "popup/basics/buttons/BackButton"; +import { addStyleClasses } from "popup/helpers/addStyleClasses"; + +import "./styles.scss"; + +interface ViewContextProps { + isAppLayout?: boolean; +} + +const ViewContext = createContext({ isAppLayout: undefined }); + +// Header +interface ViewHeaderProps { + showFreighterLogo?: boolean; + showBottomBorder?: boolean; +} + +const ViewHeader: React.FC = ({ + showFreighterLogo = true, + showBottomBorder = true, + ...props +}: ViewHeaderProps) => ( +
+ +
+ {showFreighterLogo ? ( + Freighter logo + ) : null} +
+
+
+); + +// App header +interface ViewAppHeaderProps { + leftContent?: React.ReactNode; + rightContent?: React.ReactNode; + centerContent?: React.ReactNode; + pageTitle?: React.ReactNode; + pageSubtitle?: React.ReactNode; + hasBackButton?: boolean; + customBackAction?: () => void; + customBackIcon?: React.ReactNode; + children?: React.ReactNode; +} + +const ViewAppHeader: React.FC = ({ + leftContent, + rightContent, + centerContent, + pageTitle, + pageSubtitle, + hasBackButton, + customBackAction, + customBackIcon, + children, + ...props +}: ViewAppHeaderProps) => ( +
+ + {/* Left */} +
+ {hasBackButton ? ( + + ) : null} + + {leftContent ?? null} +
+ + {/* Center */} + {centerContent ? ( +
+ {centerContent} +
+ ) : ( +
+
+ + {pageTitle} + +
+ {pageSubtitle ? ( +
{pageSubtitle}
+ ) : null} +
+ )} + + {/* Right */} +
+ {rightContent} +
+
+ + {children} +
+); + +// Content +interface ViewContentProps { + children: React.ReactNode; + // TODO: handle other cases: "start", "end" + alignment?: "center"; + contentFooter?: React.ReactNode; + hasNoTopPadding?: boolean; +} + +const ViewContent: React.FC = ({ + children, + alignment, + contentFooter, + hasNoTopPadding, + ...props +}: ViewContentProps) => { + const { isAppLayout } = useContext(ViewContext); + + return ( +
+ + {children} + + {contentFooter ? ( + + {contentFooter} + + ) : null} +
+ ); +}; + +// Footer +interface ViewFooterProps { + children: React.ReactNode; + customHeight?: string; + customGap?: string; + hasExtraPaddingBottom?: boolean; + hasTopBorder?: boolean; + isInline?: boolean; + allowWrap?: boolean; + style?: React.CSSProperties; +} + +const ViewFooter: React.FC = ({ + children, + customHeight, + customGap, + hasExtraPaddingBottom, + hasTopBorder, + isInline, + allowWrap, + style, + ...props +}: ViewFooterProps) => { + const customStyle = { + ...(customHeight ? { "--View-footer-height": customHeight } : {}), + ...(hasExtraPaddingBottom + ? { "--View-footer-padding-bottom": "1.5rem" } + : {}), + ...(customGap ? { "--View-footer-gap": customGap } : {}), + } as React.CSSProperties; + + return ( +
+ + {children} + +
+ ); +}; + +// Inset +interface ViewInsetProps { + children: React.ReactNode; + // Using wide layout for onboarding and similar views + isWide?: boolean; + // Align items inline + isInline?: boolean; + // TODO: handle other cases: "start", "end" + alignment?: "center"; + hasVerticalBorder?: boolean; + hasTopBorder?: boolean; + additionalClassName?: string; + hasScrollShadow?: boolean; + hasNoTopPadding?: boolean; +} + +export const ViewInset: React.FC = ({ + children, + isWide, + isInline, + alignment, + hasVerticalBorder, + hasTopBorder, + additionalClassName, + hasScrollShadow, + hasNoTopPadding, + ...props +}: ViewInsetProps) => { + const customStyle = { + ...(hasNoTopPadding ? { "--View-inset-padding-top": "0" } : {}), + } as React.CSSProperties; + + return ( +
+ {children} +
+ ); +}; + +// View +interface ViewComponent { + Header: React.FC; + AppHeader: React.FC; + Content: React.FC; + Footer: React.FC; + Inset: React.FC; +} + +interface ViewLayoutProps { + children: React.ReactNode; + isAppLayout?: boolean; +} + +export const View: React.FC & ViewComponent = ({ + children, + // Most views have "app" layout, so defaulting to that + isAppLayout = true, + ...props +}: ViewLayoutProps) => ( + +
+ {children} +
+
+); + +View.Header = ViewHeader; +View.AppHeader = ViewAppHeader; +View.Content = ViewContent; +View.Footer = ViewFooter; +View.Inset = ViewInset; diff --git a/extension/src/popup/basics/layout/View/styles.scss b/extension/src/popup/basics/layout/View/styles.scss new file mode 100644 index 0000000000..b0945b3534 --- /dev/null +++ b/extension/src/popup/basics/layout/View/styles.scss @@ -0,0 +1,216 @@ +@use "../../../styles/utils.scss" as *; + +.View { + --View-header-height: #{pxToRem(60px)}; + --View-inset-padding-top: #{pxToRem(24px)}; + --View-footer-height: #{pxToRem(80px)}; + --View-footer-padding-bottom: 1.5rem; + --View-footer-gap: 1rem; + + position: relative; + width: 100%; + min-width: var(--popup--width); + // dvh = dynamic viewport height + height: 100dvh; + min-height: var(--popup--height); + display: flex; + flex-direction: column; + justify-content: space-between; + overflow: hidden; + + &__header { + min-height: var(--View-header-height); + flex-shrink: 0; + flex-grow: 0; + + &--tall { + --View-header-height: #{pxToRem(80px)}; + } + + &--border { + border-bottom: 1px solid var(--color-gray-40); + } + + &__logo { + width: pxToRem(100px); + height: pxToRem(24px); + display: block; + } + + &__box { + display: flex; + align-items: center; + height: var(--View-header-height); + gap: 1rem; + + &--left { + justify-content: flex-start; + } + + &--center { + justify-content: center; + flex-direction: column; + flex: 1; + } + + &--right { + justify-content: flex-end; + } + } + + &__subtitle { + font-size: pxToRem(14px); + line-height: pxToRem(22px); + color: var(--color-gray-60); + margin-top: -1.5rem; + } + } + + &__contentAndFooterWrapper { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + } + + &__content { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; + + .View__inset { + flex: 1; + width: 100%; + overflow: auto; + padding-top: var(--View-inset-padding-top); + padding-bottom: 1.5rem; + + // TODO: update styling + &--scroll-shadows { + // https://css-tricks.com/books/greatest-css-tricks/scroll-shadows/ + --bgRGB: 0, 0, 0; + --bg: rgb(var(--bgRGB)); + --bgTrans: rgba(var(--bgRGB), 0); + --shadow: rgba(255, 255, 255, 0.2); + + background: + /* Shadow Cover TOP */ linear-gradient( + var(--bg) 30%, + var(--bgTrans) + ) + center top, + /* Shadow Cover BOTTOM */ + linear-gradient(var(--bgTrans), var(--bg) 70%) center bottom, + /* Shadow TOP */ + radial-gradient( + farthest-side at 50% 0, + var(--shadow), + rgba(0, 0, 0, 0.1) + ) + center top, + /* Shadow BOTTOM */ + radial-gradient( + farthest-side at 50% 100%, + var(--shadow), + rgba(0, 0, 0, 0.1) + ) + center bottom; + + background-repeat: no-repeat; + background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px; + background-attachment: local, local, scroll, scroll; + + // Hide scrollbar + &::-webkit-scrollbar { + // Chrome, Edge, Brave, etc + display: none; + // Firefox + scrollbar-width: none; + // Old Microsoft browsers + -ms-overflow-style: none; + } + } + + &__footer { + flex: none; + padding-bottom: var(--View-footer-padding-bottom); + padding-top: 0; + } + } + } + + &__footer { + height: var(--View-footer-height); + flex-shrink: 0; + flex-grow: 0; + + .View__inset { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + gap: var(--View-footer-gap); + padding-bottom: var(--View-footer-padding-bottom); + height: 100%; + + &--inline { + flex-direction: row; + justify-content: space-between; + align-items: flex-end; + + & > * { + flex: 1; + } + } + } + + &--wrap { + height: fit-content; + + .View__inset { + flex-wrap: wrap; + padding-top: var(--View-footer-padding-bottom); + } + } + } + + &__inset { + position: relative; + margin: 0 auto; + // 848px = 796 + 16 * 2 (padding) + 20 (scrollbar) + max-width: pxToRem(848px); + padding-left: 1rem; + padding-right: 1rem; + + &--align-center { + display: flex; + flex-direction: column; + align-items: center; + } + + &--wide { + // 1200px = 1200 + 16 * 2 (padding) + max-width: pxToRem(1232px); + } + + &--inline { + display: flex; + flex-direction: row; + justify-content: space-between; + height: 100%; + gap: 1rem; + } + + &--vertical-border { + @media (min-width: 850px) { + border-left: 1px solid var(--color-gray-40); + border-right: 1px solid var(--color-gray-40); + } + } + + &--top-border { + border-top: 1px solid var(--color-gray-40); + } + } +} diff --git a/extension/src/popup/components/BottomNav/index.tsx b/extension/src/popup/components/BottomNav/index.tsx index 19bacb0568..5f26b65899 100644 --- a/extension/src/popup/components/BottomNav/index.tsx +++ b/extension/src/popup/components/BottomNav/index.tsx @@ -2,6 +2,7 @@ import React from "react"; import { NavLink } from "react-router-dom"; import { useTranslation } from "react-i18next"; +import { View } from "popup/basics/layout/View"; import { ROUTES } from "popup/constants/routes"; import HistoryIcon from "popup/assets/icon-history.svg"; @@ -30,19 +31,21 @@ export const BottomNav = () => { const { t } = useTranslation(); return ( -
- - wallet icon - - - history icon - - - swap icon - - - settings icon - -
+ +
+ + wallet icon + + + history icon + + + swap icon + + + settings icon + +
+
); }; diff --git a/extension/src/popup/components/BottomNav/styles.scss b/extension/src/popup/components/BottomNav/styles.scss index 5ab45235f6..c217cd358a 100644 --- a/extension/src/popup/components/BottomNav/styles.scss +++ b/extension/src/popup/components/BottomNav/styles.scss @@ -1,18 +1,17 @@ .BottomNav { - border-top: solid 1px var(--color-gray-40); display: flex; align-items: center; justify-content: space-between; - height: var(--bottom-nav--height); - top: calc(var(--popup--height) - var(--bottom-nav--height)); - position: absolute; width: 100%; - - img { - margin: 1rem 2rem; - } + height: 100%; + padding-top: 1rem; &--link { + display: block; + width: 4.875rem; + display: flex; + align-items: center; + justify-content: center; filter: brightness(0) saturate(100%) invert(31%) sepia(6%) saturate(476%) hue-rotate(195deg) brightness(100%) contrast(89%); diff --git a/extension/src/popup/components/Header/index.tsx b/extension/src/popup/components/Header/index.tsx deleted file mode 100644 index 8371b4db99..0000000000 --- a/extension/src/popup/components/Header/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; - -import FreighterLogo from "popup/assets/logo-freighter.svg"; - -import "./styles.scss"; - -interface HeaderProps { - isPopupView?: boolean; -} - -export const Header = ({ isPopupView = false }: HeaderProps) => ( -
-
- Freighter logo -
-
-); diff --git a/extension/src/popup/components/Header/styles.scss b/extension/src/popup/components/Header/styles.scss deleted file mode 100644 index 0c663b5d31..0000000000 --- a/extension/src/popup/components/Header/styles.scss +++ /dev/null @@ -1,18 +0,0 @@ -.Header { - border-bottom: 1px solid var(--color-gray-30); - padding: 1.5rem 0; - - &__popup { - max-width: 0; - padding: 0; - border: none; - } - - &__fullscreen { - align-items: center; - display: flex; - justify-content: space-between; - margin: 0 auto; - max-width: var(--fullscreen--max-width); - } -} diff --git a/extension/src/popup/components/Loading/index.tsx b/extension/src/popup/components/Loading/index.tsx index 30014b2592..26df90319f 100644 --- a/extension/src/popup/components/Loading/index.tsx +++ b/extension/src/popup/components/Loading/index.tsx @@ -1,12 +1,13 @@ import React from "react"; import { Loader } from "@stellar/design-system"; +import { View } from "popup/basics/layout/View"; import "./styles.scss"; export const Loading = () => ( -
-
+ +
-
+ ); diff --git a/extension/src/popup/components/Loading/styles.scss b/extension/src/popup/components/Loading/styles.scss index 458f37c638..1a19ce6c95 100644 --- a/extension/src/popup/components/Loading/styles.scss +++ b/extension/src/popup/components/Loading/styles.scss @@ -1,14 +1,13 @@ .Loading { - height: 100%; - overflow: hidden; + display: flex; + justify-content: center; + align-items: center; position: absolute; - width: 100%; - z-index: var(--z-index--loader); - - &__wrapper { - display: flex; - height: calc(100vh - var(--header--height)); - justify-content: center; - align-items: center; - } + top: 0; + right: 0; + bottom: 0; + left: 0; + min-height: 6rem; + min-width: 6rem; + overflow: hidden; } diff --git a/extension/src/popup/components/Onboarding/index.tsx b/extension/src/popup/components/Onboarding/index.tsx index 8a9359a425..72a83f59f8 100644 --- a/extension/src/popup/components/Onboarding/index.tsx +++ b/extension/src/popup/components/Onboarding/index.tsx @@ -1,75 +1,113 @@ import React from "react"; import { useHistory } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { Button, Heading } from "@stellar/design-system"; import { BackButton } from "popup/basics/buttons/BackButton"; +import { Box } from "popup/basics/layout/Box"; import "./styles.scss"; +interface OnboardingProps { + children: React.ReactNode; + layout: "half" | "full"; + customWidth?: string; +} + export const Onboarding = ({ - customBackAction, - hasGoBackBtn, children, -}: { - customBackAction?: () => void; - hasGoBackBtn?: boolean; - children: React.ReactNode; -}) => { - const history = useHistory(); - const isNewTabSession = history.length === 1; + layout, + customWidth, +}: OnboardingProps) => { + const customStyle = { + ...(!customWidth && layout === "full" + ? { "--Onboarding-layout-width": "100%" } + : {}), + ...(customWidth ? { "--Onboarding-layout-width": customWidth } : {}), + } as React.CSSProperties; return ( -
- {hasGoBackBtn && !isNewTabSession ? ( -
- -
- ) : null} - {children} +
+ <>{children}
); }; interface OnboardingHeaderProps { - className?: string; children: React.ReactNode; } -export const OnboardingHeader = ({ - className, +export const OnboardingHeader = ({ children }: OnboardingHeaderProps) => ( + + {children} + +); + +export const OnboardingOneCol = ({ children, ...props -}: OnboardingHeaderProps) => ( -
+}: { + children: React.ReactElement | React.ReactElement[]; +}) => ( + {children} -
+ ); -interface OnboardingScreenProps { - className?: string; - children: React.ReactNode; -} - -export const OnboardingScreen = ({ - className, +export const OnboardingTwoCol = ({ children, ...props -}: OnboardingScreenProps) => ( -
+}: { + children: React.ReactElement | React.ReactElement[]; +}) => ( + {children} -
+ ); -interface OnboardingHalfScreenProps { - className?: string; - children: React.ReactNode; +interface OnboardingButtonsProps { + hasGoBackBtn?: boolean; + customBackAction?: () => void; + children?: React.ReactElement; } -export const OnboardingHalfScreen = ({ - className, +export const OnboardingButtons = ({ + hasGoBackBtn, + customBackAction, children, - ...props -}: OnboardingHalfScreenProps) => ( -
- {children} -
-); +}: OnboardingButtonsProps) => { + const history = useHistory(); + const { t } = useTranslation(); + + const isNewTabSession = history.length === 1; + const showBackButton = hasGoBackBtn && !isNewTabSession; + + if (children || showBackButton) { + return ( + + <> + {showBackButton ? ( + + {t("Back")} + + } + customBackAction={customBackAction} + /> + ) : null} + + {children} + + + ); + } + + return null; +}; diff --git a/extension/src/popup/components/Onboarding/styles.scss b/extension/src/popup/components/Onboarding/styles.scss index b1005f63cc..793368a18a 100644 --- a/extension/src/popup/components/Onboarding/styles.scss +++ b/extension/src/popup/components/Onboarding/styles.scss @@ -1,18 +1,31 @@ .Onboarding { - margin: 4.5rem auto 0; - max-width: var(--fullscreen--max-width); - width: 100%; + --Onboarding-layout-width: auto; + + margin-top: 4.5rem; + display: flex; + flex-direction: column; + gap: 1.5rem; + width: var(--Onboarding-layout-width); + color: var(--color-gray-70); - &--back { - margin-bottom: -1.3rem; + @media (max-width: 832px) { + // Center content when going from two cols to one + width: auto !important; + } + + .Heading { + color: var(--color-gray-80); } -} -.OnboardingHeader { - font-size: 2.5rem; - line-height: 120%; - color: var(--color-gray-90); - font-weight: var(--font-weight-medium); + .Checkbox label { + color: var(--color-gray-70); + } + + .Link--secondary { + --Link-color-default: var(--color-gray-80); + --Link-color-hover: var(--color-gray-90); + --Link-color-disabled: var(--color-gray-70); + } } .OnboardingScreen { @@ -23,9 +36,3 @@ width: 100%; margin: auto; } - -.OnboardingHalfScreen { - display: flex; - padding: 2rem 0 2rem 1.55rem; - width: 27rem; -} diff --git a/extension/src/popup/components/PasswordRequirements/styles.scss b/extension/src/popup/components/PasswordRequirements/styles.scss index 62907e4407..98b4bb2e9f 100644 --- a/extension/src/popup/components/PasswordRequirements/styles.scss +++ b/extension/src/popup/components/PasswordRequirements/styles.scss @@ -1,8 +1,5 @@ .PasswordRequirements { - padding: 1.25rem 0 2.5rem; - &__list { - color: var(--color-gray-60) !important; list-style-type: disc !important; list-style-position: inside; font-size: 1rem; diff --git a/extension/src/popup/components/SlideupModal/styles.scss b/extension/src/popup/components/SlideupModal/styles.scss index 59de9f4f2f..1896fbe022 100644 --- a/extension/src/popup/components/SlideupModal/styles.scss +++ b/extension/src/popup/components/SlideupModal/styles.scss @@ -4,6 +4,6 @@ box-shadow: 0 1rem 1.5rem rgba(0, 0, 0, 0.24); position: absolute; transition: all var(--dropdown-animation); - width: var(--popup--width); + width: 100%; z-index: var(--z-index-modal); } diff --git a/extension/src/popup/components/SubviewHeader/index.tsx b/extension/src/popup/components/SubviewHeader/index.tsx index b50892c60c..13c6198f80 100644 --- a/extension/src/popup/components/SubviewHeader/index.tsx +++ b/extension/src/popup/components/SubviewHeader/index.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { BackButton } from "popup/basics/buttons/BackButton"; +import { View } from "popup/basics/layout/View"; import "./styles.scss"; @@ -8,6 +8,7 @@ interface SubviewHeaderProps { customBackAction?: () => void; customBackIcon?: React.ReactNode; title: string; + subtitle?: React.ReactNode; hasBackButton?: boolean; rightButton?: React.ReactNode; } @@ -16,18 +17,16 @@ export const SubviewHeader = ({ customBackAction, customBackIcon, title, + subtitle, hasBackButton = true, rightButton, }: SubviewHeaderProps) => ( -
- {hasBackButton ? ( - - ) : null} -
{title}
- {rightButton || - (hasBackButton ?
: null)} -
+ ); diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index fe07da651b..cb45f0ddaf 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -30,6 +30,7 @@ import { ManageAssetRow, NewAssetFlags, } from "popup/components/manageAssets/ManageAssetRows"; +import { View } from "popup/basics/layout/View"; import { useNetworkFees } from "popup/helpers/useNetworkFees"; import { publicKeySelector, @@ -351,96 +352,103 @@ export const ScamAssetWarning = ({ return (
-
-
Warning
-
- {t( - "This asset was tagged as fraudulent by stellar.expert, a reliable community-maintained directory.", - )} -
-
- -
-
-
- {isSendWarning ? ( - -

- {t( - "Trading or sending this asset is not recommended. Projects related to this asset may be fraudulent even if the creators say otherwise.", - )} -

-
- ) : ( - -
-

- {isValidatingSafeAssetsEnabled - ? t( - "Freighter automatically blocked this asset. Projects related to this asset may be fraudulent even if the creators say otherwise.", - ) - : t( - "Projects related to this asset may be fraudulent even if the creators say otherwise. ", - )} -

-

- {t("You can")}{" "} - {`${ - isValidatingSafeAssetsEnabled ? t("disable") : t("enable") - }`}{" "} - {t("this alert by going to")}{" "} - {t("Settings > Preferences")} -

-
-
+ +
+
Warning
+
+ {t( + "This asset was tagged as fraudulent by stellar.expert, a reliable community-maintained directory.", )}
-
- - {isSendWarning && ( - - )} - {!isValidatingSafeAssetsEnabled && !isSendWarning && ( +
+ +
+
+
+ {isSendWarning ? ( + +

+ {t( + "Trading or sending this asset is not recommended. Projects related to this asset may be fraudulent even if the creators say otherwise.", + )} +

+
+ ) : ( + +
+

+ {isValidatingSafeAssetsEnabled + ? t( + "Freighter automatically blocked this asset. Projects related to this asset may be fraudulent even if the creators say otherwise.", + ) + : t( + "Projects related to this asset may be fraudulent even if the creators say otherwise. ", + )} +

+

+ {t("You can")}{" "} + {`${ + isValidatingSafeAssetsEnabled + ? t("disable") + : t("enable") + }`}{" "} + {t("this alert by going to")}{" "} + {t("Settings > Preferences")} +

+
+
+ )} +
+
- )} -
{" "} + {isSendWarning && ( + + )} + {!isValidatingSafeAssetsEnabled && !isSendWarning && ( + + )} +
{" "} +
-
+
); }; @@ -539,101 +547,103 @@ export const NewAssetWarning = ({ return (
-
-
- {t("Before You Add This Asset")} -
-
- {t( - "Please double-check its information and characteristics. This can help you identify fraudulent assets.", - )} -
-
- -
-
-
- {isRevocable && ( -
-
- revocable -
-
-
- {t("Revocable Asset")} -
-
- {t( - "The asset creator can revoke your access to this asset at anytime", - )} -
-
-
- )} -
- {isNewAsset && ( -
-
- new asset -
-
-
- {t("New Asset")} -
-
- {t("This is a relatively new asset.")} -
-
-
+ +
+
+ {t("Before You Add This Asset")} +
+
+ {t( + "Please double-check its information and characteristics. This can help you identify fraudulent assets.", )}
-
- {isInvalidDomain && ( +
+ +
+
+
+ {isRevocable && (
- invalid domain + revocable
- {t("Invalid Format Asset")} + {t("Revocable Asset")}
{t( - "Asset home domain doesn’t exist, TOML file format is invalid, or asset doesn't match currency description", + "The asset creator can revoke your access to this asset at anytime", )}
)} -
-
- - +
+ {isNewAsset && ( +
+
+ new asset +
+
+
+ {t("New Asset")} +
+
+ {t("This is a relatively new asset.")} +
+
+
+ )} +
+
+ {isInvalidDomain && ( +
+
+ invalid domain +
+
+
+ {t("Invalid Format Asset")} +
+
+ {t( + "Asset home domain doesn’t exist, TOML file format is invalid, or asset doesn't match currency description", + )} +
+
+
+ )} +
+
+ + +
-
+
); }; diff --git a/extension/src/popup/components/WarningMessages/styles.scss b/extension/src/popup/components/WarningMessages/styles.scss index 34eaf3d917..03584e82c6 100644 --- a/extension/src/popup/components/WarningMessages/styles.scss +++ b/extension/src/popup/components/WarningMessages/styles.scss @@ -95,26 +95,27 @@ .ScamAssetWarning { z-index: var(--z-index--scam-warning); + display: flex; position: fixed; - height: calc(var(--popup--height)); - width: var(--popup--width); - top: 0; + height: fit-content; + width: 100%; + bottom: 0; left: 0; + .View__inset { + padding-bottom: 1rem; + } + &__wrapper { z-index: calc(var(--z-index--scam-warning) + 1); - position: absolute; - bottom: calc(-1 * var(--popup--height)); - height: calc(var(--popup--height) - 2rem); + height: 100%; + bottom: 0; display: flex; flex-direction: column; background: var(--color-gray-00); - margin-top: 2rem; transition: bottom var(--dropdown-animation); border-top-right-radius: 1rem; border-top-left-radius: 1rem; - padding: var(--popup-vertical-padding) var(--account-view-padding-side) 1rem - var(--account-view-padding-side); } &__header { @@ -139,6 +140,7 @@ display: flex; align-items: center; justify-content: space-between; + margin-bottom: 2rem; &__code { color: var(--color-gray-90); @@ -176,23 +178,24 @@ .NewAssetWarning { z-index: var(--z-index--scam-warning); position: fixed; - height: calc(var(--popup--height)); - width: var(--popup--width); - top: 0; + height: fit-content; + width: 100%; + bottom: 0; left: 0; + display: flex; + + .View__inset { + padding-bottom: 1rem; + } &__wrapper { z-index: calc(var(--z-index--scam-warning) + 1); - position: absolute; - bottom: calc(-1 * var(--popup--height)); display: flex; flex-direction: column; background: var(--color-gray-00); - margin-top: 2rem; transition: bottom var(--dropdown-animation); border-top-right-radius: 1rem; border-top-left-radius: 1rem; - padding: var(--popup-vertical-padding) var(--account-view-padding-side); } &__header { diff --git a/extension/src/popup/components/account/AccountHeader/index.tsx b/extension/src/popup/components/account/AccountHeader/index.tsx index b008a1fd16..2f941cbd5a 100644 --- a/extension/src/popup/components/account/AccountHeader/index.tsx +++ b/extension/src/popup/components/account/AccountHeader/index.tsx @@ -8,6 +8,7 @@ import { useTranslation } from "react-i18next"; import { ROUTES } from "popup/constants/routes"; import { LoadingBackground } from "popup/basics/LoadingBackground"; +import { View } from "popup/basics/layout/View"; import { isActiveNetwork } from "helpers/stellar"; import { AccountListIdenticon } from "popup/components/identicons/AccountListIdenticon"; import { @@ -24,14 +25,14 @@ import IconCube from "popup/assets/icon-cube.svg"; import "./styles.scss"; interface AccountHeaderProps { - accountDropDownRef: React.RefObject; + // accountDropDownRef: React.RefObject; allAccounts: Array; currentAccountName: string; publicKey: string; } export const AccountHeader = ({ - accountDropDownRef, + // accountDropDownRef, allAccounts, currentAccountName, publicKey, @@ -64,30 +65,33 @@ export const AccountHeader = ({ activeNetworkIndex.current = index; return ( -
-
setIsDropdownOpen(!isDropdownOpen)} - > - -
-
setIsNetworkSelectorOpen(!isNetworkSelectorOpen)} - > - -
- {networkDetails.networkName} + leftContent={ +
setIsDropdownOpen(!isDropdownOpen)} + > + +
+ } + rightContent={ +
setIsNetworkSelectorOpen(!isNetworkSelectorOpen)} + > + +
+ {networkDetails.networkName} +
-
+ } + > -
+ ); }; diff --git a/extension/src/popup/components/account/AccountHeader/styles.scss b/extension/src/popup/components/account/AccountHeader/styles.scss index cc555b0fc8..7c5d26c8c0 100644 --- a/extension/src/popup/components/account/AccountHeader/styles.scss +++ b/extension/src/popup/components/account/AccountHeader/styles.scss @@ -1,13 +1,9 @@ -:root { - --acount-row-height: 4.25rem; -} - .AccountHeader { display: flex; flex-direction: row; justify-content: space-between; align-items: center; - height: var(--acount-row-height); + height: 5rem; &__icon-btn { cursor: pointer; diff --git a/extension/src/popup/components/account/AccountList/styles.scss b/extension/src/popup/components/account/AccountList/styles.scss index 3f885418ea..92a7ff39fc 100644 --- a/extension/src/popup/components/account/AccountList/styles.scss +++ b/extension/src/popup/components/account/AccountList/styles.scss @@ -3,7 +3,7 @@ display: flex; justify-content: space-between; width: 100%; - height: var(--acount-row-height); + height: 5rem; align-items: center; > div { diff --git a/extension/src/popup/components/account/AssetDetail/index.tsx b/extension/src/popup/components/account/AssetDetail/index.tsx index be6193ea13..44894bd6c9 100644 --- a/extension/src/popup/components/account/AssetDetail/index.tsx +++ b/extension/src/popup/components/account/AssetDetail/index.tsx @@ -20,7 +20,6 @@ import { useAssetDomain } from "popup/helpers/useAssetDomain"; import { navigateTo } from "popup/helpers/navigate"; import { formatTokenAmount } from "popup/helpers/soroban"; import { getAssetFromCanonical } from "helpers/stellar"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import { ROUTES } from "popup/constants/routes"; import { PillButton } from "popup/basics/buttons/PillButton"; @@ -37,6 +36,7 @@ import { } from "popup/components/accountHistory/TransactionDetail"; import { SlideupModal } from "popup/components/SlideupModal"; import { SubviewHeader } from "popup/components/SubviewHeader"; +import { View } from "popup/basics/layout/View"; import { saveAsset, saveDestinationAsset, @@ -127,88 +127,90 @@ export const AssetDetail = ({ return isDetailViewShowing ? ( ) : ( -
-
- setSelectedAsset("")} - /> - {balance && "name" in balance && ( - {balance.name} - )} - {isNative ? ( -
- - {availableTotal} {t("available")} - - setIsModalOpen(true)} + + + + {availableTotal} {t("available")} + + setIsModalOpen(true)} + > + } />{" "} + +
+ ) : null + } + customBackAction={() => setSelectedAsset("")} + /> + +
+ {balance && "name" in balance && ( + {balance.name} + )} +
+
- } />{" "} - -
- ) : null} -
-
- {displayTotal} + {displayTotal} +
+
+ +
-
- +
+ {balance?.total && new BigNumber(balance?.total).toNumber() > 0 ? ( + <> + {/* Hide send for Soroban until send work is ready for Soroban tokens */} + {!isSorobanAsset && ( + { + dispatch(saveAsset(selectedAsset)); + navigateTo(ROUTES.sendPayment); + }} + > + {t("SEND")} + + )} + {!isSorobanAsset && ( + { + dispatch(saveAsset(selectedAsset)); + navigateTo(ROUTES.swap); + }} + > + {t("SWAP")} + + )} + + ) : ( + { + dispatch(saveDestinationAsset(selectedAsset)); + navigateTo(ROUTES.swap); + }} + > + {t("SWAP")} + + )}
-
-
- {balance?.total && new BigNumber(balance?.total).toNumber() > 0 ? ( - <> - {/* Hide send for Soroban until send work is ready for Soroban tokens */} - {!isSorobanAsset && ( - { - dispatch(saveAsset(selectedAsset)); - navigateTo(ROUTES.sendPayment); - }} - > - {t("SEND")} - - )} - {!isSorobanAsset && ( - { - dispatch(saveAsset(selectedAsset)); - navigateTo(ROUTES.swap); - }} - > - {t("SWAP")} - - )} - - ) : ( - { - dispatch(saveDestinationAsset(selectedAsset)); - navigateTo(ROUTES.swap); - }} - > - {t("SWAP")} - - )} -
-
{isOwnedScamAsset && ( @@ -263,8 +265,9 @@ export const AssetDetail = ({ {t("No transactions to show")}
)} -
-
+
+
+ {/* TODO: fix the slideup modal */} {isNative && (
@@ -316,6 +319,6 @@ export const AssetDetail = ({
)} -
+ ); }; diff --git a/extension/src/popup/components/account/AssetDetail/styles.scss b/extension/src/popup/components/account/AssetDetail/styles.scss index cdff5122d1..aba05a1e7c 100644 --- a/extension/src/popup/components/account/AssetDetail/styles.scss +++ b/extension/src/popup/components/account/AssetDetail/styles.scss @@ -1,10 +1,6 @@ .AssetDetail { - height: var(--popup--height); - overflow: hidden; - position: relative; - &__wrapper { - padding: var(--popup-vertical-padding) var(--popup--side-padding); + position: relative; } .SubviewHeader { diff --git a/extension/src/popup/components/accountHistory/HistoryList/index.tsx b/extension/src/popup/components/accountHistory/HistoryList/index.tsx index ebdbfbead0..f923402117 100644 --- a/extension/src/popup/components/accountHistory/HistoryList/index.tsx +++ b/extension/src/popup/components/accountHistory/HistoryList/index.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import "./styles.scss"; @@ -9,9 +8,9 @@ interface HistoryListProps { } export const HistoryList = ({ children, assetDetail }: HistoryListProps) => ( -
{children}
-
+
); diff --git a/extension/src/popup/components/accountHistory/HistoryList/styles.scss b/extension/src/popup/components/accountHistory/HistoryList/styles.scss index 3f6a3fcdc0..fdc98ad63c 100644 --- a/extension/src/popup/components/accountHistory/HistoryList/styles.scss +++ b/extension/src/popup/components/accountHistory/HistoryList/styles.scss @@ -1,12 +1,4 @@ .HistoryList { - max-height: 25rem; - padding-right: var(--popup--side-padding); - width: calc(var(--popup--width) - var(--popup--side-padding)); - - &--assetDetail { - max-height: 18rem; - } - &__items { display: flex; flex-direction: column; diff --git a/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx b/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx index 4627e655a9..059e36f03e 100644 --- a/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx +++ b/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx @@ -6,6 +6,7 @@ import { Button } from "@stellar/design-system"; import { KeyIdenticon } from "popup/components/identicons/KeyIdenticon"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { AssetNetworkInfo } from "popup/components/accountHistory/AssetNetworkInfo"; +import { View } from "popup/basics/layout/View"; import { emitMetric } from "helpers/metrics"; import { openTab } from "popup/helpers/navigate"; @@ -75,88 +76,92 @@ export const TransactionDetail = ({ const networkDetails = useSelector(settingsNetworkDetailsSelector); return assetIssuer && !assetDomain ? null : ( -
-
- setIsDetailViewShowing(false)} - title={headerTitle} - /> - {isPayment ? ( -
- {operationText} - -
- ) : null} + + setIsDetailViewShowing(false)} + title={headerTitle} + /> + +
+ {isPayment ? ( +
+ {operationText} + +
+ ) : null} -
-
- {isPayment && !isSwap ? ( - <> - {isRecipient ? ( - <> -
{t("From")}
-
- -
- - ) : ( - <> -
{t("To")}
-
- -
- - )} - - ) : ( - !isSwap && ( +
+
+ {isPayment && !isSwap ? ( <> -
{t("Action")}
-
{operationText}
+ {isRecipient ? ( + <> +
{t("From")}
+
+ +
+ + ) : ( + <> +
{t("To")}
+
+ +
+ + )} - ) - )} -
-
-
{t("Date")}
-
- {createdAtTime} • {createdAtDateStr} + ) : ( + !isSwap && ( + <> +
{t("Action")}
+
{operationText}
+ + ) + )} +
+
+
{t("Date")}
+
+ {createdAtTime} • {createdAtDateStr} +
+
+
+
{t("Memo")}
+
{memo || `None`}
+
+
+
{t("Transaction fee")}
+
{stroopToXlm(feeCharged).toString()} XLM
-
-
-
{t("Memo")}
-
{memo || `None`}
-
-
-
{t("Transaction fee")}
-
{stroopToXlm(feeCharged).toString()} XLM
-
- {!isCustomNetwork(networkDetails) ? ( - - ) : null} -
+ + + {!isCustomNetwork(networkDetails) ? ( + + ) : null} + + ); }; diff --git a/extension/src/popup/components/accountHistory/TransactionDetail/styles.scss b/extension/src/popup/components/accountHistory/TransactionDetail/styles.scss index dd85c75e4a..e24c805e14 100644 --- a/extension/src/popup/components/accountHistory/TransactionDetail/styles.scss +++ b/extension/src/popup/components/accountHistory/TransactionDetail/styles.scss @@ -3,11 +3,6 @@ } .TransactionDetail { - display: flex; - flex-direction: column; - height: var(--popup--height); - padding: var(--popup-vertical-padding) var(--popup--side-padding); - &__header { border-bottom: 1px solid var(--color-gray-40); color: var(--color-white); diff --git a/extension/src/popup/components/accountMigration/ConfirmMigration/index.tsx b/extension/src/popup/components/accountMigration/ConfirmMigration/index.tsx new file mode 100644 index 0000000000..6e1754aee4 --- /dev/null +++ b/extension/src/popup/components/accountMigration/ConfirmMigration/index.tsx @@ -0,0 +1,108 @@ +import React from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { + Button, + Heading, + Notification, + Loader, + Paragraph, +} from "@stellar/design-system"; +import { useTranslation } from "react-i18next"; +import { Formik, Form } from "formik"; + +import { ROUTES } from "popup/constants/routes"; +import { AppDispatch } from "popup/App"; +import { navigateTo } from "popup/helpers/navigate"; +import { useNetworkFees } from "popup/helpers/useNetworkFees"; +import { migrateAccounts } from "popup/ducks/accountServices"; +import { transactionDataSelector } from "popup/ducks/transactionSubmission"; + +import { + MigrationHeader, + MigrationBody, + MigrationButton, + MigrationParagraph, +} from "../basics"; + +import "./styles.scss"; + +export const ConfirmMigration = () => { + const { t } = useTranslation(); + const { recommendedFee } = useNetworkFees(); + const dispatch: AppDispatch = useDispatch(); + const { balancesToMigrate, isMergeSelected } = useSelector( + transactionDataSelector, + ); + + const handleCancel = () => { + window.close(); + }; + + const handleContinue = async () => { + const migrateAccountsRes = await dispatch( + migrateAccounts({ + balancesToMigrate, + isMergeSelected, + recommendedFee, + }), + ); + + if (migrateAccounts.fulfilled.match(migrateAccountsRes)) { + navigateTo(ROUTES.accountMigrationMigrationComplete); + } + }; + + return ( +
+ + {({ isSubmitting }) => ( +
+ {isSubmitting ? ( +
+ + +
{t("Migrating...")}
+
+ + {t("Please don’t close this window.")} + +
+ ) : ( + <> + + {t("Before we start with migration, please read")} + + + + {t( + "As long as you have your old and new mnemonics phrase, you’ll still be able to control accounts related to your current backup phrase which were not merged. For that, you’ll need to import your current backup phrase into Freighter (Freighter supports one backup phrase imported at a time).", + )} + + + {t( + "One of your accounts is a signer for another account. Freighter won’t migrate signing settings. For your safety, Freighter won’t merge accounts with signature set up so you can still control it.", + )} + + + + + + + + + )} +
+ )} +
+
+ ); +}; diff --git a/extension/src/popup/components/accountMigration/ConfirmMigration/styles.scss b/extension/src/popup/components/accountMigration/ConfirmMigration/styles.scss new file mode 100644 index 0000000000..c5ef8cf5dc --- /dev/null +++ b/extension/src/popup/components/accountMigration/ConfirmMigration/styles.scss @@ -0,0 +1,9 @@ +.ConfirmMigration { + &__loader { + display: flex; + flex-direction: column; + gap: 1.5rem; + align-items: center; + margin-top: 8rem; + } +} diff --git a/extension/src/popup/components/accountMigration/MigrationComplete/index.tsx b/extension/src/popup/components/accountMigration/MigrationComplete/index.tsx new file mode 100644 index 0000000000..fb95a14516 --- /dev/null +++ b/extension/src/popup/components/accountMigration/MigrationComplete/index.tsx @@ -0,0 +1,105 @@ +import React from "react"; +import { useSelector } from "react-redux"; +import { Badge, Button, Icon } from "@stellar/design-system"; +import { useTranslation } from "react-i18next"; +import { MigratedAccount } from "@shared/api/types"; + +import { migratedAccountsSelector } from "popup/ducks/accountServices"; + +import { + MigrationButton, + MigrationHeader, + MigrationParagraph, + MigrationReviewHeader, + MigrationReviewListSection, + MigrationReviewListHeader, + MigrationReviewDetailRow, + MigrationReviewAccountInfo, + MigrationReviewBadge, +} from "../basics"; + +import "./styles.scss"; + +const handleClick = () => { + window.close(); +}; + +const AccountListItems = ({ + migratedAccounts, +}: { + migratedAccounts: MigratedAccount[]; +}) => { + const { t } = useTranslation(); + + return ( + <> + {migratedAccounts.map((acct) => ( + + <> +
+ + + +
+
+ +
+
+ + + + +
+ {acct.trustlineBalances.length} {t("trustlines")} +
+
+
+
+ + + {acct.isMigrated ? ( + {t("Migrated")} + ) : ( + {t("Not migrated")} + )} + + +
+ +
+ ))} + + ); +}; + +export const MigrationComplete = () => { + const { t } = useTranslation(); + + const migratedAccounts = useSelector(migratedAccountsSelector); + + return ( +
+ + {t("Migration complete")} + + {t( + "Remember, Freighter will now display accounts related to the new backup phrase that was just created. Use this backup phrase from now on to use your new accounts. If you have accounts that were not merged, keep and use your old backup phrase to access them.", + )} + + + + + + +
+ ); +}; diff --git a/extension/src/popup/components/accountMigration/MigrationComplete/styles.scss b/extension/src/popup/components/accountMigration/MigrationComplete/styles.scss new file mode 100644 index 0000000000..e6cf130563 --- /dev/null +++ b/extension/src/popup/components/accountMigration/MigrationComplete/styles.scss @@ -0,0 +1,32 @@ +.MigrationComplete { + margin-top: -7rem; + + &__arrow { + margin-top: 0.5rem; + + svg { + height: 1rem; + } + } + + &__migrated-account { + flex-grow: 1; + } + + .MigrationReviewListSection { + justify-content: initial; + gap: 2.5rem; + } + + .MigrationReviewAccountInfo { + margin-top: 0.5rem; + } + + .MigrationReviewAccountInfo__identicon-wrapper { + margin-top: -1.5rem; + } + + .MigrationReviewAccountInfo__text { + display: block; + } +} diff --git a/extension/src/popup/components/accountMigration/MigrationStart/index.tsx b/extension/src/popup/components/accountMigration/MigrationStart/index.tsx new file mode 100644 index 0000000000..6d7b64db0f --- /dev/null +++ b/extension/src/popup/components/accountMigration/MigrationStart/index.tsx @@ -0,0 +1,79 @@ +import React, { useState } from "react"; +import { useDispatch } from "react-redux"; +import { Button, Notification } from "@stellar/design-system"; +import { useTranslation } from "react-i18next"; + +import { ROUTES } from "popup/constants/routes"; +import { navigateTo } from "popup/helpers/navigate"; +import { changeNetwork } from "popup/ducks/settings"; +import { NETWORK_NAMES } from "@shared/constants/stellar"; + +import { + MigrationHeader, + MigrationBody, + MigrationParagraph, + MigrationButton, +} from "../basics"; + +import "./styles.scss"; + +export const MigrationStart = () => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [isConfirmed, setIsConfirmed] = useState(false); + + const handleContinue = async () => { + await dispatch(changeNetwork({ networkName: NETWORK_NAMES.PUBNET })); + navigateTo(ROUTES.accountMigrationReviewMigration); + }; + + return isConfirmed ? ( +
+ + {t("Make sure you have your 12 word backup phrase")} + + + + {t( + "At the end of this process, Freighter will only display accounts related to the new backup phrase. You’ll still be able to import your current backup phrase into Freighter and control current accounts as long as they were not merged into the new accounts.", + )} + + + {t( + "Make sure you have your current 12 words backup phrase before continuing.", + )} + + + + + +
+ ) : ( +
+ {t("Account Migration")} + + + {t( + "In this process, Freighter will create a new backup phrase for you and migrate your lumens, trustlines, and assets to the new account.", + )} + + + {t( + "You can choose to merge your current account into the new accounts after the migration, which will effectively destroy your current account. Merging is optional and will allow you to send your current account’s funding lumens to the new accounts.", + )} + + + + + +
+ ); +}; diff --git a/extension/src/popup/components/accountMigration/MigrationStart/styles.scss b/extension/src/popup/components/accountMigration/MigrationStart/styles.scss new file mode 100644 index 0000000000..811257ca0b --- /dev/null +++ b/extension/src/popup/components/accountMigration/MigrationStart/styles.scss @@ -0,0 +1,9 @@ +.MigrationStart { + display: flex; + flex-direction: column; + + &__button { + display: flex; + justify-content: flex-end; + } +} diff --git a/extension/src/popup/components/accountMigration/MnemonicPhrase/index.tsx b/extension/src/popup/components/accountMigration/MnemonicPhrase/index.tsx new file mode 100644 index 0000000000..1914dea237 --- /dev/null +++ b/extension/src/popup/components/accountMigration/MnemonicPhrase/index.tsx @@ -0,0 +1,47 @@ +import React, { useEffect, useState } from "react"; +import shuffle from "lodash/shuffle"; + +import { getMigratedMnemonicPhrase } from "@shared/api/internal"; + +import { ConfirmMnemonicPhrase } from "popup/components/mnemonicPhrase/ConfirmMnemonicPhrase"; +import { DisplayMnemonicPhrase } from "popup/components/mnemonicPhrase/DisplayMnemonicPhrase"; +import { Onboarding } from "popup/components/Onboarding"; + +import "./styles.scss"; + +export const MnemonicPhrase = () => { + const [isConfirmed, setIsConfirmed] = useState(false); + const [mnemonicPhrase, setMnemonicPhrase] = useState(""); + + useEffect(() => { + const fetchMnemonicPhrase = async () => { + const { + mnemonicPhrase: migratedMnemonicPhrase, + } = await getMigratedMnemonicPhrase(); + + setMnemonicPhrase(migratedMnemonicPhrase); + }; + fetchMnemonicPhrase(); + }, []); + + return isConfirmed ? ( + +
+ +
+
+ ) : ( + +
+ +
+
+ ); +}; diff --git a/extension/src/popup/components/accountMigration/MnemonicPhrase/styles.scss b/extension/src/popup/components/accountMigration/MnemonicPhrase/styles.scss new file mode 100644 index 0000000000..b78192a276 --- /dev/null +++ b/extension/src/popup/components/accountMigration/MnemonicPhrase/styles.scss @@ -0,0 +1,7 @@ +.MigrationMnemonicPhrase { + margin-top: 7.5rem; + + &--display { + margin-top: 5.5rem; + } +} diff --git a/extension/src/popup/components/accountMigration/ReviewMigration/index.tsx b/extension/src/popup/components/accountMigration/ReviewMigration/index.tsx new file mode 100644 index 0000000000..64e24f5268 --- /dev/null +++ b/extension/src/popup/components/accountMigration/ReviewMigration/index.tsx @@ -0,0 +1,405 @@ +import React, { useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { Badge, Button, Checkbox, Loader } from "@stellar/design-system"; +import { Horizon } from "stellar-sdk"; +import { getAccountInfo, getMigratableAccounts } from "@shared/api/internal"; +import { BalanceToMigrate } from "@shared/api/types"; +import { useTranslation } from "react-i18next"; +import { BigNumber } from "bignumber.js"; +import { Field, FieldProps, Form, Formik, useFormikContext } from "formik"; +import { object as YupObject, boolean as YupBoolean } from "yup"; + +import { ROUTES } from "popup/constants/routes"; +import { BASE_RESERVE } from "@shared/constants/stellar"; +import { + calculateSenderMinBalance, + getMigrationFeeAmount, +} from "@shared/helpers/migration"; + +import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; +import { + saveBalancesToMigrate, + saveIsMergeSelected, +} from "popup/ducks/transactionSubmission"; +import { useNetworkFees } from "popup/helpers/useNetworkFees"; + +import { navigateTo } from "popup/helpers/navigate"; + +import { + MigrationHeader, + MigrationButton, + MigrationParagraph, + MigrationReviewHeader, + MigrationReviewListSection, + MigrationReviewListHeader, + MigrationReviewHighlight, + MigrationReviewDetailRow, + MigrationReviewDescription, + MigrationReviewAccountInfo, + MigrationReviewBadge, +} from "../basics"; + +import "./styles.scss"; + +type AccountToMigrate = { + publicKey: string; + name: string; + trustlines: number; + dataEntries: number; + xlmBalance: string; + trustlineBalances: Horizon.HorizonApi.BalanceLine[]; + isSigner: boolean; + minBalance: string; + keyIdIndex: number; +}; + +interface FormValues { + isMergeSelected: boolean; +} + +const isReadyToMigrate = ({ + xlmBalance, + dataEntries, + isSigner, + minBalance, + recommendedFee, + trustlineBalancesLength, + isMergeSelected, +}: { + xlmBalance: string; + dataEntries: number; + isSigner: boolean; + minBalance: string; + recommendedFee: string; + trustlineBalancesLength: number; + isMergeSelected: boolean; +}) => + Boolean( + xlmBalance && + !dataEntries && + !isSigner && + calculateSenderMinBalance({ + minBalance, + recommendedFee, + trustlineBalancesLength, + isMergeSelected, + }) < new BigNumber(xlmBalance).minus(minBalance), + ); + +type AccountListItemRow = AccountToMigrate & { isReadyToMigrate: boolean }; + +const AccountListItems = ({ + accountList, + setIsSubmitDisabled, +}: { + accountList: AccountToMigrate[]; + setIsSubmitDisabled: (isSubmitDisabled: boolean) => void; +}) => { + const { t } = useTranslation(); + const { recommendedFee } = useNetworkFees(); + const formik = useFormikContext(); + const [accountListItems, setAccountListItems] = useState( + [] as AccountListItemRow[], + ); + + const { isMergeSelected } = formik.values; + + useEffect(() => { + const acctListItems: AccountListItemRow[] = []; + if (!recommendedFee) return; + accountList.forEach((acct) => { + const acctIsReadyToMigrate = isReadyToMigrate({ + xlmBalance: acct.xlmBalance, + dataEntries: acct.dataEntries, + isSigner: acct.isSigner, + minBalance: acct.minBalance, + recommendedFee, + trustlineBalancesLength: acct.trustlineBalances.length, + isMergeSelected, + }); + + if (!acctIsReadyToMigrate) { + setIsSubmitDisabled(true); + } + + acctListItems.push({ + ...acct, + isReadyToMigrate: acctIsReadyToMigrate, + }); + }); + + setAccountListItems(acctListItems); + }, [accountList, isMergeSelected, recommendedFee, setIsSubmitDisabled]); + + return accountListItems.length ? ( + <> + {accountListItems.map((acct) => ( + + {acct.xlmBalance ? ( + <> +
+ + + + +
+ {acct.trustlines} {t("trustlines")} +
+
+ +
+ {acct.dataEntries} {t("data entries")} +
+
+ +
+ {acct.isSigner ? t("Signs for external accounts") : ""} +
+
+
+ +
+ + + {acct.isReadyToMigrate ? ( + {t("Ready to migrate")} + ) : ( + {t("Unable to migrate")} + )} + + + + + + +
+ + ) : ( + <> + + + + + + {t("Not funded")} + + + + )} +
+ ))} + + ) : ( +
+ +
+ ); +}; + +export const ReviewMigration = () => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const networkDetails = useSelector(settingsNetworkDetailsSelector); + const [accountToMigrateList, setAccountToMigrateList] = useState( + [] as AccountToMigrate[], + ); + const [isSubmitDisabled, setIsSubmitDisabled] = useState(false); + const { recommendedFee } = useNetworkFees(); + + useEffect(() => { + const acctItemArr: AccountToMigrate[] = []; + let hasUnmigratableAccount = false; + + const fetchAccountData = async () => { + const { migratableAccounts } = await getMigratableAccounts(); + + if (!migratableAccounts || !recommendedFee) { + hasUnmigratableAccount = true; + return; + } + + // eslint-disable-next-line no-plusplus + for (let i = 0; i < migratableAccounts.length; i++) { + const publicKey = migratableAccounts[i].publicKey; + + // eslint-disable-next-line no-await-in-loop + const { account, isSigner } = await getAccountInfo({ + publicKey, + networkDetails, + }); + + const DEFAULT_ACCT_ITEM = { + publicKey, + name: migratableAccounts[i].name, + trustlines: 0, + dataEntries: 0, + xlmBalance: "", + trustlineBalances: [] as Horizon.HorizonApi.BalanceLine[], + isSigner, + minBalance: "", + keyIdIndex: migratableAccounts[i].keyIdIndex, + }; + + let acctItem = { + ...DEFAULT_ACCT_ITEM, + }; + + if (account) { + const minBalance = new BigNumber( + (2 + account.subentry_count) * BASE_RESERVE, + ).toString(); + + const xlmBalance = + account.balances[account.balances.length - 1].balance; + const dataEntries = Object.keys(account.data_attr).length; + const trustlineBalances = account.balances.filter( + ({ asset_type: assetType }) => assetType !== "native", + ); + + acctItem = { + ...DEFAULT_ACCT_ITEM, + trustlines: account.balances.length - 1, + dataEntries, + xlmBalance, + trustlineBalances, + minBalance, + }; + } else { + hasUnmigratableAccount = true; + } + + acctItemArr.push(acctItem); + } + + setAccountToMigrateList(acctItemArr); + setIsSubmitDisabled(hasUnmigratableAccount); + }; + + fetchAccountData(); + }, [networkDetails, recommendedFee]); + + const handleSubmit = (values: FormValues) => { + const migratableBalances: BalanceToMigrate[] = []; + accountToMigrateList.forEach( + ({ + publicKey, + name, + minBalance, + xlmBalance, + trustlineBalances, + keyIdIndex, + }) => { + migratableBalances.push({ + publicKey, + name, + minBalance, + xlmBalance, + trustlineBalances, + keyIdIndex, + }); + }, + ); + dispatch(saveBalancesToMigrate(migratableBalances)); + dispatch(saveIsMergeSelected(values.isMergeSelected)); + navigateTo(ROUTES.accountMigrationMnemonicPhrase); + }; + + const initialValues: FormValues = { + isMergeSelected: false, + }; + + const ReviewMigrationFormSchema = YupObject().shape({ + isMergeSelected: YupBoolean(), + }); + + return ( +
+ + {t("Review accounts to migrate")} + + {t("Only accounts ready for migration will be migrated.")} + + + + {({ isSubmitting }) => ( + <> + +
+
+ + {({ field }: FieldProps) => ( + + + {t( + "Merge accounts after migrating (your funding lumens used to fund the current accounts will be sent to the new ones - you lose access to the current accounts.)", + )} +
+ } + {...field} + /> + )} + +
+ + + + + + )} + +
+ ); +}; diff --git a/extension/src/popup/components/accountMigration/ReviewMigration/styles.scss b/extension/src/popup/components/accountMigration/ReviewMigration/styles.scss new file mode 100644 index 0000000000..79b8ad3e31 --- /dev/null +++ b/extension/src/popup/components/accountMigration/ReviewMigration/styles.scss @@ -0,0 +1,19 @@ +.ReviewMigration { + &__loader { + display: flex; + justify-content: space-evenly; + margin-bottom: 1.75rem; + } + + &__option { + color: var(--color-gray-60); + display: flex; + line-height: 1.375rem; + margin-bottom: 1rem; + } + + &__button { + display: flex; + justify-content: flex-end; + } +} diff --git a/extension/src/popup/components/accountMigration/basics/index.tsx b/extension/src/popup/components/accountMigration/basics/index.tsx new file mode 100644 index 0000000000..6e059eda01 --- /dev/null +++ b/extension/src/popup/components/accountMigration/basics/index.tsx @@ -0,0 +1,139 @@ +import React from "react"; +import { Heading, Paragraph } from "@stellar/design-system"; +import { useTranslation } from "react-i18next"; + +import { IdenticonImg } from "popup/components/identicons/IdenticonImg"; +import { truncatedPublicKey } from "helpers/stellar"; + +import "./styles.scss"; + +interface MigrationBasicProps { + children: React.ReactNode; +} + +export const MigrationHeader = ({ children }: MigrationBasicProps) => ( +
+ + {children} + +
+); + +type MigrationBodyProps = MigrationBasicProps & { + hasWarning?: boolean; +}; + +export const MigrationBody = ({ children, hasWarning }: MigrationBodyProps) => ( +
+ {children} +
+); + +export const MigrationParagraph = ({ children }: MigrationBasicProps) => ( + + {children} + +); + +export const MigrationButton = ({ children }: MigrationBasicProps) => ( + + {children} + +); + +export const MigrationReviewHeader = ({ children }: MigrationBasicProps) => ( +
{children}
+); + +type MigrationReviewListSectionProps = MigrationBasicProps & { + isUnfunded?: boolean; +}; + +export const MigrationReviewListSection = ({ + children, + isUnfunded, +}: MigrationReviewListSectionProps) => ( +
+ {children} +
+); + +export const MigrationReviewAccountRow = ({ + children, +}: MigrationBasicProps) => ( +
{children}
+); + +export const MigrationReviewDetailRow = ({ children }: MigrationBasicProps) => ( +
{children}
+); + +interface MigrationReviewHighlightProps { + text: string; +} + +export const MigrationReviewHighlight = ({ + text, +}: MigrationReviewHighlightProps) => ( + {text} +); + +interface MigrationReviewDescriptionProps { + description: string; + highlight: string; +} + +export const MigrationReviewDescription = ({ + description, + highlight, +}: MigrationReviewDescriptionProps) => { + const { t } = useTranslation(); + return ( +
+ {t(description)}:{" "} + +
+ ); +}; + +export const MigrationReviewListHeader = ({ + children, +}: MigrationBasicProps) => ( +
{children}
+); + +export const MigrationReviewAccountInfo = ({ + publicKey, + name, + isDisabled, +}: { + publicKey: string; + name: string; + isDisabled?: boolean; +}) => ( +
+
+ +
+
+
{name}
+
+ ({truncatedPublicKey(publicKey)}) +
+
+
+); + +export const MigrationReviewBadge = ({ children }: MigrationBasicProps) => ( +
{children}
+); diff --git a/extension/src/popup/components/accountMigration/basics/styles.scss b/extension/src/popup/components/accountMigration/basics/styles.scss new file mode 100644 index 0000000000..f0f6e221a2 --- /dev/null +++ b/extension/src/popup/components/accountMigration/basics/styles.scss @@ -0,0 +1,117 @@ +.MigrationHeader { + margin-bottom: 1.5rem; +} + +.MigrationBody { + margin-bottom: 2rem; + + &--warning { + margin-bottom: 1.5rem; + } +} + +.sds-theme-dark { + .MigrationParagraph { + color: var(--color-gray-70) !important; + } +} + +.MigrationButton { + display: flex; + gap: 1rem; + justify-content: flex-end; +} + +.MigrationReviewHeader { + border-bottom: 1px solid var(--color-gray-40); + margin-bottom: 1.5rem; + padding-bottom: 1.5rem; +} + +.MigrationReviewListSection { + border-bottom: 1px solid var(--color-gray-40); + display: flex; + justify-content: space-between; + margin-bottom: 1.75rem; + padding-bottom: 1.5rem; + + &--unfunded { + padding-bottom: 0.5rem; + } +} + +%migrationRow { + align-items: center; + display: flex; + justify-content: space-between; + margin-left: 2.5rem; +} + +.MigrationReviewListHeader { + margin-bottom: 1rem; +} + +.MigrationReviewAccountRow { + @extend %migrationRow; + line-height: 1.375rem; + margin: 0 0 1.0625rem 0; +} + +.MigrationReviewHighlight { + color: var(--color-white); +} + +.MigrationReviewDescription { + color: var(--color-gray-60); + font-size: 0.75rem; + line-height: 1.25rem; + text-align: right; +} + +.MigrationReviewDetailRow { + @extend %migrationRow; + color: var(--color-white); + font-size: 0.875rem; + line-height: 1.25rem; +} + +.MigrationReviewAccountInfo { + align-items: center; + display: flex; + font-size: 0.875rem; + gap: 0.5rem; + + &--isDisabled { + opacity: 0.5; + } + + &__text { + display: flex; + gap: 0.5rem; + line-height: 1.375rem; + } + + &__name { + color: var(--color-white); + font-weight: var(--font-weight-medium); + } + + &__identicon-wrapper { + border: 1px solid var(--color-gray-40); + border-radius: 2rem; + height: 2rem; + padding: 0.33rem; + width: 2rem; + } + + &__public-key { + color: var(--color-gray-60); + } +} + +.MigrationReviewBadge { + height: 32px; + display: flex; + align-items: center; + justify-content: end; +} diff --git a/extension/src/popup/components/manageAssets/AddAsset/index.tsx b/extension/src/popup/components/manageAssets/AddAsset/index.tsx index 99e90e4ad1..5ba91e793d 100644 --- a/extension/src/popup/components/manageAssets/AddAsset/index.tsx +++ b/extension/src/popup/components/manageAssets/AddAsset/index.tsx @@ -6,6 +6,7 @@ import { Networks, StellarToml } from "stellar-sdk"; import { useTranslation } from "react-i18next"; import { FormRows } from "popup/basics/Forms"; +import { View } from "popup/basics/layout/View"; import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; @@ -80,68 +81,64 @@ export const AddAsset = () => { {({ dirty, errors, isSubmitting, isValid, touched }) => (
-
+ - -
- - {({ field }: FieldProps) => ( - - )} - -
-
- {isCurrencyNotFound ? ( - - ) : null} - {assetRows.length ? ( - <> -
- {t("Assets found in this domain")} -
-
- + +
+ + {({ field }: FieldProps) => ( + -
- - ) : null} -
-
- -
- -
+ )} + +
+
+ {isCurrencyNotFound ? ( + + ) : null} + {assetRows.length ? ( + <> +
+ {t("Assets found in this domain")} +
+
+ +
+ + ) : null} +
+ + + + + +
)}
diff --git a/extension/src/popup/components/manageAssets/AddAsset/styles.scss b/extension/src/popup/components/manageAssets/AddAsset/styles.scss index 6ba8400432..5caf71c9f4 100644 --- a/extension/src/popup/components/manageAssets/AddAsset/styles.scss +++ b/extension/src/popup/components/manageAssets/AddAsset/styles.scss @@ -4,8 +4,6 @@ } .AddAsset { - padding: var(--popup-vertical-padding) var(--popup--side-padding); - &__title { font-size: var(--font-size-secondary); font-weight: var(--font-weight-medium); diff --git a/extension/src/popup/components/manageAssets/AddToken/index.tsx b/extension/src/popup/components/manageAssets/AddToken/index.tsx index 746565e23f..a06e76edd2 100644 --- a/extension/src/popup/components/manageAssets/AddToken/index.tsx +++ b/extension/src/popup/components/manageAssets/AddToken/index.tsx @@ -11,8 +11,8 @@ import { AppDispatch } from "popup/App"; import { navigateTo } from "popup/helpers/navigate"; import { emitMetric } from "helpers/metrics"; -import { FormRows, SubmitButtonWrapper } from "popup/basics/Forms"; -import { PopupWrapper } from "popup/basics/PopupWrapper"; +import { FormRows } from "popup/basics/Forms"; +import { View } from "popup/basics/layout/View"; import { SubviewHeader } from "popup/components/SubviewHeader"; @@ -46,12 +46,12 @@ export const AddToken = () => { }; return ( - <> - - - - {({ dirty, isSubmitting, isValid, errors, touched }) => ( -
+ + {({ dirty, isSubmitting, isValid, errors, touched }) => ( + + + + {({ field }: FieldProps) => ( @@ -70,23 +70,23 @@ export const AddToken = () => { /> )} - - - - - )} -
-
- +
+ + + + +
+ )} + ); }; diff --git a/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx b/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx index 7fc506457c..0b5109a53b 100644 --- a/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx +++ b/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx @@ -17,6 +17,7 @@ import { } from "popup/ducks/settings"; import { sorobanSelector } from "popup/ducks/soroban"; import { SubviewHeader } from "popup/components/SubviewHeader"; +import { View } from "popup/basics/layout/View"; import { getCanonicalFromAsset } from "helpers/stellar"; import { stellarSdkServer } from "@shared/api/helpers/stellarSdkServer"; @@ -133,41 +134,35 @@ export const ChooseAsset = ({ balances }: ChooseAssetProps) => { ]); return ( -
- {isLoading && ( -
- -
- )} + : undefined} /> -
-
- {managingAssets ? ( - - ) : ( - - )} + + {isLoading && ( +
+ +
+ )} +
+
+ {managingAssets ? ( + + ) : ( + + )} +
+
+ {managingAssets && ( -
+ <>
) : null} -
+ )} -
-
+ +
); }; diff --git a/extension/src/popup/components/manageAssets/ChooseAsset/styles.scss b/extension/src/popup/components/manageAssets/ChooseAsset/styles.scss index 0cc844397b..ab23b9f991 100644 --- a/extension/src/popup/components/manageAssets/ChooseAsset/styles.scss +++ b/extension/src/popup/components/manageAssets/ChooseAsset/styles.scss @@ -1,9 +1,7 @@ .ChooseAsset { - padding: var(--popup-vertical-padding) var(--popup--side-padding); - &__loader { - height: var(--popup--height); - width: var(--popup--width); + height: 100%; + width: 100%; z-index: calc(var(--back--button-z-index) + 1); position: absolute; display: flex; @@ -21,23 +19,19 @@ &__assets { flex-grow: 1; - height: 28.75rem; &--short { flex-grow: 1; - height: 22.5rem; } } - &__button-container { - display: flex; - flex-direction: column; - gap: 1.5rem; - } - &__button { a { color: var(--color-gray-90); } + + button { + text-wrap: nowrap; + } } } diff --git a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx index 7dc0ed742e..0674c5a7cd 100644 --- a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx +++ b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx @@ -26,7 +26,6 @@ import { truncateString, } from "helpers/stellar"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import { PillButton } from "popup/basics/buttons/PillButton"; import { LoadingBackground } from "popup/basics/LoadingBackground"; @@ -74,14 +73,12 @@ interface ManageAssetRowsProps { children?: React.ReactNode; header?: React.ReactNode; assetRows: ManageAssetCurrency[]; - maxHeight: number; } export const ManageAssetRows = ({ children, header, assetRows, - maxHeight, }: ManageAssetRowsProps) => { const { t } = useTranslation(); const publicKey = useSelector(publicKeySelector); @@ -341,12 +338,7 @@ export const ManageAssetRows = ({ }} /> )} - +
{header}
{assetRows.map( @@ -422,7 +414,7 @@ export const ManageAssetRows = ({ })}
{children} - +
{}} isActive={showNewAssetWarning || showBlockedDomainWarning} diff --git a/extension/src/popup/components/manageAssets/ManageAssetRows/styles.scss b/extension/src/popup/components/manageAssets/ManageAssetRows/styles.scss index 08267741f8..d89b61b42f 100644 --- a/extension/src/popup/components/manageAssets/ManageAssetRows/styles.scss +++ b/extension/src/popup/components/manageAssets/ManageAssetRows/styles.scss @@ -1,11 +1,4 @@ .ManageAssetRows { - &__scrollbar { - width: calc( - var(--popup--width) - var(--popup--side-padding) - - var(--popup--side-padding) - ); - } - &__content { display: flex; flex-direction: column; diff --git a/extension/src/popup/components/manageAssets/SearchAsset/index.tsx b/extension/src/popup/components/manageAssets/SearchAsset/index.tsx index 32aba40d87..d5b51bd15f 100644 --- a/extension/src/popup/components/manageAssets/SearchAsset/index.tsx +++ b/extension/src/popup/components/manageAssets/SearchAsset/index.tsx @@ -15,6 +15,7 @@ import { isCustomNetwork } from "helpers/stellar"; import { getApiStellarExpertUrl } from "popup/helpers/account"; import { SubviewHeader } from "popup/components/SubviewHeader"; +import { View } from "popup/basics/layout/View"; import { ManageAssetRows, ManageAssetCurrency } from "../ManageAssetRows"; @@ -69,7 +70,6 @@ export const SearchAsset = () => { const { t } = useTranslation(); const networkDetails = useSelector(settingsNetworkDetailsSelector); const [assetRows, setAssetRows] = useState([] as ManageAssetCurrency[]); - const [maxHeight, setMaxHeight] = useState(0); const [isSearching, setIsSearching] = useState(false); const [hasNoResults, setHasNoResults] = useState(false); const ResultsRef = useRef(null); @@ -124,7 +124,6 @@ export const SearchAsset = () => { ); useEffect(() => { - setMaxHeight(ResultsRef?.current?.clientHeight || 600); setHasNoResults(!assetRows.length); }, [assetRows]); @@ -141,70 +140,71 @@ export const SearchAsset = () => { setHasNoResults(false); }} > -
+ - -
- - {({ field }: FieldProps) => ( - - )} - -
- {t("powered by")}{" "} - - stellar.expert - -
-
-
- {isSearching ? ( -
- + + +
+ + {({ field }: FieldProps) => ( + + )} + +
+ {t("powered by")}{" "} + + stellar.expert +
- ) : null} - - {assetRows.length ? ( - 1 ? : null} - assetRows={assetRows} - maxHeight={maxHeight} - > +
+
+ {isSearching ? ( +
+ +
+ ) : null} + + {assetRows.length ? ( + 1 ? : null} + assetRows={assetRows} + > + + + ) : null} + {hasNoResults && dirty && !isSearching ? ( - - ) : null} - {hasNoResults && dirty && !isSearching ? ( - - ) : null} -
- {!dirty && hasNoResults ? ( -
- - - + ) : null}
- ) : null} -
-
+ {!dirty && hasNoResults ? ( +
+ + + +
+ ) : null} + + + )} diff --git a/extension/src/popup/components/manageAssets/SearchAsset/styles.scss b/extension/src/popup/components/manageAssets/SearchAsset/styles.scss index b81bdd11c3..6149766c9b 100644 --- a/extension/src/popup/components/manageAssets/SearchAsset/styles.scss +++ b/extension/src/popup/components/manageAssets/SearchAsset/styles.scss @@ -3,8 +3,6 @@ } .SearchAsset { - padding: var(--popup-vertical-padding) var(--popup--side-padding); - &__search-copy { font-size: 0.8125rem; line-height: 1.5rem; @@ -32,10 +30,8 @@ &__results { flex-grow: 1; - height: 22.4rem; &--active { - height: 26.1rem; } } diff --git a/extension/src/popup/components/manageAssets/SelectAssetRows/index.tsx b/extension/src/popup/components/manageAssets/SelectAssetRows/index.tsx index 62bb771b4c..5e2762cdae 100644 --- a/extension/src/popup/components/manageAssets/SelectAssetRows/index.tsx +++ b/extension/src/popup/components/manageAssets/SelectAssetRows/index.tsx @@ -4,7 +4,6 @@ import { useHistory } from "react-router-dom"; import { Types } from "@stellar/wallet-sdk"; import { AppDispatch } from "popup/App"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import { transactionSubmissionSelector, saveAsset, @@ -25,13 +24,9 @@ import { formatAmount } from "popup/helpers/formatters"; interface SelectAssetRowsProps { assetRows: ManageAssetCurrency[]; - maxHeight: number; } -export const SelectAssetRows = ({ - assetRows, - maxHeight, -}: SelectAssetRowsProps) => { +export const SelectAssetRows = ({ assetRows }: SelectAssetRowsProps) => { const { accountBalances: { balances = {} }, assetSelect, @@ -61,12 +56,7 @@ export const SelectAssetRows = ({ assetSelect.isSource === false; return ( - +
{assetRows.map( ({ @@ -127,6 +117,6 @@ export const SelectAssetRows = ({ }, )}
- +
); }; diff --git a/extension/src/popup/components/manageAssets/SelectAssetRows/styles.scss b/extension/src/popup/components/manageAssets/SelectAssetRows/styles.scss index a2d50f21f9..c3e4d157cb 100644 --- a/extension/src/popup/components/manageAssets/SelectAssetRows/styles.scss +++ b/extension/src/popup/components/manageAssets/SelectAssetRows/styles.scss @@ -1,9 +1,4 @@ .SelectAssetRows { - &__scrollbar { - padding-right: var(--popup--side-padding); - width: calc(var(--popup--width) - var(--popup--side-padding)); - } - &__content { display: flex; flex-direction: column; diff --git a/extension/src/popup/components/manageAssets/TrustlineError/index.tsx b/extension/src/popup/components/manageAssets/TrustlineError/index.tsx index 0090ced3c2..b18e81a72c 100644 --- a/extension/src/popup/components/manageAssets/TrustlineError/index.tsx +++ b/extension/src/popup/components/manageAssets/TrustlineError/index.tsx @@ -14,6 +14,7 @@ import { emitMetric } from "helpers/metrics"; import { getResultCodes, RESULT_CODES } from "popup/helpers/parseTransaction"; import { METRIC_NAMES } from "popup/constants/metricsNames"; +import { View } from "popup/basics/layout/View"; import { Balances } from "@shared/api/types"; @@ -143,15 +144,19 @@ export const TrustlineError = ({ : TRUSTLINE_ERROR_STATES.UNKNOWN_ERROR; return ( -
-
- -
-
+ + +
+
+ +
+
+
+ -
-
+ + ); }; diff --git a/extension/src/popup/components/manageAssets/TrustlineError/styles.scss b/extension/src/popup/components/manageAssets/TrustlineError/styles.scss index 6bab627d67..3b9272f7d6 100644 --- a/extension/src/popup/components/manageAssets/TrustlineError/styles.scss +++ b/extension/src/popup/components/manageAssets/TrustlineError/styles.scss @@ -1,18 +1,11 @@ .TrustlineError { display: flex; flex-direction: column; - height: var(--popup--height); - padding: var(--popup-vertical-padding) var(--popup--side-padding); &__body { flex-grow: 1; } - &__button { - margin-top: 1rem; - flex-shrink: 0; - } - &__title { font-size: var(--font-size-secondary); font-weight: var(--font-weight-medium); diff --git a/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx b/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx index aab767a2e2..4f353c537e 100644 --- a/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx +++ b/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx @@ -6,9 +6,11 @@ import { Field, FieldProps, Form, Formik } from "formik"; import { object as YupObject, string as YupString } from "yup"; import { useHistory, useLocation } from "react-router-dom"; +import { NETWORKS } from "@shared/constants/stellar"; + import { AppDispatch } from "popup/App"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import { PillButton } from "popup/basics/buttons/PillButton"; +import { View } from "popup/basics/layout/View"; import { ROUTES } from "popup/constants/routes"; import { navigateTo } from "popup/helpers/navigate"; @@ -24,7 +26,6 @@ import { settingsErrorSelector, settingsNetworkDetailsSelector, settingsNetworksListSelector, - settingsSorobanSupportedSelector, } from "popup/ducks/settings"; import { SubviewHeader } from "popup/components/SubviewHeader"; @@ -55,7 +56,6 @@ export const NetworkForm = ({ isEditing }: NetworkFormProps) => { const networkDetails = useSelector(settingsNetworkDetailsSelector); const networksList = useSelector(settingsNetworksListSelector); const settingsError = useSelector(settingsErrorSelector); - const isSorobanSuported = useSelector(settingsSorobanSupportedSelector); const [isNetworkInUse, setIsNetworkInUse] = useState(false); const [isConfirmingRemoval, setIsConfirmingRemoval] = useState(false); @@ -272,197 +272,208 @@ export const NetworkForm = ({ isEditing }: NetworkFormProps) => { ) : null; return ( -
- {isNetworkInUse ? ( - }> -
-
- {t("Network is in use")} -
-
- {t("Please select a different network before removing it.")} -
-
-
- ) : null} - {isConfirmingRemoval ? ( - } - > -
-
- {t("Confirm removing Network")} -
-
- {t( - "Are you sure you want to remove this network? You will have to re-add it if you want to use it again.", - )} -
-
-
- ) : null} - {isNetworkUrlValid ? ( - }> -
-
- {t("CONNECTION ERROR")} -
-
- {t("Unable to connect to")} {invalidUrl} -
-
- {t( - "Please check if the network information is correct and try again. Alternatively, this network may not be operational.", - )}{" "} -
-
-
- ) : null} + - - - {({ dirty, errors, isSubmitting, isValid, touched }) => ( -
- } - label={t("Name")} - name="networkName" - placeholder={t("Enter network name")} - /> - } - label={t("HORIZON RPC URL")} - name="networkUrl" - placeholder={t("Enter network URL")} - /> - {isSorobanSuported || !isEditingDefaultNetworks ? ( + + {({ dirty, errors, isSubmitting, isValid, touched }) => ( + + +
+ {isNetworkInUse ? ( + }> +
+
+ {t("Network is in use")} +
+
+ {t( + "Please select a different network before removing it.", + )} +
+
+
+ ) : null} + {isConfirmingRemoval ? ( + } + > +
+
+ {t("Confirm removing Network")} +
+
+ {t( + "Are you sure you want to remove this network? You will have to re-add it if you want to use it again.", + )} +
+
+
+ ) : null} + {isNetworkUrlValid ? ( + }> +
+
+ {t("CONNECTION ERROR")} +
+
+ {t("Unable to connect to")} {invalidUrl} +
+
+ {t( + "Please check if the network information is correct and try again. Alternatively, this network may not be operational.", + )}{" "} +
+
+
+ ) : null} + + } + label={t("Name")} + name="networkName" + placeholder={t("Enter network name")} + /> } - label={t("SOROBAN RPC URL")} - name="sorobanRpcUrl" - placeholder={t("Enter Soroban RPC URL")} + label={t("HORIZON RPC URL")} + name="networkUrl" + placeholder={t("Enter network URL")} /> - ) : null} - } - label={t("Passphrase")} - name="networkPassphrase" - placeholder={t("Enter passphrase")} - /> - } - label={t("Friendbot URL")} - name="friendbotUrl" - placeholder={t("Enter Friendbot URL")} - /> - {!isEditingDefaultNetworks ? ( - - {({ field }: FieldProps) => ( - - )} - - ) : null} - - {isEditing ? ( -
- {!isEditingDefaultNetworks && ( - { - if (isCurrentNetworkActive) { - setIsNetworkInUse(true); - } else { - setIsConfirmingRemoval(true); + {!isEditingDefaultNetworks || + networkDetailsToEdit.network !== NETWORKS.PUBLIC ? ( + } + label={t("SOROBAN RPC URL")} + name="sorobanRpcUrl" + placeholder={t("Enter Soroban RPC URL")} + /> + ) : null} + } + label={t("Passphrase")} + name="networkPassphrase" + placeholder={t("Enter passphrase")} + /> + } + label={t("Friendbot URL")} + name="friendbotUrl" + placeholder={t("Enter Friendbot URL")} + /> + {!isEditingDefaultNetworks ? ( + + {({ field }: FieldProps) => ( + - {t("Remove")} - - )} -
- ) : ( - - {({ field }: FieldProps) => ( - - )} - - )} + label={t("Allow connecting to non-HTTPS networks")} + {...field} + /> + )} + + ) : null} + + {isEditing ? ( +
+ {!isEditingDefaultNetworks && ( + { + if (isCurrentNetworkActive) { + setIsNetworkInUse(true); + } else { + setIsConfirmingRemoval(true); + } + }} + > + {t("Remove")} + + )} +
+ ) : ( + + {({ field }: FieldProps) => ( + + )} + + )} +
+
+ {isEditing ? ( ) : ( @@ -479,10 +490,10 @@ export const NetworkForm = ({ isEditing }: NetworkFormProps) => {
)} - - )} - -
-
+ + + )} + +
); }; diff --git a/extension/src/popup/components/manageNetwork/NetworkForm/styles.scss b/extension/src/popup/components/manageNetwork/NetworkForm/styles.scss index 04acb8c6fc..b9eb5e545b 100644 --- a/extension/src/popup/components/manageNetwork/NetworkForm/styles.scss +++ b/extension/src/popup/components/manageNetwork/NetworkForm/styles.scss @@ -1,16 +1,4 @@ .NetworkForm { - display: flex; - flex-direction: column; - height: var(--popup--height); - padding: var(--popup-vertical-padding) var(--popup--side-padding); - - &__simplebar { - height: calc( - var(--popup--height) - (var(--popup-vertical-padding) * 2) - - var(--subview--header--height) - ); - } - &__modal { &__title { color: var(--color-white); diff --git a/extension/src/popup/components/manageNetwork/NetworkSettings/index.tsx b/extension/src/popup/components/manageNetwork/NetworkSettings/index.tsx index a9dcc8dbb9..e803220866 100644 --- a/extension/src/popup/components/manageNetwork/NetworkSettings/index.tsx +++ b/extension/src/popup/components/manageNetwork/NetworkSettings/index.tsx @@ -3,7 +3,6 @@ import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import { Button } from "@stellar/design-system"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import { ROUTES } from "popup/constants/routes"; import { ListNavLink, ListNavLinkWrapper } from "popup/basics/ListNavLink"; @@ -16,6 +15,7 @@ import { } from "popup/ducks/settings"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { NetworkIcon } from "popup/components/manageNetwork/NetworkIcon"; +import { View } from "popup/basics/layout/View"; import { NETWORK_INDEX_SEARCH_PARAM } from "../NetworkForm"; @@ -27,51 +27,52 @@ export const NetworkSettings = () => { const { t } = useTranslation(); return ( -
+ -
{t("Network")}
- - - {networksList.map((network, i) => { - const isActive = isActiveNetwork(activeNetworkDetails, network); + +
{t("Network")}
+
+ + {networksList.map((network, i) => { + const isActive = isActiveNetwork(activeNetworkDetails, network); - return ( - -
-
- {isActive ? ( -
- ) : null} - -
{network.networkName}
+ return ( + +
+
+ {isActive ? ( +
+ ) : null} + +
{network.networkName}
+
+
+ {network.networkUrl} +
-
- {network.networkUrl} -
-
- - ); - })} - - -
+ + ); + })} + +
+ + -
-
+ +
); }; diff --git a/extension/src/popup/components/manageNetwork/NetworkSettings/styles.scss b/extension/src/popup/components/manageNetwork/NetworkSettings/styles.scss index c051b773b4..4f56c9bf40 100644 --- a/extension/src/popup/components/manageNetwork/NetworkSettings/styles.scss +++ b/extension/src/popup/components/manageNetwork/NetworkSettings/styles.scss @@ -3,8 +3,6 @@ $activeColor: rgba(61, 204, 152, 1); .NetworkSettings { display: flex; flex-direction: column; - height: var(--popup--height); - padding: 2rem var(--popup--side-padding) var(--popup-vertical-padding); &__header { color: rgba(255, 255, 255, 0.4); @@ -13,12 +11,6 @@ $activeColor: rgba(61, 204, 152, 1); margin-bottom: 1rem; } - &__scrollbar { - height: 23.85rem; - margin-left: -0.25rem; - padding-left: 0.25rem; - } - &__name { align-items: center; display: flex; diff --git a/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/index.tsx b/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/index.tsx index 7ec5b5f719..45c461845b 100644 --- a/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/index.tsx +++ b/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/index.tsx @@ -4,15 +4,20 @@ import { useSelector, useDispatch } from "react-redux"; import { Button, Card } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; +import { AppDispatch } from "popup/App"; import { confirmMnemonicPhrase, + confirmMigratedMnemonicPhrase, authErrorSelector, } from "popup/ducks/accountServices"; -import { FormError, SubmitButtonWrapper } from "popup/basics/Forms"; +import { FormError } from "popup/basics/Forms"; + +import { navigateTo } from "popup/helpers/navigate"; +import { ROUTES } from "popup/constants/routes"; import { - OnboardingScreen, OnboardingHeader, + OnboardingButtons, } from "popup/components/Onboarding"; import { generateMnemonicPhraseDisplay } from "popup/components/mnemonicPhrase/MnemonicDisplay"; @@ -24,11 +29,17 @@ const convertToWord = (wordKey: string) => wordKey.replace(/-.*/, ""); export const ConfirmMnemonicPhrase = ({ words = [""], + isMigration, + customBackAction, + hasGoBackBtn, }: { words: string[]; + isMigration?: boolean; + customBackAction?: () => void; + hasGoBackBtn?: boolean; }) => { const { t } = useTranslation(); - const dispatch = useDispatch(); + const dispatch: AppDispatch = useDispatch(); const initialWordState = words.reduce( (obj, current, i) => ({ @@ -54,11 +65,21 @@ export const ConfirmMnemonicPhrase = ({ const wordStateArr: [string, boolean][] = Object.entries(initialWordState); - const handleSubmit = ( + const handleSubmit = async ( _values: FormikValues, formikHelpers: FormikHelpers, - ): void => { - dispatch(confirmMnemonicPhrase(joinSelectedWords())); + ): Promise => { + if (isMigration) { + const res = await dispatch( + confirmMigratedMnemonicPhrase(joinSelectedWords()), + ); + if (confirmMigratedMnemonicPhrase.fulfilled.match(res)) { + navigateTo(ROUTES.accountMigrationConfirmMigration); + } + } else { + dispatch(confirmMnemonicPhrase(joinSelectedWords())); + } + setSelectedWords([]); formikHelpers.resetForm(); }; @@ -67,64 +88,67 @@ export const ConfirmMnemonicPhrase = ({ selectedWords.map((word) => convertToWord(word)).join(" "); return ( - <> - - - {t("Confirm your recovery phrase")} - -
-

{t("Please select each word in the same order you have")}

-

{t("them noted to confirm you got them right")}

-
- - {({ dirty, isSubmitting, handleChange }) => ( -
-
- -
    e.preventDefault()} - > - {generateMnemonicPhraseDisplay({ - mnemonicPhrase: joinSelectedWords(), - })} -
-
-
- {authError} -
- {wordStateArr.map(([wordKey]) => ( - { - handleChange(e); - updatePhrase(e.target); - }} - wordKey={wordKey} - word={convertToWord(wordKey)} - /> - ))} -
- - - -
- )} -
-
- + {generateMnemonicPhraseDisplay({ + mnemonicPhrase: joinSelectedWords(), + })} + + +
+ + {authError ? {authError} : null} + +
+ {wordStateArr.map(([wordKey]) => ( + { + handleChange(e); + updatePhrase(e.target); + }} + wordKey={wordKey} + word={convertToWord(wordKey)} + /> + ))} +
+ + + + + + )} + +
); }; diff --git a/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/styles.scss b/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/styles.scss index 4f0cf6a0ca..fb8a18f4e8 100644 --- a/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/styles.scss +++ b/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/styles.scss @@ -1,7 +1,8 @@ .ConfirmMnemonicPhrase { &__screen { - flex-flow: column wrap; - justify-content: start; + display: flex; + flex-direction: column; + gap: 1rem; } &__header { @@ -9,24 +10,17 @@ } &__content { - margin: 1.5rem 0; font-size: 1rem; line-height: 1.5rem; font-weight: var(--font-weight-regular); color: var(--color-gray-60); - text-align: center; p { - color: rgba(255, 255, 255, 0.6); + color: var(--color-gray-60); margin-bottom: 0 !important; } } - &--form-wrapper { - width: 24rem; - margin: 0 auto; - } - &__selected-words-wrapper { height: 7.5rem; width: 100%; @@ -48,6 +42,6 @@ flex-flow: row wrap; justify-content: center; align-content: start; - padding: 1rem 0; + padding: 1rem 0 1.5rem; } } diff --git a/extension/src/popup/components/mnemonicPhrase/DisplayMnemonicPhrase/index.tsx b/extension/src/popup/components/mnemonicPhrase/DisplayMnemonicPhrase/index.tsx index 8a4998f716..a6cdfdd1c5 100644 --- a/extension/src/popup/components/mnemonicPhrase/DisplayMnemonicPhrase/index.tsx +++ b/extension/src/popup/components/mnemonicPhrase/DisplayMnemonicPhrase/index.tsx @@ -3,9 +3,9 @@ import { useTranslation } from "react-i18next"; import { Button, Icon, Notification } from "@stellar/design-system"; import { - OnboardingScreen, - OnboardingHalfScreen, OnboardingHeader, + OnboardingTwoCol, + OnboardingOneCol, } from "popup/components/Onboarding"; import { MnemonicDisplay } from "../MnemonicDisplay"; @@ -15,59 +15,67 @@ import "./styles.scss"; export const DisplayMnemonicPhrase = ({ mnemonicPhrase, setIsConfirmed, + isMigration, }: { mnemonicPhrase: string; setIsConfirmed: (confirmed: boolean) => void; + isMigration?: boolean; }) => { const { t } = useTranslation(); return ( -
- - - - {t("Secret Recovery phrase")} - -
+ + + + {isMigration + ? t("Now, let’s create a new mnemonic phrase") + : t("Secret Recovery phrase")} + +
+ {isMigration ? (

- {t( - "Your recovery phrase gives you access to your account and is the", - )}{" "} - {t("only way to access it in a new browser")}.{" "} - {t("Keep it in a safe place.")} + {t("This new backup phrase will be used for your new accounts.")}

-

- {t( - "For your security, we'll check if you got it right in the next step.", - )} -

-
- - - } - > - {t("Never disclose your recovery phrase")}! - - - - - -
+ ) : ( + <> +

+ {t( + "Your recovery phrase gives you access to your account and is the", + )}{" "} + {t("only way to access it in a new browser")}.{" "} + {t("Keep it in a safe place.")} +

+

+ {t( + "For your security, we'll check if you got it right in the next step.", + )} +

+ + )} +
+ + + + } + > + {t("Never disclose your recovery phrase")}! + + + + + ); }; diff --git a/extension/src/popup/components/mnemonicPhrase/DisplayMnemonicPhrase/styles.scss b/extension/src/popup/components/mnemonicPhrase/DisplayMnemonicPhrase/styles.scss index f993a026a7..9c23b3e6ac 100644 --- a/extension/src/popup/components/mnemonicPhrase/DisplayMnemonicPhrase/styles.scss +++ b/extension/src/popup/components/mnemonicPhrase/DisplayMnemonicPhrase/styles.scss @@ -1,20 +1,15 @@ .DisplayMnemonicPhrase { - &__header { - text-align: left; - } - &__content { - margin-top: 1.5rem; font-size: 1rem; line-height: 1.5rem; font-weight: var(--font-weight-regular); p { - color: rgba(255, 255, 255, 0.6); + color: var(--color-gray-70); } strong { - color: var(--color-gray-90); + color: var(--color-gray-80); } } diff --git a/extension/src/popup/components/mnemonicPhrase/MnemonicDisplay/styles.scss b/extension/src/popup/components/mnemonicPhrase/MnemonicDisplay/styles.scss index 6c83505cc9..4e7f7427b3 100644 --- a/extension/src/popup/components/mnemonicPhrase/MnemonicDisplay/styles.scss +++ b/extension/src/popup/components/mnemonicPhrase/MnemonicDisplay/styles.scss @@ -1,5 +1,4 @@ .MnemonicDisplay { - margin: 2rem 0 1rem; position: relative; text-align: center; diff --git a/extension/src/popup/components/sendPayment/SendAmount/SendType/index.tsx b/extension/src/popup/components/sendPayment/SendAmount/SendType/index.tsx index c05fdc2848..0f39c14a33 100644 --- a/extension/src/popup/components/sendPayment/SendAmount/SendType/index.tsx +++ b/extension/src/popup/components/sendPayment/SendAmount/SendType/index.tsx @@ -8,7 +8,7 @@ import { navigateTo } from "popup/helpers/navigate"; import { emitMetric } from "helpers/metrics"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { ROUTES } from "popup/constants/routes"; -import { PopupWrapper } from "popup/basics/PopupWrapper"; +import { View } from "popup/basics/layout/View"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { saveDestinationAsset, @@ -92,7 +92,7 @@ export const SendType = () => { }; return ( - + navigateTo(ROUTES.sendPaymentAmount)} @@ -108,44 +108,48 @@ export const SendType = () => { onSubmit={() => {}} > {({ values }) => ( -
- - {t( - "The destination account receives the same asset and amount sent", - )} - - } - selected={values.paymentType === PAYMENT_TYPES.REGULAR} - /> - - {t( - "The destination account can receive a different asset, the received amount is defined by the available conversion rates", - )}{" "} - - {t("Learn more")} - - - } - selected={values.paymentType === PAYMENT_TYPES.PATH_PAYMENT} - /> -
+ <> + + + + {t( + "The destination account receives the same asset and amount sent", + )} + + } + selected={values.paymentType === PAYMENT_TYPES.REGULAR} + /> + + {t( + "The destination account can receive a different asset, the received amount is defined by the available conversion rates", + )}{" "} + + {t("Learn more")} + + + } + selected={values.paymentType === PAYMENT_TYPES.PATH_PAYMENT} + /> + + + -
- + + )} -
+ ); }; diff --git a/extension/src/popup/components/sendPayment/SendAmount/index.tsx b/extension/src/popup/components/sendPayment/SendAmount/index.tsx index c1e39a4e38..dde910ffb3 100644 --- a/extension/src/popup/components/sendPayment/SendAmount/index.tsx +++ b/extension/src/popup/components/sendPayment/SendAmount/index.tsx @@ -7,13 +7,13 @@ import { Button, Icon, Loader, Notification } from "@stellar/design-system"; import { Asset } from "stellar-sdk"; import { useTranslation } from "react-i18next"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import { AssetSelect, PathPayAssetSelect, } from "popup/components/sendPayment/SendAmount/AssetSelect"; import { PillButton } from "popup/basics/buttons/PillButton"; import { LoadingBackground } from "popup/basics/LoadingBackground"; +import { View } from "popup/basics/layout/View"; import { ROUTES } from "popup/constants/routes"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { AppDispatch } from "popup/App"; @@ -48,11 +48,10 @@ import { import { BottomNav } from "popup/components/BottomNav"; import { ScamAssetWarning } from "popup/components/WarningMessages"; import { TX_SEND_MAX } from "popup/constants/transaction"; +import { BASE_RESERVE } from "@shared/constants/stellar"; import "../styles.scss"; -const BASE_RESERVE = 0.5 as const; - enum AMOUNT_ERROR { TOO_HIGH = "amount too high", DEC_MAX = "too many decimal digits", @@ -161,7 +160,6 @@ export const SendAmount = ({ const currentBal = new BigNumber( accountBalances.balances[selectedAsset].total.toFixed(), ); - availBalance = currentBal .minus(minBalance) .minus(new BigNumber(Number(recommendedFee))); @@ -391,12 +389,15 @@ export const SendAmount = ({ onContinue={() => navigateTo(next)} /> )} -
+ + {formatAmount(availBalance)}{" "} + {parsedSourceAsset.code} {t("available")} + + } hasBackButton={!isSwap} customBackAction={() => navigateTo(previous)} rightButton={ @@ -410,118 +411,8 @@ export const SendAmount = ({ ) } /> -
-
- {formatAmount(availBalance)}{" "} - {parsedSourceAsset.code} {t("available")} -
-
- { - emitMetric(METRIC_NAMES.sendPaymentSetMax); - formik.setFieldValue( - "amount", - calculateAvailBalance(formik.values.asset), - ); - }} - > - {t("SET MAX")} - -
- -
{ - e.preventDefault(); - formik.submitForm(); - }} - > - -
- { - const input = e.target; - const { - amount: newAmount, - newCursor, - } = formatAmountPreserveCursor( - e.target.value, - formik.values.amount, - getAssetDecimals(asset, tokenBalances, isToken), - e.target.selectionStart || 1, - ); - formik.setFieldValue("amount", newAmount); - runAfterUpdate(() => { - input.selectionStart = newCursor; - input.selectionEnd = newCursor; - }); - }} - autoFocus - autoComplete="off" - /> -
- {parsedSourceAsset.code} -
- {showSourceAndDestAsset && formik.values.amount !== "0" && ( - - )} -
- -
-
- {!showSourceAndDestAsset && ( - - )} - {showSourceAndDestAsset && ( - <> - - - - )} -
-
-
+
- -
-
- {isSwap && } + } + > +
+
+
+ { + emitMetric(METRIC_NAMES.sendPaymentSetMax); + formik.setFieldValue( + "amount", + calculateAvailBalance(formik.values.asset), + ); + }} + > + {t("SET MAX")} + +
+ +
+
+ { + const input = e.target; + const { + amount: newAmount, + newCursor, + } = formatAmountPreserveCursor( + e.target.value, + formik.values.amount, + getAssetDecimals(asset, tokenBalances, isToken), + e.target.selectionStart || 1, + ); + formik.setFieldValue("amount", newAmount); + runAfterUpdate(() => { + input.selectionStart = newCursor; + input.selectionEnd = newCursor; + }); + }} + autoFocus + autoComplete="off" + /> +
+ {parsedSourceAsset.code} +
+ {showSourceAndDestAsset && formik.values.amount !== "0" && ( + + )} +
+ +
+
+ {!showSourceAndDestAsset && ( + + )} + {showSourceAndDestAsset && ( + <> + + + + )} +
+
+
+
+
+ + {isSwap && } + {}} isActive={showBlockedDomainWarning} diff --git a/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx b/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx index 8fda5981c9..5cdf0cf0e2 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx +++ b/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx @@ -27,6 +27,7 @@ import { transactionSubmissionSelector, } from "popup/ducks/transactionSubmission"; import { FedOrGAddress } from "popup/basics/sendPayment/FedOrGAddress"; +import { View } from "popup/basics/layout/View"; import { AssetIcon } from "popup/components/account/AccountAssets"; import IconFail from "popup/assets/icon-fail.svg"; @@ -160,50 +161,57 @@ export const SubmitSuccess = ({ viewDetails }: { viewDetails: () => void }) => { accountBalances.balances[asset].available.isZero(); return ( -
-
- {t("Successfully")} {isSwap ? t("swapped") : t("sent")} -
-
- {formatAmount(amount)} {sourceAsset.code} -
-
- -
-
- {isSwap ? ( - - ) : ( - - )} -
-
- {suggestRemoveTrustline && ( - - -

- Your {sourceAsset.code} balance is now empty. Would you like to - remove the {sourceAsset.code} trustline? -

- -
-
- )} -
-
+ + + + {suggestRemoveTrustline && ( + + +

+ Your {sourceAsset.code} balance is now empty. Would you like + to remove the {sourceAsset.code} trustline? +

+ +
+
+ )} +
+ } + > +
+
+ {formatAmount(amount)} {sourceAsset.code} +
+
+ +
+
+ {isSwap ? ( + + ) : ( + + )} +
+
+ + @@ -216,8 +224,8 @@ export const SubmitSuccess = ({ viewDetails }: { viewDetails: () => void }) => { > {t("Done")} -
-
+ + ); }; @@ -396,20 +404,24 @@ export const SubmitFail = () => { const errorDetails = getErrorDetails(error); return ( -
-
-
{t("Error")}
-
{errorDetails.title}
-
- Icon Fail + + + +
+
{errorDetails.title}
+
+ Icon Fail +
+
+ {errorDetails.status ? `${errorDetails.status}:` : ""}{" "} + {errorDetails.opError} +
-
- {errorDetails.status ? `${errorDetails.status}:` : ""}{" "} - {errorDetails.opError} +
+ {errorDetails.errorBlock}
-
-
{errorDetails.errorBlock}
-
+ + -
-
+ + ); }; diff --git a/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/styles.scss b/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/styles.scss index e6c0b8ede7..f5b4ff06e3 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/styles.scss +++ b/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/styles.scss @@ -1,10 +1,4 @@ .SubmitResult { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 70%; - &__content { flex-grow: 1; display: flex; @@ -89,10 +83,6 @@ } &__suggest-remove-tl { - position: absolute; - top: calc(var(--popup--height) - 5.75 * var(--continue-btn-height)); - width: calc(var(--popup--width) - 2 * var(--popup--side-padding)); - .remove-tl-contents { display: flex; flex-direction: column; @@ -121,21 +111,10 @@ &__success { display: flex; column-gap: 1rem; - position: absolute; - top: calc(var(--popup--height) - 1.5 * var(--continue-btn-height)); - width: calc(var(--popup--width) - 2 * var(--popup--side-padding)); button { flex: 1; } } - &__fail { - position: absolute; - top: calc( - var(--popup--height) - 1 * var(--continue-btn-height) - - var(--bottom-btn-margin) - ); - width: calc(var(--popup--width) - 2 * var(--popup--side-padding)); - } } &__error-code { diff --git a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx index 482d50d95e..22191115d0 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx +++ b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx @@ -64,6 +64,7 @@ import { LedgerSign } from "popup/components/hardwareConnect/LedgerSign"; import { useIsOwnedScamAsset } from "popup/helpers/useIsOwnedScamAsset"; import { ScamAssetIcon } from "popup/components/account/ScamAssetIcon"; import { FlaggedWarningMessage } from "popup/components/WarningMessages"; +import { View } from "popup/basics/layout/View"; import { TRANSACTION_WARNING } from "constants/transaction"; import { formatAmount } from "popup/helpers/formatters"; @@ -414,10 +415,18 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { ) : null; + const renderPageTitle = (isSuccess: boolean) => { + if (isSuccess) { + return isSwap ? t("Swapped") : `${t("Sent")} ${sourceAsset.code}`; + } + + return isSwap ? t("Confirm Swap") : `${t("Confirm Send")}`; + }; + return ( <> {hwStatus === ShowOverlayStatus.IN_PROGRESS && } -
+ {submission.submitStatus === ActionStatus.PENDING && (
@@ -432,11 +441,9 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => {
)} void }) => { ) : null } /> - {!(isPathPayment || isSwap) && ( -
- - + {(isPathPayment || isSwap) && + submission.submitStatus !== ActionStatus.SUCCESS && + t("The final amount is approximate and may change")} +
+ } + > + {!(isPathPayment || isSwap) && ( +
+ + - -
- )} + ]} + /> + +
+ )} - {(isPathPayment || isSwap) && ( - - )} + {(isPathPayment || isSwap) && ( + + )} - {!isSwap && ( -
-
{t("Sending to")}
-
-
- + {!isSwap && ( +
+
{t("Sending to")}
+
+
+ +
-
- )} - {showMemo && ( -
-
{t("Memo")}
-
- {memo || t("None")} + )} + {showMemo && ( +
+
{t("Memo")}
+
+ {memo || t("None")} +
-
- )} + )} - {(isPathPayment || isSwap) && ( + {(isPathPayment || isSwap) && ( +
+
{t("Conversion rate")}
+
+ 1 {sourceAsset.code} /{" "} + {getConversionRate(amount, destinationAmount).toFixed(2)}{" "} + {destAsset.code} +
+
+ )}
-
{t("Conversion rate")}
+
{t("Transaction fee")}
- 1 {sourceAsset.code} /{" "} - {getConversionRate(amount, destinationAmount).toFixed(2)}{" "} - {destAsset.code} + {transactionFee} XLM
- )} -
-
{t("Transaction fee")}
-
- {transactionFee} XLM -
-
- {transactionSimulation.response && ( - <> -
-
{t("Resource cost")}
-
-
- {transactionSimulation.response.cost.cpuInsns} CPU + {transactionSimulation.response && ( + <> +
+
{t("Resource cost")}
+
+
+ {transactionSimulation.response.cost.cpuInsns} CPU +
+
+ {transactionSimulation.response.cost.memBytes} Bytes +
-
- {transactionSimulation.response.cost.memBytes} Bytes +
+
+
{t("Minimum resource fee")}
+
+ {transactionSimulation.response.minResourceFee} XLM
-
+ + )} + {isSwap && (
-
{t("Minimum resource fee")}
+
{t("Minimum Received")}
- {transactionSimulation.response.minResourceFee} XLM + {computeDestMinWithSlippage( + allowedSlippage, + destinationAmount, + ).toFixed()}{" "} + {destAsset.code}
- - )} - {isSwap && ( -
-
{t("Minimum Received")}
-
- {computeDestMinWithSlippage( - allowedSlippage, - destinationAmount, - ).toFixed()}{" "} - {destAsset.code} -
-
- )} - {submission.submitStatus === ActionStatus.IDLE && ( - - )} -
-
- {(isPathPayment || isSwap) && - submission.submitStatus !== ActionStatus.SUCCESS && - t("The final amount is approximate and may change")} -
+ )} + {submission.submitStatus === ActionStatus.IDLE && ( + + )} + + {submission.submitStatus === ActionStatus.SUCCESS ? ( ) : ( -
+ <> -
+ )} -
-
+ + ); }; diff --git a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/styles.scss b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/styles.scss index 5c17bfcfe2..02abd65005 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/styles.scss +++ b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/styles.scss @@ -34,7 +34,6 @@ &__right { color: var(--color-gray-90); overflow-y: hidden; - max-width: 10rem; &__item { margin-bottom: 0.25rem; @@ -44,8 +43,8 @@ &__processing { position: absolute; - height: var(--popup--height); - width: var(--popup--width); + height: 100%; + width: 100%; display: flex; flex-direction: column; background: black; @@ -112,7 +111,6 @@ font-size: 0.8125rem; line-height: 1.375rem; min-height: 1.375rem; - margin-bottom: 1rem; align-items: center; text-align: center; justify-content: center; diff --git a/extension/src/popup/components/sendPayment/SendConfirm/index.tsx b/extension/src/popup/components/sendPayment/SendConfirm/index.tsx index 1db4044f51..02ec3a732a 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/index.tsx +++ b/extension/src/popup/components/sendPayment/SendConfirm/index.tsx @@ -7,7 +7,6 @@ import { transactionSubmissionSelector, resetSubmission, } from "popup/ducks/transactionSubmission"; -import { PopupWrapper } from "popup/basics/PopupWrapper"; import { ROUTES } from "popup/constants/routes"; import { navigateTo } from "popup/helpers/navigate"; @@ -46,5 +45,5 @@ export const SendConfirm = ({ previous }: { previous: ROUTES }) => { } }; - return {render()}; + return render(); }; diff --git a/extension/src/popup/components/sendPayment/SendSettings/Slippage/index.tsx b/extension/src/popup/components/sendPayment/SendSettings/Slippage/index.tsx index 4a641138c4..15c4f6794c 100644 --- a/extension/src/popup/components/sendPayment/SendSettings/Slippage/index.tsx +++ b/extension/src/popup/components/sendPayment/SendSettings/Slippage/index.tsx @@ -11,8 +11,8 @@ import { } from "popup/ducks/transactionSubmission"; import { navigateTo } from "popup/helpers/navigate"; import { ROUTES } from "popup/constants/routes"; -import { PopupWrapper } from "popup/basics/PopupWrapper"; import { InfoTooltip } from "popup/basics/InfoTooltip"; +import { View } from "popup/basics/layout/View"; import { SubviewHeader } from "popup/components/SubviewHeader"; import "./styles.scss"; @@ -33,7 +33,7 @@ export const SendSettingsSlippage = ({ previous }: { previous: ROUTES }) => { } return ( - + navigateTo(previous)} @@ -52,23 +52,21 @@ export const SendSettingsSlippage = ({ previous }: { previous: ROUTES }) => { } /> -
- { - dispatch( - saveAllowedSlippage( - values.customSlippage || values.presetSlippage, - ), - ); - navigateTo(previous); - }} - validationSchema={YupObject().shape({ - customSlippage: YupNumber().max(10, `${t("must be below")} 10%`), - })} - > - {({ setFieldValue, values, errors }) => ( -
+ { + dispatch( + saveAllowedSlippage(values.customSlippage || values.presetSlippage), + ); + navigateTo(previous); + }} + validationSchema={YupObject().shape({ + customSlippage: YupNumber().max(10, `${t("must be below")} 10%`), + })} + > + {({ setFieldValue, values, errors }) => ( + +
- +
+ + + + + )} +
+ ); }; diff --git a/extension/src/popup/components/sendPayment/SendSettings/TransactionFee/index.tsx b/extension/src/popup/components/sendPayment/SendSettings/TransactionFee/index.tsx index 377381ebeb..b264f73a90 100644 --- a/extension/src/popup/components/sendPayment/SendSettings/TransactionFee/index.tsx +++ b/extension/src/popup/components/sendPayment/SendSettings/TransactionFee/index.tsx @@ -9,9 +9,9 @@ import { useTranslation } from "react-i18next"; import { navigateTo } from "popup/helpers/navigate"; import { useNetworkFees } from "popup/helpers/useNetworkFees"; import { ROUTES } from "popup/constants/routes"; -import { PopupWrapper } from "popup/basics/PopupWrapper"; import { FormRows } from "popup/basics/Forms"; import { InfoTooltip } from "popup/basics/InfoTooltip"; +import { View } from "popup/basics/layout/View"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { saveTransactionFee, @@ -27,7 +27,7 @@ export const SendSettingsFee = ({ previous }: { previous: ROUTES }) => { const { networkCongestion, recommendedFee } = useNetworkFees(); return ( - + navigateTo(previous)} @@ -53,22 +53,23 @@ export const SendSettingsFee = ({ previous }: { previous: ROUTES }) => { } /> -
- { - dispatch(saveTransactionFee(String(values.transactionFee))); - navigateTo(previous); - }} - validationSchema={YupObject().shape({ - transactionFee: YupNumber().min( - 0.00001, - `${t("must be greater than")} 0.00001`, - ), - })} - > - {({ setFieldValue, values, isValid, errors }) => ( -
+ + { + dispatch(saveTransactionFee(String(values.transactionFee))); + navigateTo(previous); + }} + validationSchema={YupObject().shape({ + transactionFee: YupNumber().min( + 0.00001, + `${t("must be greater than")} 0.00001`, + ), + })} + > + {({ setFieldValue, values, isValid, errors }) => ( + + {({ field }: FieldProps) => ( @@ -102,21 +103,21 @@ export const SendSettingsFee = ({ previous }: { previous: ROUTES }) => { )} -
- -
- - )} -
-
-
+ + + + + + )} +
+ ); }; diff --git a/extension/src/popup/components/sendPayment/SendSettings/index.tsx b/extension/src/popup/components/sendPayment/SendSettings/index.tsx index 119aa63ed7..d99cd8480e 100644 --- a/extension/src/popup/components/sendPayment/SendSettings/index.tsx +++ b/extension/src/popup/components/sendPayment/SendSettings/index.tsx @@ -10,9 +10,9 @@ import { useNetworkFees } from "popup/helpers/useNetworkFees"; import { useIsSwap } from "popup/helpers/useIsSwap"; import { isMuxedAccount, xlmToStroop } from "helpers/stellar"; import { ROUTES } from "popup/constants/routes"; -import { PopupWrapper } from "popup/basics/PopupWrapper"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { FormRows } from "popup/basics/Forms"; +import { View } from "popup/basics/layout/View"; import { saveMemo, transactionDataSelector, @@ -125,20 +125,20 @@ export const SendSettings = ({ } return ( - -
- navigateTo(previous)} - /> - { - dispatch(saveMemo(values.memo)); - }} - > - {({ submitForm }) => ( -
+ + navigateTo(previous)} + /> + { + dispatch(saveMemo(values.memo)); + }} + > + {({ submitForm }) => ( + + {!isToken ? (
@@ -272,24 +272,23 @@ export const SendSettings = ({ )} - -
- -
- - )} - -
- +
+ + + + + )} +
+
); }; diff --git a/extension/src/popup/components/sendPayment/SendTo/index.tsx b/extension/src/popup/components/sendPayment/SendTo/index.tsx index 2e063e7ea1..8af9dd1563 100644 --- a/extension/src/popup/components/sendPayment/SendTo/index.tsx +++ b/extension/src/popup/components/sendPayment/SendTo/index.tsx @@ -28,8 +28,7 @@ import { emitMetric } from "helpers/metrics"; import { navigateTo } from "popup/helpers/navigate"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { ROUTES } from "popup/constants/routes"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; -import { PopupWrapper } from "popup/basics/PopupWrapper"; +import { View } from "popup/basics/layout/View"; import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; import { saveDestination, @@ -217,113 +216,116 @@ export const SendTo = ({ previous }: { previous: ROUTES }) => { }, [dispatch, validatedPubKey, networkDetails]); return ( - + navigateTo(previous)} /> - - - -
- {isLoading ? ( -
- -
- ) : ( -
- {formik.values.destination === "" ? ( - <> - {recentAddresses.length > 0 && ( -
{t("RECENT")}
- )} - -
    - {recentAddresses.map((address) => ( -
  • - -
  • - ))} -
-
- - ) : ( -
- {formik.isValid ? ( - <> - {!destinationBalances.isFunded && ( - - )} - {isFederationAddress(formik.values.destination) && ( - <> -
- {t("FEDERATION ADDRESS")} -
-
- {formik.values.destination} -
- - )} -
Address
-
- - {truncatedPublicKey(validatedPubKey)} -
- -
- -
- - ) : ( - - )} -
- )} -
- )} -
-
+ + + + +
+ {isLoading ? ( +
+ +
+ ) : ( +
+ {formik.values.destination === "" ? ( + <> + {recentAddresses.length > 0 && ( +
{t("RECENT")}
+ )} +
+
    + {recentAddresses.map((address) => ( +
  • + +
  • + ))} +
+
+ + ) : ( +
+ {formik.isValid ? ( + <> + {!destinationBalances.isFunded && ( + + )} + {isFederationAddress(formik.values.destination) && ( + <> +
+ {t("FEDERATION ADDRESS")} +
+
+ {formik.values.destination} +
+ + )} +
Address
+
+ + {truncatedPublicKey(validatedPubKey)} +
+ + ) : ( + + )} +
+ )} +
+ )} +
+
+ + {!isLoading && formik.values.destination && formik.isValid ? ( + + ) : null} + + ); }; diff --git a/extension/src/popup/components/sendPayment/styles.scss b/extension/src/popup/components/sendPayment/styles.scss index ec468438ba..27e76c7d19 100644 --- a/extension/src/popup/components/sendPayment/styles.scss +++ b/extension/src/popup/components/sendPayment/styles.scss @@ -4,42 +4,25 @@ --continue-btn-height: 2.625rem; } -.SendPayment { - &__btn-continue { - position: absolute; - top: calc( - var(--popup--height) - var(--continue-btn-height) - - var(--bottom-btn-margin) - ); - width: calc(var(--popup--width) - 2 * var(--popup--side-padding)); - left: var(--popup--side-padding); - } -} - .SendAmount { - padding: var(--popup-vertical-padding) var(--account-view-padding-side); - height: var(--popup--height); display: flex; flex-direction: column; - - &__full-height { - height: calc(var(--popup--height) - var(--bottom-nav--height)); - } + height: 100%; &__content { display: flex; flex-direction: column; align-items: center; - flex-grow: 1; - margin-top: -1.5rem; + height: 100%; + + form { + height: 100%; + width: 100%; + } } + &__simplebar { margin-top: 1rem; - height: 19rem; - - &__full-height { - height: 23rem; - } &__content { display: flex; @@ -54,8 +37,8 @@ cursor: pointer; display: flex; padding: 0; - width: 0.9rem; - height: 1rem; + width: 1.25rem; + height: 1.25rem; display: flex; align-items: center; z-index: var(--back--button-z-index); @@ -68,13 +51,6 @@ margin-bottom: 1rem; } - &__form { - height: 100%; - width: 100%; - display: flex; - flex-direction: column; - } - &__amount-warning { margin: 1rem 0; @@ -216,10 +192,6 @@ &__info-block { font-size: 0.875rem; } - - &__simplebar { - height: 20rem; - } } .SendSettings { diff --git a/extension/src/popup/components/signAuthEntry/AuthEntry/index.tsx b/extension/src/popup/components/signAuthEntry/AuthEntry/index.tsx index a24affcb12..68f286ae9c 100644 --- a/extension/src/popup/components/signAuthEntry/AuthEntry/index.tsx +++ b/extension/src/popup/components/signAuthEntry/AuthEntry/index.tsx @@ -2,7 +2,6 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { xdr, buildInvocationTree } from "stellar-sdk"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import "./styles.scss"; interface TransactionProps { @@ -22,13 +21,13 @@ export const AuthEntry = ({ preimageXdr }: TransactionProps) => {
{t("Authorization Entry")}
-          
+          
{JSON.stringify( rootJson, (_, val) => (typeof val === "bigint" ? val.toString() : val), 2, )} - +
diff --git a/extension/src/popup/components/signTransaction/Operations/index.tsx b/extension/src/popup/components/signTransaction/Operations/index.tsx index a22e617506..52bd2c48fd 100644 --- a/extension/src/popup/components/signTransaction/Operations/index.tsx +++ b/extension/src/popup/components/signTransaction/Operations/index.tsx @@ -25,7 +25,6 @@ import { formatTokenAmount, } from "popup/helpers/soroban"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import { KeyIdenticon } from "popup/components/identicons/KeyIdenticon"; import { hasSorobanClient, SorobanContext } from "popup/SorobanContext"; @@ -159,11 +158,11 @@ const KeyValueWithScValue = ({ {operationKey} {operationKey ? ":" : null}
- +
{JSON.stringify(operationValue, null, 2)}
- +
); @@ -182,7 +181,7 @@ const KeyValueWithScAuth = ({ {operationKey} {operationKey ? ":" : null}
- +
             {JSON.stringify(
@@ -192,7 +191,7 @@ const KeyValueWithScAuth = ({
             )}
           
- +
); }; @@ -338,7 +337,7 @@ export const Operations = ({ /* Needed to translate enum strings: - + t("Authorization Required") t("Authorization Revocable") t("Authorization Required; Authorization Required") diff --git a/extension/src/popup/components/signTransaction/Operations/styles.scss b/extension/src/popup/components/signTransaction/Operations/styles.scss index 00ffa5c28e..03e4902bd6 100644 --- a/extension/src/popup/components/signTransaction/Operations/styles.scss +++ b/extension/src/popup/components/signTransaction/Operations/styles.scss @@ -42,6 +42,7 @@ font-size: 0.875rem; height: 7rem; margin: 0.5rem 0; + overflow: scroll; padding: 1rem; } diff --git a/extension/src/popup/constants/metricsNames.ts b/extension/src/popup/constants/metricsNames.ts index 56c7adfb11..f1113a555f 100644 --- a/extension/src/popup/constants/metricsNames.ts +++ b/extension/src/popup/constants/metricsNames.ts @@ -41,6 +41,16 @@ export const METRIC_NAMES = { sendPaymentSettingsSlippage: "loaded screen: send payment settings slippage", sendPaymentConfirm: "loaded screen: send payment confirm", + viewAccountMigration: "loaded screen: account migration", + viewAccountMigrationReviewMigration: + "loaded screen: account migration review migration", + viewAccountMigrationMnemonicPhrase: + "loaded screen: account migration mnemonic phrase", + viewAccountMigrationConfirmMigration: + "loaded screen: account migration confirm migration", + viewAccountMigrationMigrationComplete: + "loaded screen: account migration migration complete", + sendPaymentRecentAddress: "send payment: recent address", sendPaymentSetMax: "send payment: set max", sendPaymentTypePayment: "send payment: selected type payment", diff --git a/extension/src/popup/constants/routes.ts b/extension/src/popup/constants/routes.ts index d2e9d14606..c99be51f30 100644 --- a/extension/src/popup/constants/routes.ts +++ b/extension/src/popup/constants/routes.ts @@ -53,4 +53,9 @@ export enum ROUTES { addNetwork = "/manage-network/add-network", editNetwork = "/manage-network/edit-network", networkSettings = "/manage-network/network-settings", + accountMigration = "/account-migration", + accountMigrationReviewMigration = "/account-migration/review-migration", + accountMigrationMnemonicPhrase = "/account-migration/mnemonic-phrase", + accountMigrationConfirmMigration = "/account-migration/confirm-migration", + accountMigrationMigrationComplete = "/account-migration/migration-complete", } diff --git a/extension/src/popup/ducks/accountServices.ts b/extension/src/popup/ducks/accountServices.ts index da2b2bcd01..bafe7aa25a 100644 --- a/extension/src/popup/ducks/accountServices.ts +++ b/extension/src/popup/ducks/accountServices.ts @@ -14,6 +14,7 @@ import { makeAccountActive as makeAccountActiveService, updateAccountName as updateAccountNameService, confirmMnemonicPhrase as confirmMnemonicPhraseService, + confirmMigratedMnemonicPhrase as confirmMigratedMnemonicPhraseService, createAccount as createAccountService, fundAccount as fundAccountService, recoverAccount as recoverAccountService, @@ -21,8 +22,15 @@ import { confirmPassword as confirmPasswordService, signOut as signOutService, addTokenId as addTokenIdService, + migrateAccounts as migrateAccountsService, } from "@shared/api/internal"; -import { Account, AccountType, ErrorMessage } from "@shared/api/types"; +import { + Account, + AccountType, + BalanceToMigrate, + ErrorMessage, + MigratedAccount, +} from "@shared/api/types"; import { WalletType } from "@shared/constants/hardwareWallet"; import { AppState } from "popup/App"; @@ -230,6 +238,40 @@ export const confirmMnemonicPhrase = createAsyncThunk< }, ); +export const confirmMigratedMnemonicPhrase = createAsyncThunk< + { isCorrectPhrase: boolean }, + string, + { rejectValue: ErrorMessage } +>( + "auth/confirmMigratedMnemonicPhrase", + + async (phrase: string, thunkApi) => { + let res = { + isCorrectPhrase: false, + }; + try { + res = await confirmMigratedMnemonicPhraseService(phrase); + } catch (e) { + console.error("Failed when confirming Mnemonic Phrase: ", e.message); + return thunkApi.rejectWithValue({ + errorMessage: e.message, + }); + } + + if (res.isCorrectPhrase) { + res = { + isCorrectPhrase: true, + }; + } else { + return thunkApi.rejectWithValue({ + errorMessage: "The secret phrase you entered is incorrect.", + }); + } + + return res; + }, +); + export const confirmPassword = createAsyncThunk< { publicKey: string; @@ -362,8 +404,51 @@ export const addTokenId = createAsyncThunk< return res; }); +export const migrateAccounts = createAsyncThunk< + { + allAccounts: Array; + migratedAccounts: Array; + hasPrivateKey: boolean; + publicKey: string; + error: string; + }, + { + balancesToMigrate: BalanceToMigrate[]; + isMergeSelected: boolean; + recommendedFee: string; + }, + { rejectValue: ErrorMessage } +>( + "auth/migrateAccounts", + async ({ balancesToMigrate, isMergeSelected, recommendedFee }, thunkApi) => { + let res = { + migratedAccounts: [] as Array, + allAccounts: [] as Array, + publicKey: "", + hasPrivateKey: false, + error: "", + }; + + try { + res = await migrateAccountsService({ + balancesToMigrate, + isMergeSelected, + recommendedFee, + }); + } catch (e) { + console.error("Failed when migrating an account: ", e.message); + return thunkApi.rejectWithValue({ + errorMessage: e.message, + }); + } + + return res; + }, +); + interface InitialState { allAccounts: Array; + migratedAccounts: Array; applicationState: APPLICATION_STATE; hasPrivateKey: boolean; publicKey: string; @@ -375,6 +460,7 @@ interface InitialState { const initialState: InitialState = { allAccounts: [], + migratedAccounts: [], applicationState: APPLICATION_STATE.APPLICATION_LOADING, hasPrivateKey: false, publicKey: "", @@ -570,6 +656,16 @@ const authSlice = createSlice({ ...state, applicationState: action.payload.applicationState, })); + builder.addCase(confirmMigratedMnemonicPhrase.rejected, (state, action) => { + const { errorMessage } = action.payload || { + errorMessage: "", + }; + + return { + ...state, + error: errorMessage, + }; + }); builder.addCase(loadAccount.fulfilled, (state, action) => { const { hasPrivateKey, @@ -664,6 +760,36 @@ const authSlice = createSlice({ builder.addCase(addTokenId.rejected, (state, action) => { const { errorMessage } = action.payload || { errorMessage: "" }; + return { + ...state, + error: errorMessage, + }; + }); + builder.addCase(migrateAccounts.fulfilled, (state, action) => { + const { + publicKey, + allAccounts, + migratedAccounts, + hasPrivateKey, + } = action.payload || { + publicKey: "", + allAccounts: [], + migratedAccounts: [], + hasPrivateKey: false, + }; + + return { + ...state, + error: "", + allAccounts, + migratedAccounts, + hasPrivateKey, + publicKey, + }; + }); + builder.addCase(migrateAccounts.rejected, (state, action) => { + const { errorMessage } = action.payload || { errorMessage: "" }; + return { ...state, error: errorMessage, @@ -698,6 +824,10 @@ export const bipPathSelector = createSelector( authSelector, (auth: InitialState) => auth.bipPath, ); +export const migratedAccountsSelector = createSelector( + authSelector, + (auth: InitialState) => auth.migratedAccounts, +); export const accountNameSelector = createSelector( publicKeySelector, diff --git a/extension/src/popup/ducks/transactionSubmission.ts b/extension/src/popup/ducks/transactionSubmission.ts index 9ab05f4ee7..c916f3dcd4 100644 --- a/extension/src/popup/ducks/transactionSubmission.ts +++ b/extension/src/popup/ducks/transactionSubmission.ts @@ -37,6 +37,7 @@ import { AccountType, ActionStatus, BlockedAccount, + BalanceToMigrate, } from "@shared/api/types"; import { NetworkDetails } from "@shared/constants/stellar"; @@ -392,6 +393,8 @@ interface TransactionData { path: Array; allowedSlippage: string; isToken: boolean; + isMergeSelected: boolean; + balancesToMigrate: BalanceToMigrate[]; } interface HardwareWalletData { @@ -451,6 +454,8 @@ export const initialState: InitialState = { path: [], allowedSlippage: "1", isToken: false, + isMergeSelected: false, + balancesToMigrate: [] as BalanceToMigrate[], }, transactionSimulation: { response: null, @@ -545,6 +550,12 @@ const transactionSubmissionSlice = createSlice({ saveAssetSelectSource: (state, action) => { state.assetSelect.isSource = action.payload; }, + saveIsMergeSelected: (state, action) => { + state.transactionData.isMergeSelected = action.payload; + }, + saveBalancesToMigrate: (state, action) => { + state.transactionData.balancesToMigrate = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(submitFreighterTransaction.pending, (state) => { @@ -675,6 +686,8 @@ export const { closeHwOverlay, saveAssetSelectType, saveAssetSelectSource, + saveIsMergeSelected, + saveBalancesToMigrate, } = transactionSubmissionSlice.actions; export const { reducer } = transactionSubmissionSlice; diff --git a/extension/src/popup/helpers/addStyleClasses.ts b/extension/src/popup/helpers/addStyleClasses.ts new file mode 100644 index 0000000000..7a778f239f --- /dev/null +++ b/extension/src/popup/helpers/addStyleClasses.ts @@ -0,0 +1,2 @@ +export const addStyleClasses = (classes: string[]) => + [...classes].filter(Boolean).join(" "); diff --git a/extension/src/popup/locales/en/translation.json b/extension/src/popup/locales/en/translation.json index 955f23a6b7..bb8cb2d121 100644 --- a/extension/src/popup/locales/en/translation.json +++ b/extension/src/popup/locales/en/translation.json @@ -8,6 +8,8 @@ "About": "About", "account": "account", "Account": "Account", + "Account migration": "Account migration", + "Account Migration": "Account Migration", "Account minimum balance is too low": "Account minimum balance is too low", "Account not available": "Account not available", "Action": "Action", @@ -46,6 +48,9 @@ "Approve using": "Approve using", "are required to add a new asset": "are required to add a new asset.", "Are you sure you want to remove this network? You will have to re-add it if you want to use it again": "Are you sure you want to remove this network? You will have to re-add it if you want to use it again.", + "As long as you have your old and new mnemonics phrase, you’ll still be able to control accounts related to your current backup phrase which were not merged": { + " For that, you’ll need to import your current backup phrase into Freighter (Freighter supports one backup phrase imported at a time)": "As long as you have your old and new mnemonics phrase, you’ll still be able to control accounts related to your current backup phrase which were not merged. For that, you’ll need to import your current backup phrase into Freighter (Freighter supports one backup phrase imported at a time)." + }, "Asset Code": "Asset Code", "Asset domain": "Asset domain", "Asset home domain doesn’t exist, TOML file format is invalid, or asset doesn't match currency description": "Asset home domain doesn’t exist, TOML file format is invalid, or asset doesn't match currency description", @@ -53,6 +58,9 @@ "Asset not found": "Asset not found", "Assets found in this domain": "Assets found in this domain", "At least one uppercase letter": "At least one uppercase letter", + "At the end of this process, Freighter will only display accounts related to the new backup phrase": { + " You’ll still be able to import your current backup phrase into Freighter and control current accounts as long as they were not merged into the new accounts": "At the end of this process, Freighter will only display accounts related to the new backup phrase. You’ll still be able to import your current backup phrase into Freighter and control current accounts as long as they were not merged into the new accounts." + }, "Authorization Entry": "Authorization Entry", "Authorization Immutable": "Authorization Immutable", "Authorization Required": "Authorization Required", @@ -66,8 +74,10 @@ " Keep your recovery phrase safe, it’s your responsibility": "Awesome, you passed the test. Keep your recovery phrase safe, it’s your responsibility." }, "Awesome, you passed the test! Pin the extension in your browser to access it easily": "Awesome, you passed the test! Pin the extension in your browser to access it easily.", + "Back": "Back", "Balance Id": "Balance Id", "Base fee": "Base fee", + "Before we start with migration, please read": "Before we start with migration, please read", "Before You Add This Asset": "Before You Add This Asset", "Block malicious or unsafe addresses and domains": "Block malicious or unsafe addresses and domains", "Block trustlines to malicious or fraudulent assets": "Block trustlines to malicious or fraudulent assets", @@ -106,6 +116,7 @@ "Create Account": "Create Account", "Create Wallet": "Create Wallet", "Custom": "Custom", + "data entries": "data entries", "Date": "Date", "Destination": "Destination", "Destination account does not accept this asset": "Destination account does not accept this asset", @@ -147,6 +158,7 @@ "Fees can vary depending on the network congestion": { " Please try using the suggested fee and try again": "Fees can vary depending on the network congestion. Please try using the suggested fee and try again." }, + "Finish": "Finish", "Footprint": "Footprint", "For your safety, signing this transaction is disabled": "For your safety, signing this transaction is disabled", "For your security, we'll check if you got it right in the next step": "For your security, we'll check if you got it right in the next step.", @@ -181,6 +193,7 @@ "I have my 12 word seed phrase": "I have my 12 word seed phrase", "I have my recovery phrase safe": "I have my recovery phrase safe", "I have read and agree to": "I have read and agree to", + "I understand, start migration": "I understand, start migration", "I’m aware Freighter can’t recover the imported secret key": "I’m aware Freighter can’t recover the imported secret key", "I’m going to need a seed phrase": "I’m going to need a seed phrase", "I’m new!": "I’m new!", @@ -195,6 +208,7 @@ "Important": "Important", "Important Warning": "Important Warning", "in order to remove an asset": "in order to remove an asset.", + "In this process, Freighter will create a new backup phrase for you and migrate your lumens, trustlines, and assets to the new account": "In this process, Freighter will create a new backup phrase for you and migrate your lumens, trustlines, and assets to the new account.", "Include a custom memo to this transaction": "Include a custom memo to this transaction", "Inflation Destination": "Inflation Destination", "Inner Transaction": "Inner Transaction", @@ -234,6 +248,8 @@ "Log In": "Log In", "Log Out": "Log Out", "Low Threshold": "Low Threshold", + "Make sure you have your 12 word backup phrase": "Make sure you have your 12 word backup phrase", + "Make sure you have your current 12 words backup phrase before continuing": "Make sure you have your current 12 words backup phrase before continuing.", "Malicious": "Malicious", "Manage Assets": "Manage Assets", "Manage connected apps": "Manage connected apps", @@ -248,6 +264,14 @@ "Memo": "Memo", "Memo (optional)": "Memo (optional)", "Memo required": "Memo required", + "Merge accounts after migrating (your funding lumens used to fund the current accounts will be sent to the new ones - you lose access to the current accounts": { + ")": "Merge accounts after migrating (your funding lumens used to fund the current accounts will be sent to the new ones - you lose access to the current accounts.)" + }, + "Migrated": "Migrated", + "Migrating": { + "": "Migrating..." + }, + "Migration complete": "Migration complete", "Min 8 characters": "Min 8 characters", "Min Amount A": "Min Amount A", "Min Amount B": "Min Amount B", @@ -266,6 +290,7 @@ "Network is in use": "Network is in use", "Network Settings": "Network Settings", "Never disclose your recovery phrase": "Never disclose your recovery phrase", + "Nevermind, cancel": "Nevermind, cancel", "New account": "New account", "New Asset": "New Asset", "New password": "New password", @@ -278,12 +303,22 @@ "None": "None", "Not defined": "Not defined", "Not enough lumens": "Not enough lumens", + "Not funded": "Not funded", + "Not migrated": "Not migrated", "Not recommended asset": "Not recommended asset", + "Now, let’s create a new mnemonic phrase": "Now, let’s create a new mnemonic phrase", "Offer Id": "Offer Id", + "One of your accounts is a signer for another account": { + " Freighter won’t migrate signing settings": { + " For your safety, Freighter won’t merge accounts with signature set up so you can still control it": "One of your accounts is a signer for another account. Freighter won’t migrate signing settings. For your safety, Freighter won’t merge accounts with signature set up so you can still control it." + } + }, "One or more operations in this transaction failed": "One or more operations in this transaction failed.", + "Only accounts ready for migration will be migrated": "Only accounts ready for migration will be migrated.", "only way to access it in a new browser": "only way to access it in a new browser", "Operation": "Operation", "Operations": "Operations", + "Optional": "Optional", "Parameters": "Parameters", "Passphrase": "Passphrase", "Paths": "Paths", @@ -307,16 +342,23 @@ " ": "Projects related to this asset may be fraudulent even if the creators say otherwise. " }, "Read before importing your key": "Read before importing your key", + "Ready to migrate": "Ready to migrate", "Received": "Received", "RECEIVED": "RECEIVED", "RECENT": "RECENT", "Recipient Stellar address": "Recipient Stellar address", "Reject": "Reject", + "Remember, Freighter will now display accounts related to the new backup phrase that was just created": { + " Use this backup phrase from now on to use your new accounts": { + " If you have accounts that were not merged, keep and use your old backup phrase to access them": "Remember, Freighter will now display accounts related to the new backup phrase that was just created. Use this backup phrase from now on to use your new accounts. If you have accounts that were not merged, keep and use your old backup phrase to access them." + } + }, "Remove": "Remove", "Remove trustline": "Remove trustline", "Reserved Balance*": "Reserved Balance*", "Resource cost": "Resource cost", "Review": "Review", + "Review accounts to migrate": "Review accounts to migrate", "Review transaction on device": "Review transaction on device", "Revocable Asset": "Revocable Asset", "Same source and destination asset": "Same source and destination asset", @@ -348,6 +390,7 @@ "Signer": "Signer", "Signing arbitrary data with a hardware wallet is currently not supported": "Signing arbitrary data with a hardware wallet is currently not supported.", "Signing this transaction is not possible at the moment": "Signing this transaction is not possible at the moment.", + "Signs for external accounts": "Signs for external accounts", "SOROBAN RPC URL": "SOROBAN RPC URL", "Source": "Source", "Sponsored Id": "Sponsored Id", @@ -387,6 +430,7 @@ "expert, a reliable community-maintained directory": "This asset was tagged as fraudulent by stellar.expert, a reliable community-maintained directory." }, "This is a relatively new asset": "This is a relatively new asset.", + "This new backup phrase will be used for your new accounts": "This new backup phrase will be used for your new accounts.", "This transaction could not be completed": "This transaction could not be completed.", "This website wants to know data about your account": "This website wants to know data about your account", "To": "To", @@ -405,8 +449,10 @@ "Transaction Rejected": "Transaction Rejected.", "Transaction sequence number": "Transaction sequence number", "Transactions": "Transactions", + "trustlines": "trustlines", "Trustor": "Trustor", "Unable to connect to": "Unable to connect to", + "Unable to migrate": "Unable to migrate", "Unable to search for assets": "Unable to search for assets", "Unsafe": "Unsafe", "Unsupported signing method": "Unsupported signing method", @@ -426,6 +472,7 @@ "which is not available on Freighter": { " If you own this account, you can import it into Freighter to complete this request": "which is not available on Freighter. If you own this account, you can import it into Freighter to complete this request." }, + "XLM": "XLM", "You are attempting to sign arbitrary data": { " Please use extreme caution and understand the implications of signing this data": "You are attempting to sign arbitrary data. Please use extreme caution and understand the implications of signing this data." }, @@ -436,6 +483,9 @@ " Proceed at your own risk": "You are interacting with data that may be using untested and changing schemas. Proceed at your own risk." }, "You can": "You can", + "You can choose to merge your current account into the new accounts after the migration, which will effectively destroy your current account": { + " Merging is optional and will allow you to send your current account’s funding lumens to the new accounts": "You can choose to merge your current account into the new accounts after the migration, which will effectively destroy your current account. Merging is optional and will allow you to send your current account’s funding lumens to the new accounts." + }, "You can fund this account using the friendbot tool": { " The friendbot is a horizon API endpoint that will fund an account with 10,000 lumens": "You can fund this account using the friendbot tool. The friendbot is a horizon API endpoint that will fund an account with 10,000 lumens." }, diff --git a/extension/src/popup/locales/pt/translation.json b/extension/src/popup/locales/pt/translation.json index 29d82f7cb0..45011aeab7 100644 --- a/extension/src/popup/locales/pt/translation.json +++ b/extension/src/popup/locales/pt/translation.json @@ -8,6 +8,8 @@ "About": "About", "account": "account", "Account": "Account", + "Account migration": "Account migration", + "Account Migration": "Account Migration", "Account minimum balance is too low": "Account minimum balance is too low", "Account not available": "Account not available", "Action": "Action", @@ -46,6 +48,9 @@ "Approve using": "Approve using", "are required to add a new asset": "are required to add a new asset.", "Are you sure you want to remove this network? You will have to re-add it if you want to use it again": "Are you sure you want to remove this network? You will have to re-add it if you want to use it again.", + "As long as you have your old and new mnemonics phrase, you’ll still be able to control accounts related to your current backup phrase which were not merged": { + " For that, you’ll need to import your current backup phrase into Freighter (Freighter supports one backup phrase imported at a time)": "As long as you have your old and new mnemonics phrase, you’ll still be able to control accounts related to your current backup phrase which were not merged. For that, you’ll need to import your current backup phrase into Freighter (Freighter supports one backup phrase imported at a time)." + }, "Asset Code": "Asset Code", "Asset domain": "Asset domain", "Asset home domain doesn’t exist, TOML file format is invalid, or asset doesn't match currency description": "Asset home domain doesn’t exist, TOML file format is invalid, or asset doesn't match currency description", @@ -53,6 +58,9 @@ "Asset not found": "Asset not found", "Assets found in this domain": "Assets found in this domain", "At least one uppercase letter": "At least one uppercase letter", + "At the end of this process, Freighter will only display accounts related to the new backup phrase": { + " You’ll still be able to import your current backup phrase into Freighter and control current accounts as long as they were not merged into the new accounts": "At the end of this process, Freighter will only display accounts related to the new backup phrase. You’ll still be able to import your current backup phrase into Freighter and control current accounts as long as they were not merged into the new accounts." + }, "Authorization Entry": "Authorization Entry", "Authorization Immutable": "Authorization Immutable", "Authorization Required": "Authorization Required", @@ -66,8 +74,10 @@ " Keep your recovery phrase safe, it’s your responsibility": "Awesome, you passed the test. Keep your recovery phrase safe, it’s your responsibility." }, "Awesome, you passed the test! Pin the extension in your browser to access it easily": "Awesome, you passed the test! Pin the extension in your browser to access it easily.", + "Back": "Back", "Balance Id": "Balance Id", "Base fee": "Base fee", + "Before we start with migration, please read": "Before we start with migration, please read", "Before You Add This Asset": "Before You Add This Asset", "Block malicious or unsafe addresses and domains": "Block malicious or unsafe addresses and domains", "Block trustlines to malicious or fraudulent assets": "Block trustlines to malicious or fraudulent assets", @@ -106,6 +116,7 @@ "Create Account": "Create Account", "Create Wallet": "Create Wallet", "Custom": "Custom", + "data entries": "data entries", "Date": "Date", "Destination": "Destination", "Destination account does not accept this asset": "Destination account does not accept this asset", @@ -147,6 +158,7 @@ "Fees can vary depending on the network congestion": { " Please try using the suggested fee and try again": "Fees can vary depending on the network congestion. Please try using the suggested fee and try again." }, + "Finish": "Finish", "Footprint": "Footprint", "For your safety, signing this transaction is disabled": "For your safety, signing this transaction is disabled", "For your security, we'll check if you got it right in the next step": "For your security, we'll check if you got it right in the next step.", @@ -181,6 +193,7 @@ "I have my 12 word seed phrase": "I have my 12 word seed phrase", "I have my recovery phrase safe": "I have my recovery phrase safe", "I have read and agree to": "I have read and agree to", + "I understand, start migration": "I understand, start migration", "I’m aware Freighter can’t recover the imported secret key": "I’m aware Freighter can’t recover the imported secret key", "I’m going to need a seed phrase": "I’m going to need a seed phrase", "I’m new!": "I’m new!", @@ -195,6 +208,7 @@ "Important": "Important", "Important Warning": "Important Warning", "in order to remove an asset": "in order to remove an asset.", + "In this process, Freighter will create a new backup phrase for you and migrate your lumens, trustlines, and assets to the new account": "In this process, Freighter will create a new backup phrase for you and migrate your lumens, trustlines, and assets to the new account.", "Include a custom memo to this transaction": "Include a custom memo to this transaction", "Inflation Destination": "Inflation Destination", "Inner Transaction": "Inner Transaction", @@ -234,6 +248,8 @@ "Log In": "Log In", "Log Out": "Log Out", "Low Threshold": "Low Threshold", + "Make sure you have your 12 word backup phrase": "Make sure you have your 12 word backup phrase", + "Make sure you have your current 12 words backup phrase before continuing": "Make sure you have your current 12 words backup phrase before continuing.", "Malicious": "Malicious", "Manage Assets": "Manage Assets", "Manage connected apps": "Manage connected apps", @@ -248,6 +264,14 @@ "Memo": "Memo", "Memo (optional)": "Memo (optional)", "Memo required": "Memo required", + "Merge accounts after migrating (your funding lumens used to fund the current accounts will be sent to the new ones - you lose access to the current accounts": { + ")": "Merge accounts after migrating (your funding lumens used to fund the current accounts will be sent to the new ones - you lose access to the current accounts.)" + }, + "Migrated": "Migrated", + "Migrating": { + "": "Migrating..." + }, + "Migration complete": "Migration complete", "Min 8 characters": "Min 8 characters", "Min Amount A": "Min Amount A", "Min Amount B": "Min Amount B", @@ -266,6 +290,7 @@ "Network is in use": "Network is in use", "Network Settings": "Network Settings", "Never disclose your recovery phrase": "Never disclose your recovery phrase", + "Nevermind, cancel": "Nevermind, cancel", "New account": "New account", "New Asset": "New Asset", "New password": "New password", @@ -278,12 +303,22 @@ "None": "None", "Not defined": "Not defined", "Not enough lumens": "Not enough lumens", + "Not funded": "Not funded", + "Not migrated": "Not migrated", "Not recommended asset": "Not recommended asset", + "Now, let’s create a new mnemonic phrase": "Now, let’s create a new mnemonic phrase", "Offer Id": "Offer Id", + "One of your accounts is a signer for another account": { + " Freighter won’t migrate signing settings": { + " For your safety, Freighter won’t merge accounts with signature set up so you can still control it": "One of your accounts is a signer for another account. Freighter won’t migrate signing settings. For your safety, Freighter won’t merge accounts with signature set up so you can still control it." + } + }, "One or more operations in this transaction failed": "One or more operations in this transaction failed.", + "Only accounts ready for migration will be migrated": "Only accounts ready for migration will be migrated.", "only way to access it in a new browser": "only way to access it in a new browser", "Operation": "Operation", "Operations": "Operations", + "Optional": "Optional", "Parameters": "Parameters", "Passphrase": "Passphrase", "Paths": "Paths", @@ -307,16 +342,23 @@ " ": "Projects related to this asset may be fraudulent even if the creators say otherwise. " }, "Read before importing your key": "Read before importing your key", + "Ready to migrate": "Ready to migrate", "Received": "Received", "RECEIVED": "RECEIVED", "RECENT": "RECENT", "Recipient Stellar address": "Recipient Stellar address", "Reject": "Reject", + "Remember, Freighter will now display accounts related to the new backup phrase that was just created": { + " Use this backup phrase from now on to use your new accounts": { + " If you have accounts that were not merged, keep and use your old backup phrase to access them": "Remember, Freighter will now display accounts related to the new backup phrase that was just created. Use this backup phrase from now on to use your new accounts. If you have accounts that were not merged, keep and use your old backup phrase to access them." + } + }, "Remove": "Remove", "Remove trustline": "Remove trustline", "Reserved Balance*": "Reserved Balance*", "Resource cost": "Resource cost", "Review": "Review", + "Review accounts to migrate": "Review accounts to migrate", "Review transaction on device": "Review transaction on device", "Revocable Asset": "Revocable Asset", "Same source and destination asset": "Same source and destination asset", @@ -348,6 +390,7 @@ "Signer": "Signer", "Signing arbitrary data with a hardware wallet is currently not supported": "Signing arbitrary data with a hardware wallet is currently not supported.", "Signing this transaction is not possible at the moment": "Signing this transaction is not possible at the moment.", + "Signs for external accounts": "Signs for external accounts", "SOROBAN RPC URL": "SOROBAN RPC URL", "Source": "Source", "Sponsored Id": "Sponsored Id", @@ -387,6 +430,7 @@ "expert, a reliable community-maintained directory": "This asset was tagged as fraudulent by stellar.expert, a reliable community-maintained directory." }, "This is a relatively new asset": "This is a relatively new asset.", + "This new backup phrase will be used for your new accounts": "This new backup phrase will be used for your new accounts.", "This transaction could not be completed": "This transaction could not be completed.", "This website wants to know data about your account": "This website wants to know data about your account", "To": "To", @@ -405,8 +449,10 @@ "Transaction Rejected": "Transaction Rejected.", "Transaction sequence number": "Transaction sequence number", "Transactions": "Transactions", + "trustlines": "trustlines", "Trustor": "Trustor", "Unable to connect to": "Unable to connect to", + "Unable to migrate": "Unable to migrate", "Unable to search for assets": "Unable to search for assets", "Unsafe": "Unsafe", "Unsupported signing method": "Unsupported signing method", @@ -426,6 +472,7 @@ "which is not available on Freighter": { " If you own this account, you can import it into Freighter to complete this request": "which is not available on Freighter. If you own this account, you can import it into Freighter to complete this request." }, + "XLM": "XLM", "You are attempting to sign arbitrary data": { " Please use extreme caution and understand the implications of signing this data": "You are attempting to sign arbitrary data. Please use extreme caution and understand the implications of signing this data." }, @@ -436,6 +483,9 @@ " Proceed at your own risk": "You are interacting with data that may be using untested and changing schemas. Proceed at your own risk." }, "You can": "You can", + "You can choose to merge your current account into the new accounts after the migration, which will effectively destroy your current account": { + " Merging is optional and will allow you to send your current account’s funding lumens to the new accounts": "You can choose to merge your current account into the new accounts after the migration, which will effectively destroy your current account. Merging is optional and will allow you to send your current account’s funding lumens to the new accounts." + }, "You can fund this account using the friendbot tool": { " The friendbot is a horizon API endpoint that will fund an account with 10,000 lumens": "You can fund this account using the friendbot tool. The friendbot is a horizon API endpoint that will fund an account with 10,000 lumens." }, diff --git a/extension/src/popup/metrics/views.ts b/extension/src/popup/metrics/views.ts index 3521bbd480..e58648e165 100644 --- a/extension/src/popup/metrics/views.ts +++ b/extension/src/popup/metrics/views.ts @@ -64,6 +64,15 @@ const routeToEventName = { [ROUTES.editNetwork]: METRIC_NAMES.viewEditNetwork, [ROUTES.networkSettings]: METRIC_NAMES.viewNetworkSettings, [ROUTES.leaveFeedback]: METRIC_NAMES.viewLeaveFeedback, + [ROUTES.accountMigration]: METRIC_NAMES.viewAccountMigration, + [ROUTES.accountMigrationReviewMigration]: + METRIC_NAMES.viewAccountMigrationReviewMigration, + [ROUTES.accountMigrationMnemonicPhrase]: + METRIC_NAMES.viewAccountMigrationMnemonicPhrase, + [ROUTES.accountMigrationConfirmMigration]: + METRIC_NAMES.viewAccountMigrationConfirmMigration, + [ROUTES.accountMigrationMigrationComplete]: + METRIC_NAMES.viewAccountMigrationMigrationComplete, }; registerHandler(navigate, (_, a) => { diff --git a/extension/src/popup/styles/global.scss b/extension/src/popup/styles/global.scss index 7cd3132d51..86d475a5ce 100644 --- a/extension/src/popup/styles/global.scss +++ b/extension/src/popup/styles/global.scss @@ -16,7 +16,7 @@ --fullscreen--max-width: 1100px; - --back--button-dimension: 1rem; + --back--button-dimension: 1.25rem; --back--button-z-index: 2; --dropdown-animation: 0.3s ease-out; @@ -31,12 +31,6 @@ body { padding: 0; } -body, -html { - height: var(--popup--height); - width: var(--popup--width); -} - a { text-decoration: none; } @@ -65,17 +59,13 @@ a { } } -.simplebar-content { - height: 100%; -} - -.simplebar-scrollbar::before { - background: var(--color-gray-40); -} - // Form layout .FormLayoutView { height: 100%; + flex: 1; + display: flex; + flex-direction: column; + overflow: auto; .FormRows { height: 100%; @@ -105,15 +95,12 @@ a { } } -* { - scrollbar-color: rgba(var(--color-shadow-rgb), 0.25) var(--color-gray-00); - - &::-webkit-scrollbar { - background-color: var(--color-gray-00); - } - - &::-webkit-scrollbar-thumb { - background: rgba(var(--color-shadow-rgb), 0.25); - border-radius: 0.5rem; - } +// TODO: fix in SDS +.sds-theme-light p, +.sds-theme-light ul, +.sds-theme-light ol, +.sds-theme-dark p, +.sds-theme-dark ul, +.sds-theme-dark ol { + color: inherit !important; } diff --git a/extension/src/popup/styles/utils.scss b/extension/src/popup/styles/utils.scss new file mode 100644 index 0000000000..9486e6b808 --- /dev/null +++ b/extension/src/popup/styles/utils.scss @@ -0,0 +1,12 @@ +@use "sass:math"; + +// Convert px to rem +$base-font-size: 16px; + +@function removePxUnit($value) { + @return math.div($value, $value * 0 + 1); +} + +@function pxToRem($pxValue) { + @return #{math.div(removePxUnit($pxValue), removePxUnit($base-font-size))}rem; +} diff --git a/extension/src/popup/views/About/index.tsx b/extension/src/popup/views/About/index.tsx index b27799c006..3244d3ad59 100644 --- a/extension/src/popup/views/About/index.tsx +++ b/extension/src/popup/views/About/index.tsx @@ -3,6 +3,7 @@ import { Icon } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; import { SubviewHeader } from "popup/components/SubviewHeader"; +import { View } from "popup/basics/layout/View"; import FreighterLogo from "popup/assets/logo-freighter.svg"; @@ -27,31 +28,36 @@ export const About = () => { const currentYear = new Date().getFullYear(); return ( -
+ -
- Freighter logo -
-
-
- {t( - "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser. It’s a safer alternative to copying and pasting private keys for use with web apps.", - )} + +
+
+ Freighter logo +
+
+
+ {t( + "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser. It’s a safer alternative to copying and pasting private keys for use with web apps.", + )} +
+
{t("LINKS")}
+ + + + {t("Privacy Policy")} + + + {t("Terms of Service")} + +
-
{t("LINKS")}
- - - - {t("Privacy Policy")} - - - {t("Terms of Service")} - -
- -
- {`© ${currentYear} Stellar Development Foundation`} -
-
+ + +
+ {`© ${currentYear} Stellar Development Foundation`} +
+
+
); }; diff --git a/extension/src/popup/views/About/styles.scss b/extension/src/popup/views/About/styles.scss index 5638ef13ed..5eb02ebdc3 100644 --- a/extension/src/popup/views/About/styles.scss +++ b/extension/src/popup/views/About/styles.scss @@ -1,8 +1,6 @@ .About { display: flex; flex-direction: column; - height: var(--popup--height); - padding: var(--popup-vertical-padding) var(--popup--side-padding); font-size: 0.875rem; &__body { diff --git a/extension/src/popup/views/Account/index.tsx b/extension/src/popup/views/Account/index.tsx index 1f45e7784e..0770516585 100644 --- a/extension/src/popup/views/Account/index.tsx +++ b/extension/src/popup/views/Account/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect, useRef } from "react"; +import React, { useContext, useState, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { Button, CopyText, Icon, NavButton } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; @@ -11,7 +11,7 @@ import { ActionStatus, } from "@shared/api/types"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; +import { View } from "popup/basics/layout/View"; import { settingsNetworkDetailsSelector, settingsSorobanSupportedSelector, @@ -85,7 +85,8 @@ export const Account = () => { const [assetOperations, setAssetOperations] = useState({} as AssetOperations); const [selectedAsset, setSelectedAsset] = useState(""); - const accountDropDownRef = useRef(null); + // TODO: what is this ref used for? + // const accountDropDownRef = useRef(null); const { balances, isFunded } = accountBalances; @@ -179,85 +180,93 @@ export const Account = () => { subentryCount={accountBalances.subentryCount} /> ) : ( - <> + {isLoading ? ( ) : ( -
+ <> -
-
-
- {currentAccountName} -
- -
- {truncatedPublicKey(publicKey)} - + + +
+ ) : null + } + > +
+
+
+
+ {currentAccountName} +
+ +
+ {truncatedPublicKey(publicKey)} + +
+
+
+
+
+ } + onClick={() => navigateTo(ROUTES.viewPublicKey)} + /> +
+
+ } + onClick={() => navigateTo(ROUTES.sendPayment)} + /> +
- -
-
-
- } - onClick={() => navigateTo(ROUTES.viewPublicKey)} - />
-
- } - onClick={() => navigateTo(ROUTES.sendPayment)} + {isFunded ? ( +
+ +
+ ) : ( + -
-
-
- {isFunded ? ( - - - - ) : ( - - )} - {isFunded ? ( -
- + )}
- ) : null} -
+ + )} - + ); }; diff --git a/extension/src/popup/views/Account/styles.scss b/extension/src/popup/views/Account/styles.scss index ea7516c2c3..b1e07efc9b 100644 --- a/extension/src/popup/views/Account/styles.scss +++ b/extension/src/popup/views/Account/styles.scss @@ -3,8 +3,6 @@ } .AccountView { - padding: 0.25rem var(--account-view-padding-side); - &__account-actions { align-items: center; justify-content: space-between; @@ -67,13 +65,6 @@ } &__assets-wrapper { - // TODO: Standardize scrollbar implementation - //https://github.com/stellar/freighter/issues/419 - - height: 20.375rem; - padding-right: var(--popup--side-padding); - width: calc(var(--popup--width) - var(--popup--side-padding)); - .AccountAssets__asset { margin: 2rem 0; } @@ -82,5 +73,6 @@ &__assets-button { display: flex; justify-content: center; + margin-bottom: 1.5rem; } } diff --git a/extension/src/popup/views/AccountCreator/index.tsx b/extension/src/popup/views/AccountCreator/index.tsx index 844af56a1a..e792355e4f 100644 --- a/extension/src/popup/views/AccountCreator/index.tsx +++ b/extension/src/popup/views/AccountCreator/index.tsx @@ -16,18 +16,16 @@ import { publicKeySelector, authErrorSelector, } from "popup/ducks/accountServices"; -import { FormRows, SubmitButtonWrapper } from "popup/basics/Forms"; +import { FormRows } from "popup/basics/Forms"; +import { View } from "popup/basics/layout/View"; -import { FullscreenStyle } from "popup/components/FullscreenStyle"; import { Onboarding, - OnboardingScreen, - OnboardingHalfScreen, + OnboardingButtons, OnboardingHeader, + OnboardingOneCol, } from "popup/components/Onboarding"; -import { Header } from "popup/components/Header"; import { PasswordRequirements } from "popup/components/PasswordRequirements"; - import { MnemonicPhrase } from "popup/views/MnemonicPhrase"; import "./styles.scss"; @@ -67,22 +65,19 @@ export const AccountCreator = () => { return mnemonicPhrase && publicKey ? ( ) : ( - <> - -
- - - - {t("Create a password")} - - - {({ isValid, dirty, isSubmitting, errors, touched }) => ( + + + + + {({ isValid, dirty, isSubmitting, errors, touched }) => ( + + {t("Create a password")}
- + {({ field }: FieldProps) => ( @@ -121,49 +116,49 @@ export const AccountCreator = () => { )} + -
- - {({ field }: FieldProps) => ( - - {t("I have read and agree to")}{" "} - - {t("Terms of Use")} - - - } - {...field} - /> - )} - -
- + + + {({ field }: FieldProps) => ( + + {t("I have read and agree to")}{" "} + + {t("Terms of Use")} + + + } + {...field} + /> + )} + + + - -
+ +
- )} -
-
-
- + + )} + + + ); }; diff --git a/extension/src/popup/views/AccountHistory/index.tsx b/extension/src/popup/views/AccountHistory/index.tsx index 672e7a00f9..998f6cfd19 100644 --- a/extension/src/popup/views/AccountHistory/index.tsx +++ b/extension/src/popup/views/AccountHistory/index.tsx @@ -37,6 +37,7 @@ import { TransactionDetailProps, } from "popup/components/accountHistory/TransactionDetail"; import { BottomNav } from "popup/components/BottomNav"; +import { View } from "popup/basics/layout/View"; import { SorobanContext } from "../../SorobanContext"; import "./styles.scss"; @@ -186,60 +187,66 @@ export const AccountHistory = () => { return isDetailViewShowing ? ( ) : ( -
- {isLoading || isTokenBalanceLoading ? ( -
- -
- ) : ( -
-
- {t("Transactions")} -
-
- {Object.values(SELECTOR_OPTIONS).map((option) => ( -
setSelectedSegment(option)} - > - {t(option)} + + +
+ {isLoading || isTokenBalanceLoading ? ( +
+ +
+ ) : ( + <> +
+ {t("Transactions")} +
+
+ {Object.values(SELECTOR_OPTIONS).map((option) => ( +
setSelectedSegment(option)} + > + {t(option)} +
+ ))}
- ))} -
-
- {historySegments?.[SELECTOR_OPTIONS[selectedSegment]].length ? ( - - <> - {historySegments![SELECTOR_OPTIONS[selectedSegment]].map( - (operation: HistoryItemOperation) => ( - - ), - )} - - - ) : ( -
- {isAccountHistoryLoading ? null : t("No transactions to show")} +
+ {historySegments?.[SELECTOR_OPTIONS[selectedSegment]].length ? ( + + <> + {historySegments![SELECTOR_OPTIONS[selectedSegment]].map( + (operation: HistoryItemOperation) => ( + + ), + )} + + + ) : ( +
+ {isAccountHistoryLoading + ? null + : t("No transactions to show")} +
+ )}
- )} -
+ + )}
- )} +
-
+ ); }; diff --git a/extension/src/popup/views/AccountHistory/styles.scss b/extension/src/popup/views/AccountHistory/styles.scss index d1b598212c..ad1e6eff53 100644 --- a/extension/src/popup/views/AccountHistory/styles.scss +++ b/extension/src/popup/views/AccountHistory/styles.scss @@ -1,11 +1,7 @@ .AccountHistory { - &__wrapper { - padding: var(--popup-vertical-padding) var(--popup--side-padding); - } - &__loader { - height: calc(var(--popup--height) - var(--bottom-nav--height)); - width: var(--popup--width); + height: 100%; + width: 100%; z-index: calc(var(--back--button-z-index) + 1); position: absolute; display: flex; diff --git a/extension/src/popup/views/AccountMigration/index.tsx b/extension/src/popup/views/AccountMigration/index.tsx new file mode 100644 index 0000000000..5be1cb59d3 --- /dev/null +++ b/extension/src/popup/views/AccountMigration/index.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import { Switch } from "react-router-dom"; + +import { PublicKeyRoute, VerifiedAccountRoute } from "popup/Router"; +import { ROUTES } from "popup/constants/routes"; +import { View } from "popup/basics/layout/View"; + +import { MigrationStart } from "popup/components/accountMigration/MigrationStart"; +import { ReviewMigration } from "popup/components/accountMigration/ReviewMigration"; +import { MnemonicPhrase } from "popup/components/accountMigration/MnemonicPhrase"; +import { ConfirmMigration } from "popup/components/accountMigration/ConfirmMigration"; +import { MigrationComplete } from "popup/components/accountMigration/MigrationComplete"; + +import "./styles.scss"; + +export const AccountMigration = () => ( + <> + + + + + +
+ +
+
+ +
+ +
+
+ + + + +
+ +
+
+ +
+ +
+
+
+
+
+ +); diff --git a/extension/src/popup/views/AccountMigration/styles.scss b/extension/src/popup/views/AccountMigration/styles.scss new file mode 100644 index 0000000000..5700e1610a --- /dev/null +++ b/extension/src/popup/views/AccountMigration/styles.scss @@ -0,0 +1,6 @@ +.AccountMigration { + height: 100%; + margin: 10rem auto 0; + max-width: 588px; + width: 100%; +} diff --git a/extension/src/popup/views/AddAccount/AddAccount/index.tsx b/extension/src/popup/views/AddAccount/AddAccount/index.tsx index 3dcc867b18..c3b71f6b77 100644 --- a/extension/src/popup/views/AddAccount/AddAccount/index.tsx +++ b/extension/src/popup/views/AddAccount/AddAccount/index.tsx @@ -10,8 +10,8 @@ import { AppDispatch } from "popup/App"; import { navigateTo } from "popup/helpers/navigate"; import { emitMetric } from "helpers/metrics"; -import { FormRows, SubmitButtonWrapper } from "popup/basics/Forms"; -import { PopupWrapper } from "popup/basics/PopupWrapper"; +import { FormRows } from "popup/basics/Forms"; +import { View } from "popup/basics/layout/View"; import { SubviewHeader } from "popup/components/SubviewHeader"; @@ -54,12 +54,12 @@ export const AddAccount = () => { ]); return ( - <> - - - - {({ dirty, isSubmitting, isValid, errors, touched }) => ( -
+ + + + {({ dirty, isSubmitting, isValid, errors, touched }) => ( + + {({ field }: FieldProps) => ( @@ -79,23 +79,23 @@ export const AddAccount = () => { /> )} - - - - - )} - -
- + + + + + + )} + + ); }; diff --git a/extension/src/popup/views/AddAccount/ImportAccount/index.tsx b/extension/src/popup/views/AddAccount/ImportAccount/index.tsx index e19314573f..1677ae8c0b 100644 --- a/extension/src/popup/views/AddAccount/ImportAccount/index.tsx +++ b/extension/src/popup/views/AddAccount/ImportAccount/index.tsx @@ -22,6 +22,7 @@ import { importAccount, authErrorSelector } from "popup/ducks/accountServices"; import { SubviewHeader } from "popup/components/SubviewHeader"; import "./styles.scss"; +import { View } from "popup/basics/layout/View"; export const ImportAccount = () => { interface FormValues { @@ -64,68 +65,72 @@ export const ImportAccount = () => { }; return ( -
+ -
- } - title={t("Read before importing your key")} - > - {t( - "Freighter can’t recover your imported secret key using your recovery phrase. Storing your secret key is your responsibility. Freighter will never ask for your secret key outside of the extension.", - )} - -
+ {({ dirty, isSubmitting, isValid }) => ( -
- - - {({ field }: FieldProps) => ( - - )} - - - {({ field }: FieldProps) => ( - - )} - - - {({ field }: FieldProps) => ( - - )} - - -
+ + +
+ } + title={t("Read before importing your key")} + > + {t( + "Freighter can’t recover your imported secret key using your recovery phrase. Storing your secret key is your responsibility. Freighter will never ask for your secret key outside of the extension.", + )} + +
+ + + + {({ field }: FieldProps) => ( + + )} + + + {({ field }: FieldProps) => ( + + )} + + + {({ field }: FieldProps) => ( + + )} + + +
+ -
+
)}
-
+ ); }; diff --git a/extension/src/popup/views/AddAccount/ImportAccount/styles.scss b/extension/src/popup/views/AddAccount/ImportAccount/styles.scss index 5a70f9c22d..f0a2529b79 100644 --- a/extension/src/popup/views/AddAccount/ImportAccount/styles.scss +++ b/extension/src/popup/views/AddAccount/ImportAccount/styles.scss @@ -1,9 +1,4 @@ .ImportAccount { - padding: var(--popup-vertical-padding) var(--popup--side-padding); - height: var(--popup--height); - display: flex; - flex-direction: column; - &__warning-block { margin-bottom: 1rem; } diff --git a/extension/src/popup/views/AddAccount/connect/ConnectWallet/index.tsx b/extension/src/popup/views/AddAccount/connect/ConnectWallet/index.tsx index 8d1ecfd029..3937a8bb19 100644 --- a/extension/src/popup/views/AddAccount/connect/ConnectWallet/index.tsx +++ b/extension/src/popup/views/AddAccount/connect/ConnectWallet/index.tsx @@ -10,6 +10,7 @@ import LedgerLogo from "popup/assets/ledger-logo.png"; import { WalletType } from "@shared/constants/hardwareWallet"; import "./styles.scss"; +import { View } from "popup/basics/layout/View"; export const ConnectWallet = () => { const dispatch = useDispatch(); @@ -19,24 +20,26 @@ export const ConnectWallet = () => { navigateTo(ROUTES.connectWalletPlugin); }; return ( - <> -
- navigateTo(ROUTES.account)} - /> -

Select a hardware wallet you’d like to use with Freighter.

-
    -
  • handleContinue(WalletType.LEDGER)} - > - ledger logo -
  • -
-
+ + navigateTo(ROUTES.account)} + /> + +
+

Select a hardware wallet you’d like to use with Freighter.

+
    +
  • handleContinue(WalletType.LEDGER)} + > + ledger logo +
  • +
+
+
- +
); }; diff --git a/extension/src/popup/views/AddAccount/connect/ConnectWallet/styles.scss b/extension/src/popup/views/AddAccount/connect/ConnectWallet/styles.scss index 6d0917fd49..43ac5e95e9 100644 --- a/extension/src/popup/views/AddAccount/connect/ConnectWallet/styles.scss +++ b/extension/src/popup/views/AddAccount/connect/ConnectWallet/styles.scss @@ -1,7 +1,4 @@ .ConnectWallet { - padding: var(--popup-vertical-padding) var(--account-view-padding-side) 1rem - var(--account-view-padding-side); - height: calc(var(--popup--height) - var(--bottom-nav--height)); display: flex; flex-direction: column; diff --git a/extension/src/popup/views/DisplayBackupPhrase/index.tsx b/extension/src/popup/views/DisplayBackupPhrase/index.tsx index ed74fd11d2..334bb8ff92 100644 --- a/extension/src/popup/views/DisplayBackupPhrase/index.tsx +++ b/extension/src/popup/views/DisplayBackupPhrase/index.tsx @@ -13,6 +13,7 @@ import { METRIC_NAMES } from "popup/constants/metricsNames"; import { MnemonicDisplay } from "popup/components/mnemonicPhrase/MnemonicDisplay"; import { SubviewHeader } from "popup/components/SubviewHeader"; +import { View } from "popup/basics/layout/View"; import { BackupPhraseWarningMessage } from "popup/components/WarningMessages"; @@ -57,19 +58,21 @@ export const DisplayBackupPhrase = () => { }; return ( -
+ {isPhraseUnlocked ? ( <> -
-

- {t( - "Anyone who has access to this phrase has access to your account and to the funds in it, so save it in a safe and secure place.", - )} -

- -
-
+ +
+

+ {t( + "Anyone who has access to this phrase has access to your account and to the funds in it, so save it in a safe and secure place.", + )} +

+ +
+
+ -
+ ) : ( <> - {({ dirty, isSubmitting, isValid }) => (
- } - type="password" - name="password" - placeholder={t("Enter your password")} - /> - - + + + } + type="password" + name="password" + placeholder={t("Enter your password")} + /> + + + +
)}
)} -
+ ); }; diff --git a/extension/src/popup/views/DisplayBackupPhrase/styles.scss b/extension/src/popup/views/DisplayBackupPhrase/styles.scss index 1f862e76aa..a1702f0332 100644 --- a/extension/src/popup/views/DisplayBackupPhrase/styles.scss +++ b/extension/src/popup/views/DisplayBackupPhrase/styles.scss @@ -1,9 +1,4 @@ .DisplayBackupPhrase { - display: flex; - flex-direction: column; - height: var(--popup--height); - padding: var(--popup-vertical-padding) var(--popup--side-padding); - &__form { display: flex; flex-direction: column; diff --git a/extension/src/popup/views/FullscreenSuccessMessage/index.tsx b/extension/src/popup/views/FullscreenSuccessMessage/index.tsx index 94971e0036..0b79341806 100644 --- a/extension/src/popup/views/FullscreenSuccessMessage/index.tsx +++ b/extension/src/popup/views/FullscreenSuccessMessage/index.tsx @@ -9,9 +9,8 @@ import { ROUTES } from "popup/constants/routes"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { navigateTo } from "popup/helpers/navigate"; import { SubmitButtonWrapper } from "popup/basics/Forms"; -import { FullscreenStyle } from "popup/components/FullscreenStyle"; -import { Header } from "popup/components/Header"; -import { OnboardingHeader } from "popup/components/Onboarding"; +import { Onboarding, OnboardingHeader } from "popup/components/Onboarding"; +import { View } from "popup/basics/layout/View"; import ExtensionIllo from "popup/assets/illo-extension.png"; import "./styles.scss"; @@ -64,11 +63,10 @@ const MnemonicPhraseConfirmedMessage = () => {

- +
)} - + +
- -
+ {/* GitHub */} +
+
+ +
+
+ {t( + "Have a suggestion or bug report? Create an issue on Github", + )} +
- {/* GitHub */} -
-
- + +
-
- {t("Have a suggestion or bug report? Create an issue on Github")} -
- -
-
-
+ + ); }; diff --git a/extension/src/popup/views/LeaveFeedback/styles.scss b/extension/src/popup/views/LeaveFeedback/styles.scss index 860d5730d5..5c63e03790 100644 --- a/extension/src/popup/views/LeaveFeedback/styles.scss +++ b/extension/src/popup/views/LeaveFeedback/styles.scss @@ -1,8 +1,6 @@ .LeaveFeedback { display: flex; flex-direction: column; - height: var(--popup--height); - padding: var(--popup-vertical-padding) var(--popup--side-padding); font-size: 0.875rem; line-height: 1.375rem; diff --git a/extension/src/popup/views/ManageConnectedApps/index.tsx b/extension/src/popup/views/ManageConnectedApps/index.tsx index 5df6f5a65e..bea2ef2a4f 100644 --- a/extension/src/popup/views/ManageConnectedApps/index.tsx +++ b/extension/src/popup/views/ManageConnectedApps/index.tsx @@ -2,11 +2,11 @@ import React from "react"; import { useSelector, useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; import { PillButton } from "popup/basics/buttons/PillButton"; import { saveAllowList, settingsSelector } from "popup/ducks/settings"; import { SubviewHeader } from "popup/components/SubviewHeader"; +import { View } from "popup/basics/layout/View"; import "./styles.scss"; @@ -26,29 +26,36 @@ export const ManageConnectedApps = () => { }; return ( -
+ - {allowList.length ? ( - -
- {allowList.map( - (allowedDomain) => - allowedDomain && ( -
-
{allowedDomain}
- handleRemove(allowedDomain)}> - {t("Remove")} - -
- ), - )} -
-
- ) : ( -
- No connected apps found + +
+ {allowList.length ? ( +
+
+ {allowList.map( + (allowedDomain) => + allowedDomain && ( +
+
{allowedDomain}
+ handleRemove(allowedDomain)}> + {t("Remove")} + +
+ ), + )} +
+
+ ) : ( +
+ No connected apps found +
+ )}
- )} -
+ +
); }; diff --git a/extension/src/popup/views/ManageConnectedApps/styles.scss b/extension/src/popup/views/ManageConnectedApps/styles.scss index a0d8199706..e253dbdd00 100644 --- a/extension/src/popup/views/ManageConnectedApps/styles.scss +++ b/extension/src/popup/views/ManageConnectedApps/styles.scss @@ -2,18 +2,6 @@ display: flex; flex-direction: column; height: 100%; - padding: var(--popup-vertical-padding) var(--popup--side-padding); - - &__wrapper { - max-height: calc( - var(--popup--height) - var(--subview--header--height) - - var(--popup-vertical-padding) - ); - width: calc( - var(--popup--width) - var(--popup--side-padding) - - var(--popup--side-padding) - ); - } &__content { display: flex; @@ -31,10 +19,6 @@ &__empty { align-items: center; display: flex; - height: calc( - var(--popup--height) - var(--subview--header--height) - - var(--popup-vertical-padding) - ); justify-content: center; } } diff --git a/extension/src/popup/views/MnemonicPhrase.tsx b/extension/src/popup/views/MnemonicPhrase.tsx index b75934ce40..fc46f83b3d 100644 --- a/extension/src/popup/views/MnemonicPhrase.tsx +++ b/extension/src/popup/views/MnemonicPhrase.tsx @@ -6,12 +6,11 @@ import { Redirect } from "react-router-dom"; import { APPLICATION_STATE } from "@shared/constants/applicationState"; import { ROUTES } from "popup/constants/routes"; -import { FullscreenStyle } from "popup/components/FullscreenStyle"; -import { Header } from "popup/components/Header"; import { Onboarding } from "popup/components/Onboarding"; import { ConfirmMnemonicPhrase } from "popup/components/mnemonicPhrase/ConfirmMnemonicPhrase"; import { DisplayMnemonicPhrase } from "popup/components/mnemonicPhrase/DisplayMnemonicPhrase"; import { applicationStateSelector } from "popup/ducks/accountServices"; +import { View } from "popup/basics/layout/View"; interface MnemonicPhraseProps { mnemonicPhrase: string; @@ -29,24 +28,30 @@ export const MnemonicPhrase = ({ if (mnemonicPhrase) { return isConfirmed ? ( - <> -
- - setIsConfirmed(false)} hasGoBackBtn> - - - + + + + + setIsConfirmed(false)} + hasGoBackBtn + /> + + + ) : ( - <> -
- - - - - + + + + + + + + ); } diff --git a/extension/src/popup/views/PinExtension/index.tsx b/extension/src/popup/views/PinExtension/index.tsx index 8cbd9e522c..3e098fb16f 100644 --- a/extension/src/popup/views/PinExtension/index.tsx +++ b/extension/src/popup/views/PinExtension/index.tsx @@ -4,55 +4,58 @@ import { Button, Icon } from "@stellar/design-system"; import ExtensionsPin from "popup/assets/illo-pin-extension.svg"; import { SubmitButtonWrapper } from "popup/basics/Forms"; -import { Header } from "popup/components/Header"; -import { FullscreenStyle } from "popup/components/FullscreenStyle"; -import { OnboardingHeader } from "popup/components/Onboarding"; +import { Onboarding, OnboardingHeader } from "popup/components/Onboarding"; import "./styles.scss"; +import { View } from "popup/basics/layout/View"; export const PinExtension = () => { const { t } = useTranslation(); return ( - <> -
- -
-
- + + + + + {t("Your Freighter install is complete")} -
-
- 1.{" "} - {t( - "Click on the extensions button at the top of your browser’s bar", - )} +
+
+
+
+ 1.{" "} + {t( + "Click on the extensions button at the top of your browser’s bar", + )} +
+
+ 2.{" "} + {t( + "Click on Freighter’s pin button to have it always visible", + )} +
+
+
+ Extensions Pin +
+ + +
-
- 2.{" "} - {t("Click on Freighter’s pin button to have it always visible")} -
-
-
- Extensions Pin
- - - -
-
- +
+
+
); }; diff --git a/extension/src/popup/views/PinExtension/styles.scss b/extension/src/popup/views/PinExtension/styles.scss index b56755bc71..0c0f81fd9d 100644 --- a/extension/src/popup/views/PinExtension/styles.scss +++ b/extension/src/popup/views/PinExtension/styles.scss @@ -1,5 +1,4 @@ .PinExtension { - margin-top: 4.5rem; width: 100%; display: flex; flex-direction: column; @@ -9,15 +8,6 @@ max-width: 31rem; } - &__title { - font-size: 1.5rem; - line-height: 2.25rem; - text-align: center; - color: var(--color-gray-90); - margin-bottom: 1rem; - font-weight: var(--font-weight-medium); - } - &__caption { line-height: 1.5rem; margin-bottom: 2rem; diff --git a/extension/src/popup/views/Preferences/index.tsx b/extension/src/popup/views/Preferences/index.tsx index cfe3f76400..da64a8300a 100644 --- a/extension/src/popup/views/Preferences/index.tsx +++ b/extension/src/popup/views/Preferences/index.tsx @@ -3,7 +3,7 @@ import { Toggle } from "@stellar/design-system"; import { Field, Form, Formik } from "formik"; import { useSelector, useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; -import { SimpleBarWrapper } from "popup/basics/SimpleBarWrapper"; +import { View } from "popup/basics/layout/View"; import { saveSettings, settingsSelector } from "popup/ducks/settings"; @@ -60,107 +60,109 @@ export const Preferences = () => { }; return ( -
+ - - -
- -
-
- {t("Verification with")} stellar.expert -
-
- - } - id="isValidatingMemoValue" - /> -
-
- - } - id="isValidatingSafetyValue" - /> -
+ + +
+ + +
+
+ {t("Verification with")} stellar.expert +
+
+ + } + id="isValidatingMemoValue" + /> +
+
+ + } + id="isValidatingSafetyValue" + /> +
-
- - } - id="isValidatingSafeAssetsValue" - /> +
+ + } + id="isValidatingSafeAssetsValue" + /> +
-
-
-
- {t("Anonymous data sharing")}{" "} -
+
+
+ {t("Anonymous data sharing")}{" "} +
-
- - } - id="isDataSharingAllowedValue" - /> -
-
-
-
- {t("Enable experimental mode")}{" "} +
+ + } + id="isDataSharingAllowedValue" + /> +
+
+
+ {t("Enable experimental mode")}{" "} +
-
- - } - id="isExperimentalModeEnabledValue" - /> +
+ + } + id="isExperimentalModeEnabledValue" + /> +
-
- - - -
+ +
+ + + ); }; diff --git a/extension/src/popup/views/Preferences/styles.scss b/extension/src/popup/views/Preferences/styles.scss index 43c3a06a5f..a8fdfc50e9 100644 --- a/extension/src/popup/views/Preferences/styles.scss +++ b/extension/src/popup/views/Preferences/styles.scss @@ -1,20 +1,10 @@ .Preferences { font-size: 0.875rem; - padding: var(--popup-vertical-padding) 0 var(--popup-vertical-padding) - var(--popup--side-padding); - - &__simplebar { - height: calc( - var(--popup--height) - (var(--popup-vertical-padding) * 2) - - var(--subview--header--height) - ); - } &--section { display: flex; flex-direction: column; gap: 1rem; - padding-right: var(--popup-vertical-padding); &--title { color: var(--color-gray-60); diff --git a/extension/src/popup/views/RecoverAccount/index.tsx b/extension/src/popup/views/RecoverAccount/index.tsx index f6d53779e1..db5fae9d9c 100644 --- a/extension/src/popup/views/RecoverAccount/index.tsx +++ b/extension/src/popup/views/RecoverAccount/index.tsx @@ -5,10 +5,15 @@ import { object as YupObject } from "yup"; import { Input, Checkbox, Icon, Link, Button } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; -import { Onboarding } from "popup/components/Onboarding"; -import { FormError, FormRows, SubmitButtonWrapper } from "popup/basics/Forms"; -import { FullscreenStyle } from "popup/components/FullscreenStyle"; -import { Header } from "popup/components/Header"; +import { + Onboarding, + OnboardingButtons, + OnboardingHeader, + OnboardingOneCol, + OnboardingTwoCol, +} from "popup/components/Onboarding"; +import { FormError, FormRows } from "popup/basics/Forms"; +import { View } from "popup/basics/layout/View"; import { PasswordRequirements } from "popup/components/PasswordRequirements"; import { ROUTES } from "popup/constants/routes"; @@ -132,68 +137,74 @@ export const RecoverAccount = () => { }; return ( - <> -
- - + + + {({ dirty, touched, isSubmitting, isValid, errors }) => ( -
-
-
-
+ + + + {t("Import wallet from recovery phrase")} -
+
{t("Enter your 12 word phrase to restore your wallet")}
-
- {phraseInputs.map((phraseInput, i) => ( - + + + +
+ {phraseInputs.map((phraseInput, i) => ( + + ))} +
+ {authError ? {authError} : <>} +
+ + + + } + id="password-input" + name="password" + placeholder={t("New password")} + type="password" + error={ + errors.password && touched.password + ? errors.password + : "" + } /> - ))} -
- {authError} -
-
- - } - id="password-input" - name="password" - placeholder={t("New password")} - type="password" - error={ - errors.password && touched.password - ? errors.password - : "" - } - /> - } - id="confirm-password-input" - name="confirmPassword" - placeholder={t("Confirm password")} - type="password" - error={ - errors.confirmPassword && touched.confirmPassword - ? errors.confirmPassword - : null - } - /> - + } + id="confirm-password-input" + name="confirmPassword" + placeholder={t("Confirm password")} + type="password" + error={ + errors.confirmPassword && touched.confirmPassword + ? errors.confirmPassword + : null + } + /> + + + {({ field }: FieldProps) => ( { /> )} - - - - -
-
-
+ + + + + + + +
)} - - + + ); }; diff --git a/extension/src/popup/views/Security/index.tsx b/extension/src/popup/views/Security/index.tsx index b34261573e..c56855a077 100644 --- a/extension/src/popup/views/Security/index.tsx +++ b/extension/src/popup/views/Security/index.tsx @@ -3,9 +3,17 @@ import { useTranslation } from "react-i18next"; import { ROUTES } from "popup/constants/routes"; +import { newTabHref } from "helpers/urls"; +import { openTab } from "popup/helpers/navigate"; + import { SubviewHeader } from "popup/components/SubviewHeader"; +import { View } from "popup/basics/layout/View"; -import { ListNavLink, ListNavLinkWrapper } from "popup/basics/ListNavLink"; +import { + ListNavLink, + ListNavButtonLink, + ListNavLinkWrapper, +} from "popup/basics/ListNavLink"; import "./styles.scss"; @@ -13,20 +21,29 @@ export const Security = () => { const { t } = useTranslation(); return ( -
+ - - {/* + + + {/* TODO: Add Change Password - Change Password + Change Password */} - - {t("Show recovery phrase")} - - - {t("Manage connected apps")} - - -
+ + {t("Show recovery phrase")} + + + {t("Manage connected apps")} + + { + openTab(newTabHref(ROUTES.accountMigration)); + }} + > + {t("Account migration")} + + + + ); }; diff --git a/extension/src/popup/views/Security/styles.scss b/extension/src/popup/views/Security/styles.scss index 9e8ca28656..b4d500b210 100644 --- a/extension/src/popup/views/Security/styles.scss +++ b/extension/src/popup/views/Security/styles.scss @@ -1,3 +1,2 @@ .Security { - padding: var(--popup-vertical-padding) var(--popup--side-padding); } diff --git a/extension/src/popup/views/Settings/index.tsx b/extension/src/popup/views/Settings/index.tsx index cb126ce68d..4a8e8c30dc 100644 --- a/extension/src/popup/views/Settings/index.tsx +++ b/extension/src/popup/views/Settings/index.tsx @@ -7,6 +7,7 @@ import { ROUTES } from "popup/constants/routes"; import { navigateTo } from "popup/helpers/navigate"; import { ListNavLink, ListNavLinkWrapper } from "popup/basics/ListNavLink"; +import { View } from "popup/basics/layout/View"; import { BottomNav } from "popup/components/BottomNav"; @@ -27,44 +28,48 @@ export const Settings = () => { }; return ( - <> - + } + > + + - + ); }; diff --git a/extension/src/popup/views/Settings/styles.scss b/extension/src/popup/views/Settings/styles.scss index 4a304bedff..2aec8c8119 100644 --- a/extension/src/popup/views/Settings/styles.scss +++ b/extension/src/popup/views/Settings/styles.scss @@ -1,8 +1,6 @@ .Settings { display: flex; flex-direction: column; - height: calc(var(--popup--height) - var(--bottom-nav--height)); - padding: 2rem var(--popup--side-padding) var(--popup-vertical-padding); &__header { margin-bottom: 2rem; @@ -14,9 +12,7 @@ } &__logout { - flex-grow: 1; display: flex; - flex-direction: column; - justify-content: flex-end; + justify-content: center; } } diff --git a/extension/src/popup/views/SignAuthEntry/index.tsx b/extension/src/popup/views/SignAuthEntry/index.tsx index 8a8a6d684e..980f3ec5f7 100644 --- a/extension/src/popup/views/SignAuthEntry/index.tsx +++ b/extension/src/popup/views/SignAuthEntry/index.tsx @@ -3,11 +3,6 @@ import { Button, Card, Icon, Notification } from "@stellar/design-system"; import { useLocation } from "react-router-dom"; import { useSelector } from "react-redux"; import { useTranslation, Trans } from "react-i18next"; -import { - ButtonsContainer, - ModalHeader, - ModalWrapper, -} from "popup/basics/Modal"; import { truncatedPublicKey } from "helpers/stellar"; import { LedgerSign } from "popup/components/hardwareConnect/LedgerSign"; @@ -20,16 +15,17 @@ import { WarningMessageVariant, WarningMessage, } from "popup/components/WarningMessages"; +import { AuthEntry } from "popup/components/signAuthEntry/AuthEntry"; +import { View } from "popup/basics/layout/View"; import { signEntry, rejectAuthEntry } from "popup/ducks/access"; import { settingsExperimentalModeSelector } from "popup/ducks/settings"; import { ShowOverlayStatus } from "popup/ducks/transactionSubmission"; import { VerifyAccount } from "popup/views/VerifyAccount"; import { EntryToSign, parsedSearchParam } from "helpers/urls"; -import { AuthEntry } from "popup/components/signAuthEntry/AuthEntry"; +import { useSetupSigningFlow } from "popup/helpers/useSetupSigningFlow"; import "./styles.scss"; -import { useSetupSigningFlow } from "popup/helpers/useSetupSigningFlow"; export const SignAuthEntry = () => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); @@ -65,41 +61,37 @@ export const SignAuthEntry = () => { if (isHardwareWallet) { return ( - - window.close()} - isActive - header={t("Unsupported signing method")} - > -

- {t( - "Signing arbitrary data with a hardware wallet is currently not supported.", - )} -

-
-
+ window.close()} + isActive + header={t("Unsupported signing method")} + > +

+ {t( + "Signing arbitrary data with a hardware wallet is currently not supported.", + )} +

+
); } if (!params.url.startsWith("https") && !isExperimentalModeEnabled) { return ( - - window.close()} - isActive - variant={WarningMessageVariant.warning} - header={t("WEBSITE CONNECTION IS NOT SECURE")} - > -

- - The website {{ domain: params.url }} does not use - an SSL certificate. For additional safety Freighter only works - with websites that provide an SSL certificate. - -

-
-
+ window.close()} + isActive + variant={WarningMessageVariant.warning} + header={t("WEBSITE CONNECTION IS NOT SECURE")} + > +

+ + The website {{ domain: params.url }} does not use + an SSL certificate. For additional safety Freighter only works with + websites that provide an SSL certificate. + +

+
); } @@ -112,11 +104,9 @@ export const SignAuthEntry = () => { ) : ( <> {hwStatus === ShowOverlayStatus.IN_PROGRESS && } -
- - - {t("Confirm Data")} - + + + {isExperimentalModeEnabled ? ( { transaction={{ _operations: [{ auth: params.entry }] }} /> */} - - + + - + { />
-
+ ); }; diff --git a/extension/src/popup/views/SignBlob/index.tsx b/extension/src/popup/views/SignBlob/index.tsx index fc1b4dd94a..ad36c7f254 100644 --- a/extension/src/popup/views/SignBlob/index.tsx +++ b/extension/src/popup/views/SignBlob/index.tsx @@ -14,24 +14,19 @@ import { WarningMessage, FirstTimeWarningMessage, } from "popup/components/WarningMessages"; +import { View } from "popup/basics/layout/View"; import { ShowOverlayStatus } from "popup/ducks/transactionSubmission"; -import { - ButtonsContainer, - ModalHeader, - ModalWrapper, -} from "popup/basics/Modal"; - import { LedgerSign } from "popup/components/hardwareConnect/LedgerSign"; import { SlideupModal } from "popup/components/SlideupModal"; import { VerifyAccount } from "popup/views/VerifyAccount"; import { BlobToSign, parsedSearchParam } from "helpers/urls"; import { truncatedPublicKey } from "helpers/stellar"; +import { useSetupSigningFlow } from "popup/helpers/useSetupSigningFlow"; import "./styles.scss"; -import { useSetupSigningFlow } from "popup/helpers/useSetupSigningFlow"; export const SignBlob = () => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); @@ -62,41 +57,37 @@ export const SignBlob = () => { if (isHardwareWallet) { return ( - - window.close()} - isActive - header={t("Unsupported signing method")} - > -

- {t( - "Signing arbitrary data with a hardware wallet is currently not supported.", - )} -

-
-
+ window.close()} + isActive + header={t("Unsupported signing method")} + > +

+ {t( + "Signing arbitrary data with a hardware wallet is currently not supported.", + )} +

+
); } if (!domain.startsWith("https") && !isExperimentalModeEnabled) { return ( - - window.close()} - isActive - variant={WarningMessageVariant.warning} - header={t("WEBSITE CONNECTION IS NOT SECURE")} - > -

- - The website {{ domain }} does not use an SSL - certificate. For additional safety Freighter only works with - websites that provide an SSL certificate. - -

-
-
+ window.close()} + isActive + variant={WarningMessageVariant.warning} + header={t("WEBSITE CONNECTION IS NOT SECURE")} + > +

+ + The website {{ domain }} does not use an SSL + certificate. For additional safety Freighter only works with + websites that provide an SSL certificate. + +

+
); } @@ -109,11 +100,9 @@ export const SignBlob = () => { ) : ( <> {hwStatus === ShowOverlayStatus.IN_PROGRESS && } -
- - - {t("Confirm Data")} - + + + {isExperimentalModeEnabled ? ( { ) : null}
- - +
+ - + { />
-
+ ); }; diff --git a/extension/src/popup/views/SignTransaction/index.tsx b/extension/src/popup/views/SignTransaction/index.tsx index 61f228e666..8f8d7dbb21 100644 --- a/extension/src/popup/views/SignTransaction/index.tsx +++ b/extension/src/popup/views/SignTransaction/index.tsx @@ -32,12 +32,6 @@ import { decodeMemo } from "popup/helpers/parseTransaction"; import { useSetupSigningFlow } from "popup/helpers/useSetupSigningFlow"; import { TransactionHeading } from "popup/basics/TransactionHeading"; -import { - ButtonsContainer, - ModalHeader, - ModalWrapper, -} from "popup/basics/Modal"; - import { METRIC_NAMES } from "popup/constants/metricsNames"; import { AccountListIdenticon } from "popup/components/identicons/AccountListIdenticon"; @@ -52,6 +46,7 @@ import { import { Transaction as SignTxTransaction } from "popup/components/signTransaction/Transaction"; import { LedgerSign } from "popup/components/hardwareConnect/LedgerSign"; import { SlideupModal } from "popup/components/SlideupModal"; +import { View } from "popup/basics/layout/View"; import { VerifyAccount } from "popup/views/VerifyAccount"; @@ -84,10 +79,10 @@ export const SignTransaction = () => { flaggedKeys, } = tx; - /* - Reconstruct the tx from xdr as passing a tx through extension contexts - loses custom prototypes associated with some values. This is fine for most cases - where we just need a high level overview of the tx, like just a list of operations. + /* + Reconstruct the tx from xdr as passing a tx through extension contexts + loses custom prototypes associated with some values. This is fine for most cases + where we just need a high level overview of the tx, like just a list of operations. But in this case, we will need the hostFn prototype associated with Soroban tx operations. */ @@ -187,41 +182,37 @@ export const SignTransaction = () => { if (_networkPassphrase !== networkPassphrase) { return ( - - window.close()} - isActive - header={`${t("Freighter is set to")} ${networkName}`} - > -

- {t("The transaction you’re trying to sign is on")}{" "} - {_networkPassphrase}. -

-

{t("Signing this transaction is not possible at the moment.")}

-
-
+ window.close()} + isActive + header={`${t("Freighter is set to")} ${networkName}`} + > +

+ {t("The transaction you’re trying to sign is on")}{" "} + {_networkPassphrase}. +

+

{t("Signing this transaction is not possible at the moment.")}

+
); } if (!isHttpsDomain && !isExperimentalModeEnabled) { return ( - - window.close()} - isActive - variant={WarningMessageVariant.warning} - header={t("WEBSITE CONNECTION IS NOT SECURE")} - > -

- - The website {{ domain }} does not use an SSL - certificate. For additional safety Freighter only works with - websites that provide an SSL certificate. - -

-
-
+ window.close()} + isActive + variant={WarningMessageVariant.warning} + header={t("WEBSITE CONNECTION IS NOT SECURE")} + > +

+ + The website {{ domain }} does not use an SSL + certificate. For additional safety Freighter only works with + websites that provide an SSL certificate. + +

+
); } @@ -234,11 +225,9 @@ export const SignTransaction = () => { ) : ( <> {hwStatus === ShowOverlayStatus.IN_PROGRESS && } -
- - - {t("Confirm Transaction")} - + + + {isExperimentalModeEnabled ? ( { isFeeBump={isFeeBump} isMemoRequired={isMemoRequired} /> - - + + - + { />
-
+ ); }; diff --git a/extension/src/popup/views/UnlockAccount/index.tsx b/extension/src/popup/views/UnlockAccount/index.tsx index 83b141da02..1e91d7493e 100644 --- a/extension/src/popup/views/UnlockAccount/index.tsx +++ b/extension/src/popup/views/UnlockAccount/index.tsx @@ -3,14 +3,14 @@ import get from "lodash/get"; import { useDispatch, useSelector } from "react-redux"; import { useLocation, useHistory } from "react-router-dom"; import { Field, Form, Formik, FieldProps } from "formik"; -import { Button, Input, Link } from "@stellar/design-system"; +import { Button, Heading, Input, Link } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; import { ROUTES } from "popup/constants/routes"; import { openTab } from "popup/helpers/navigate"; import { newTabHref } from "helpers/urls"; import { FormRows, SubmitButtonWrapper } from "popup/basics/Forms"; -import { Header } from "popup/components/Header"; +import { View } from "popup/basics/layout/View"; import { confirmPassword, authErrorSelector, @@ -44,52 +44,56 @@ export const UnlockAccount = () => { }; return ( -
-
-
- {t("A Stellar wallet for every website")} -
- - {({ dirty, isSubmitting, isValid, errors, touched }) => ( -
-
- - - {({ field }: FieldProps) => ( - - )} - - - - - -
-
- )} -
-
+ + + +
+ + {t("A Stellar wallet for every website")} + + + {({ dirty, isSubmitting, isValid, errors, touched }) => ( +
+
+ + + {({ field }: FieldProps) => ( + + )} + + + + + +
+
+ )} +
+
+
+
{t("Want to add another account?")}
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} @@ -103,7 +107,7 @@ export const UnlockAccount = () => { {t("Import using account seed phrase")}
-
-
+ + ); }; diff --git a/extension/src/popup/views/UnlockAccount/styles.scss b/extension/src/popup/views/UnlockAccount/styles.scss index 6e0682f222..a00b9dc92c 100644 --- a/extension/src/popup/views/UnlockAccount/styles.scss +++ b/extension/src/popup/views/UnlockAccount/styles.scss @@ -1,25 +1,7 @@ .UnlockAccount { - padding: var(--popup-vertical-padding) var(--popup--side-padding); - height: var(--popup--height); display: flex; flex-direction: column; justify-content: flex-start; - - &__header { - padding: 5rem 0 3rem; - font-size: 2rem; - line-height: 2.4rem; - color: var(--color-gray-90); - max-width: 17rem; - font-weight: var(--font-weight-medium); - } - - &__import-account { - margin-top: auto; - width: 100%; - text-align: center; - font-size: 1rem; - line-height: 1.5rem; - font-weight: var(--font-weight-regular); - } + gap: 2rem; + padding-top: 1.5rem; } diff --git a/extension/src/popup/views/VerifyAccount/index.tsx b/extension/src/popup/views/VerifyAccount/index.tsx index fb5ed06ae3..f7b01f82fe 100644 --- a/extension/src/popup/views/VerifyAccount/index.tsx +++ b/extension/src/popup/views/VerifyAccount/index.tsx @@ -13,8 +13,8 @@ import { confirmPassword, authErrorSelector, } from "popup/ducks/accountServices"; -import { PopupWrapper } from "popup/basics/PopupWrapper"; import { SubviewHeader } from "popup/components/SubviewHeader"; +import { View } from "popup/basics/layout/View"; import "./styles.scss"; @@ -45,38 +45,43 @@ export const VerifyAccount = ({ }; return ( - + -
- {isApproval - ? t("Enter your account password to verify your account.") - : t( - "Enter your account password to authorize this transaction.", - )}{" "} - You won’t be asked to do this for the next 24 hours. -
{({ dirty, isValid, isSubmitting, errors, touched }) => ( -
- - {({ field }: FieldProps) => ( - - )} - -
+ + +
+ {isApproval + ? t("Enter your account password to verify your account.") + : t( + "Enter your account password to authorize this transaction.", + )}{" "} + You won’t be asked to do this for the next 24 hours. +
+ + + {({ field }: FieldProps) => ( + + )} + +
+ -
+
)}
-
+ ); }; diff --git a/extension/src/popup/views/ViewPublicKey/index.tsx b/extension/src/popup/views/ViewPublicKey/index.tsx index 933e16d414..440d204e3c 100644 --- a/extension/src/popup/views/ViewPublicKey/index.tsx +++ b/extension/src/popup/views/ViewPublicKey/index.tsx @@ -13,7 +13,7 @@ import { truncatedPublicKey, isCustomNetwork } from "helpers/stellar"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { openTab } from "popup/helpers/navigate"; -import { BackButton } from "popup/basics/buttons/BackButton"; +import { View } from "popup/basics/layout/View"; import { accountNameSelector, publicKeySelector, @@ -64,57 +64,51 @@ export const ViewPublicKey = () => { }; return ( - <> -
-
- - {({ errors }) => ( -
- - {isEditingName ? ( - <> -
- - {({ field }: FieldProps) => ( - - )} - -
- + + + {({ errors }) => ( + <> + + + {({ field }: FieldProps) => ( + + )} + + ) : ( - <> -
- {accountName} -
- - )} +
+ {accountName} +
+ ) + } + rightContent={
-
- )} -
- + } + /> + + )} + + +
{
+
+
{!isCustomNetwork(networkDetails) ? ( ) : null}
-
- + + ); }; diff --git a/extension/src/popup/views/ViewPublicKey/styles.scss b/extension/src/popup/views/ViewPublicKey/styles.scss index 023a358fae..6638b847ca 100644 --- a/extension/src/popup/views/ViewPublicKey/styles.scss +++ b/extension/src/popup/views/ViewPublicKey/styles.scss @@ -1,10 +1,4 @@ .ViewPublicKey { - display: flex; - flex-direction: column; - align-items: center; - padding: var(--popup-vertical-padding) var(--popup--side-padding); - height: var(--popup--height); - &__content { display: flex; flex-direction: column; @@ -13,29 +7,13 @@ width: 100%; } - &__header { - height: 2rem; - font-size: 1rem; - line-height: 1.5rem; - color: var(--color-gray-90); - font-weight: var(--font-weight-medium); - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - - &--is-editing { - gap: 1rem; - justify-content: initial; - } - } - &__form { display: flex; flex-flow: row wrap; flex-grow: 1; align-items: center; justify-content: space-between; + width: 100%; * { text-align: center; diff --git a/extension/src/popup/views/Welcome/index.tsx b/extension/src/popup/views/Welcome/index.tsx index 8dcebde708..13310af5a6 100644 --- a/extension/src/popup/views/Welcome/index.tsx +++ b/extension/src/popup/views/Welcome/index.tsx @@ -1,11 +1,10 @@ import React from "react"; -import { Button, Card } from "@stellar/design-system"; +import { Button, Card, Heading } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; import { ROUTES } from "popup/constants/routes"; import { navigateTo } from "popup/helpers/navigate"; -import { FullscreenStyle } from "popup/components/FullscreenStyle"; -import { Header } from "popup/components/Header"; +import { View } from "popup/basics/layout/View"; import "./styles.scss"; @@ -13,56 +12,57 @@ export const Welcome = () => { const { t } = useTranslation(); return ( - <> - -
-
-
-
- {t("Welcome! Is this your first time using Freighter?")} + + + +
+
+ + {t("Welcome! Is this your first time using Freighter?")} +
-
-
-
- -
{t("I’m new!")}
-
- {t("I’m going to need a seed phrase")} -
-
- -
-
-
-
- -
- {t("I’ve done this before")} -
-
- {t("I have my 12 word seed phrase")} -
-
- -
-
+
+
+ +
{t("I’m new!")}
+
+ {t("I’m going to need a seed phrase")} +
+
+ +
+
+
+
+ +
+ {t("I’ve done this before")} +
+
+ {t("I have my 12 word seed phrase")} +
+
+ +
+
+
-
- +
+
); }; diff --git a/extension/src/popup/views/Welcome/styles.scss b/extension/src/popup/views/Welcome/styles.scss index 08e9cbf64d..d861acce34 100644 --- a/extension/src/popup/views/Welcome/styles.scss +++ b/extension/src/popup/views/Welcome/styles.scss @@ -8,11 +8,15 @@ &__column { display: flex; flex-direction: column; - align-content: center; - max-width: 49rem; - height: calc(100vh - var(--header--height)); + height: 100%; width: 100%; - margin: 11.625rem auto; + justify-content: center; + + .Heading { + margin-bottom: 2.5rem !important; + max-width: 30rem; + text-align: center; + } } &__centered-screen { @@ -23,16 +27,6 @@ } &__heading { - &--large { - color: var(--color-gray-90); - font-size: 2.5rem; - line-height: 3rem; - margin-bottom: 2.5rem; - text-align: center; - max-width: 36rem; - font-weight: var(--font-weight-medium); - } - &--small { color: var(--color-gray-90); font-size: 1.125rem; @@ -43,7 +37,9 @@ &__row-screen { display: flex; - justify-content: space-between; + justify-content: center; + gap: 1rem; + flex-wrap: wrap; } &__half-screen { diff --git a/yarn.lock b/yarn.lock index 3ed5504e7f..294d8f1119 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3425,11 +3425,6 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@juggle/resize-observer@^3.3.1": - version "3.3.1" - resolved "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz" - integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw== - "@lavamoat/aa@^3.1.1": version "3.1.2" resolved "https://registry.yarnpkg.com/@lavamoat/aa/-/aa-3.1.2.tgz#3e2c0bbff791204bb4dabe96c2486b0c910e1897" @@ -6060,11 +6055,6 @@ camelize@^1.0.0: resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz" integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= -can-use-dom@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/can-use-dom/-/can-use-dom-0.1.0.tgz" - integrity sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo= - caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz" @@ -6738,11 +6728,6 @@ core-js@^2.4.0, core-js@^2.5.0: resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== -core-js@^3.0.1: - version "3.21.1" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz" - integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig== - core-js@^3.23.3: version "3.27.2" resolved "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz" @@ -11072,11 +11057,6 @@ lodash.sortby@^4.7.0: resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash.throttle@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz" - integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= - lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz" @@ -12966,7 +12946,7 @@ prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" -prop-types@^15.6.1, prop-types@^15.8.1: +prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -14392,26 +14372,6 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967" integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== -simplebar-react@^2.3.6: - version "2.3.6" - resolved "https://registry.npmjs.org/simplebar-react/-/simplebar-react-2.3.6.tgz" - integrity sha512-Igm/MRdt+LQ8edTTzjRoaATfXPPMByuUsVvHQHrkX7SH4jmvL85VshtOVcXFrOBspv9vqQtnIrOq/j9VmRSNDQ== - dependencies: - prop-types "^15.6.1" - simplebar "^5.3.6" - -simplebar@^5.3.6: - version "5.3.6" - resolved "https://registry.npmjs.org/simplebar/-/simplebar-5.3.6.tgz" - integrity sha512-FJUMbV+hNDd/m+1/fvD41TXKd5mSdlI5zgBygkaQIV3SffNbcLhSbJT6ufTs8ZNRLJ6i+qc/KCFMqWmvlGWMhA== - dependencies: - "@juggle/resize-observer" "^3.3.1" - can-use-dom "^0.1.0" - core-js "^3.0.1" - lodash.debounce "^4.0.8" - lodash.memoize "^4.1.2" - lodash.throttle "^4.1.1" - sirv@^1.0.7: version "1.0.19" resolved "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz" From 137a0981b9b21f50b43ae1023a0ab6a727821dbf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:59:17 -0500 Subject: [PATCH 04/10] Bump versions to 5.10.0 (#1057) * docs(): bumping release to 5.10.0 * Empty-Commit --------- Co-authored-by: GitHub Action Co-authored-by: Piyal Basu --- extension/package.json | 2 +- extension/public/static/manifest/v2.json | 4 ++-- extension/public/static/manifest/v3.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extension/package.json b/extension/package.json index fca64d0345..42299ffcc8 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "extension", - "version": "5.9.0", + "version": "5.10.0", "license": "Apache-2.0", "prettier": "@stellar/prettier-config", "scripts": { diff --git a/extension/public/static/manifest/v2.json b/extension/public/static/manifest/v2.json index a926b533a4..09a2459855 100644 --- a/extension/public/static/manifest/v2.json +++ b/extension/public/static/manifest/v2.json @@ -1,7 +1,7 @@ { "name": "Freighter", - "version": "5.9.0", - "version_name": "5.9.0", + "version": "5.10.0", + "version_name": "5.10.0", "description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.", "browser_specific_settings": { "gecko": { diff --git a/extension/public/static/manifest/v3.json b/extension/public/static/manifest/v3.json index 3f24175e33..6491c86d51 100644 --- a/extension/public/static/manifest/v3.json +++ b/extension/public/static/manifest/v3.json @@ -1,7 +1,7 @@ { "name": "Freighter", - "version": "5.9.0", - "version_name": "5.9.0", + "version": "5.10.0", + "version_name": "5.10.0", "description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.", "background": { "service_worker": "background.min.js" From d654045dfc7b9f4a20bd6b71504e12cf651b3b0c Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Thu, 14 Dec 2023 17:36:41 -0500 Subject: [PATCH 05/10] Release/5.11.0 (#1060) * Manage trustline tests (#1058) * fixes a check for network for the undefined case * Added translations * Feature/account migration pt 1 (#1026) * setup account migration routing * Leave feedback screen (#1023) * upgrades to new stellar-sdk beta, refactors to new sdk structure and removes soroban-sdk * use imports from namespaces for stellar-sdk, update test mocks for namespaces * removes stray test logs * Upgrade SDS to v1.0.1 (#1024) * find migratable accounts and nav to mnemonic phrase * navigate to mnemonic phrase after submission * Added translations * update sdk calls * Bump versions to 5.8.0 (#1029) * docs(): bumping release to 5.8.0 * Empty-Commit --------- Co-authored-by: GitHub Action Co-authored-by: Piyal Basu * copy fix * Added translations --------- Co-authored-by: Iveta Co-authored-by: Aristides Staffieri Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action * fixes faulty check for showing soroban rpc url field in network settings * Feature/account migration submit tx (#1038) * confirm migration with happy path * streamline migration process for merge * Added translations * rm logs * add migrate trustline comment * replaceAccount comments * add comment and simplify return * fix types * update comment * rm unneeded param * simplify trustline transfer and account for removing trustlines before merge (#1039) * simplify trustline transfer and account for removing trustlines before merge * calculate minimum sender balance * polish review-migration to use dynamic fees (#1040) * Update layout (#1033) * Onboarding views * Added translations * Unlock account view * Account view * Account history * Settings views * Manage assets views * ViewPublicKey view * Send views * Added translations * Fix failing test * Popover messages updated * Welcome screen * Connect wallet + Send flow fixes * External sign views * Added translations * Remove JS scrolling + layout tweaks * Cleanup * Fix Loader layout * Added translations * Added translations * Feature/migration complete (#1041) * add completed migration page * route to correct view * add some error safeguards and fix a UI issue with unfunded accounts * Added translations * copy fix * Added translations * update migration with new sds layout (#1042) * add completed migration page * route to correct view * add some error safeguards and fix a UI issue with unfunded accounts * Added translations * copy fix * Added translations * update migration with new sds layout * Layout fixes (#1043) * Layout fixes * Adjust footer bottom padding * adjust some ui and make sure we have the user's password (#1044) * adjust some ui and make sure we have the user's password * adjust migration complete top margin * fix loader alignment * switch on Mainnet for migration and show a streamlined password prompt (#1046) * Manage trustline tests * Added translations * Cleanup * Fix tests --------- Co-authored-by: Aristides Staffieri Co-authored-by: Piyal Basu Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action * SignTransaction memo updated + added tests (#1045) * fixes a check for network for the undefined case * Added translations * Feature/account migration pt 1 (#1026) * setup account migration routing * Leave feedback screen (#1023) * upgrades to new stellar-sdk beta, refactors to new sdk structure and removes soroban-sdk * use imports from namespaces for stellar-sdk, update test mocks for namespaces * removes stray test logs * Upgrade SDS to v1.0.1 (#1024) * find migratable accounts and nav to mnemonic phrase * navigate to mnemonic phrase after submission * Added translations * update sdk calls * Bump versions to 5.8.0 (#1029) * docs(): bumping release to 5.8.0 * Empty-Commit --------- Co-authored-by: GitHub Action Co-authored-by: Piyal Basu * copy fix * Added translations --------- Co-authored-by: Iveta Co-authored-by: Aristides Staffieri Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action * fixes faulty check for showing soroban rpc url field in network settings * Feature/account migration submit tx (#1038) * confirm migration with happy path * streamline migration process for merge * Added translations * rm logs * add migrate trustline comment * replaceAccount comments * add comment and simplify return * fix types * update comment * rm unneeded param * simplify trustline transfer and account for removing trustlines before merge (#1039) * simplify trustline transfer and account for removing trustlines before merge * calculate minimum sender balance * polish review-migration to use dynamic fees (#1040) * Update layout (#1033) * Onboarding views * Added translations * Unlock account view * Account view * Account history * Settings views * Manage assets views * ViewPublicKey view * Send views * Added translations * Fix failing test * Popover messages updated * Welcome screen * Connect wallet + Send flow fixes * External sign views * Added translations * Remove JS scrolling + layout tweaks * Cleanup * Fix Loader layout * Added translations * Added translations * Feature/migration complete (#1041) * add completed migration page * route to correct view * add some error safeguards and fix a UI issue with unfunded accounts * Added translations * copy fix * Added translations * update migration with new sds layout (#1042) * add completed migration page * route to correct view * add some error safeguards and fix a UI issue with unfunded accounts * Added translations * copy fix * Added translations * update migration with new sds layout * Layout fixes (#1043) * Layout fixes * Adjust footer bottom padding * SignTransaction memo updated + added tests * Updated memo * Cleanup --------- Co-authored-by: Aristides Staffieri Co-authored-by: Piyal Basu Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action * Swap payment tests (#1047) * fixes a check for network for the undefined case * Added translations * Feature/account migration pt 1 (#1026) * setup account migration routing * Leave feedback screen (#1023) * upgrades to new stellar-sdk beta, refactors to new sdk structure and removes soroban-sdk * use imports from namespaces for stellar-sdk, update test mocks for namespaces * removes stray test logs * Upgrade SDS to v1.0.1 (#1024) * find migratable accounts and nav to mnemonic phrase * navigate to mnemonic phrase after submission * Added translations * update sdk calls * Bump versions to 5.8.0 (#1029) * docs(): bumping release to 5.8.0 * Empty-Commit --------- Co-authored-by: GitHub Action Co-authored-by: Piyal Basu * copy fix * Added translations --------- Co-authored-by: Iveta Co-authored-by: Aristides Staffieri Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action * fixes faulty check for showing soroban rpc url field in network settings * Feature/account migration submit tx (#1038) * confirm migration with happy path * streamline migration process for merge * Added translations * rm logs * add migrate trustline comment * replaceAccount comments * add comment and simplify return * fix types * update comment * rm unneeded param * simplify trustline transfer and account for removing trustlines before merge (#1039) * simplify trustline transfer and account for removing trustlines before merge * calculate minimum sender balance * polish review-migration to use dynamic fees (#1040) * Update layout (#1033) * Onboarding views * Added translations * Unlock account view * Account view * Account history * Settings views * Manage assets views * ViewPublicKey view * Send views * Added translations * Fix failing test * Popover messages updated * Welcome screen * Connect wallet + Send flow fixes * External sign views * Added translations * Remove JS scrolling + layout tweaks * Cleanup * Fix Loader layout * Added translations * Added translations * Feature/migration complete (#1041) * add completed migration page * route to correct view * add some error safeguards and fix a UI issue with unfunded accounts * Added translations * copy fix * Added translations * update migration with new sds layout (#1042) * add completed migration page * route to correct view * add some error safeguards and fix a UI issue with unfunded accounts * Added translations * copy fix * Added translations * update migration with new sds layout * Layout fixes (#1043) * Layout fixes * Adjust footer bottom padding * adjust some ui and make sure we have the user's password (#1044) * adjust some ui and make sure we have the user's password * adjust migration complete top margin * fix loader alignment * Swap payment tests * Fix tests --------- Co-authored-by: Aristides Staffieri Co-authored-by: Piyal Basu Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action * Disable migration (#1059) * disable Account Migration link in Security settings * Added translations --------- Co-authored-by: Iveta Co-authored-by: Aristides Staffieri Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action --- config/jest/setupTests.tsx | 1 + extension/src/background/helpers/migration.ts | 2 +- .../messageListener/popupMessageListener.ts | 12 +- .../src/popup/basics/layout/View/index.tsx | 14 +- .../components/WarningMessages/index.tsx | 60 ++- .../manageAssets/ChooseAsset/index.tsx | 21 +- .../manageAssets/ManageAssetRows/index.tsx | 129 ++---- .../manageAssets/SearchAsset/index.tsx | 26 +- .../manageAssets/TrustlineError/index.tsx | 26 +- .../manageAssets/TrustlineError/styles.scss | 6 + .../SendAmount/AssetSelect/index.tsx | 16 +- .../sendPayment/SendAmount/index.tsx | 17 +- .../SendConfirm/SubmitResult/index.tsx | 16 +- .../SendConfirm/TransactionDetails/index.tsx | 27 +- .../sendPayment/SendSettings/index.tsx | 10 +- .../signTransaction/TransactionInfo/index.tsx | 22 +- extension/src/popup/constants/memoTypes.ts | 5 - .../src/popup/ducks/transactionSubmission.ts | 17 +- .../helpers/__tests__/decodeHash.test.js | 50 +-- .../popup/helpers/checkForSuspiciousAsset.ts | 66 +++ extension/src/popup/helpers/getAssetDomain.ts | 8 + .../src/popup/helpers/getManageAssetXDR.ts | 44 ++ .../src/popup/helpers/horizonGetBestPath.ts | 25 ++ .../src/popup/helpers/parseTransaction.ts | 28 +- extension/src/popup/helpers/searchAsset.ts | 21 + .../src/popup/locales/en/translation.json | 2 +- .../src/popup/locales/pt/translation.json | 2 +- extension/src/popup/views/Security/index.tsx | 10 +- .../src/popup/views/SignTransaction/index.tsx | 5 +- .../views/__tests__/ManageAssets.test.tsx | 420 ++++++++++++++++++ .../views/__tests__/SignTransaction.test.tsx | 154 ++++++- .../src/popup/views/__tests__/Swap.test.tsx | 312 +++++++++++++ 32 files changed, 1331 insertions(+), 243 deletions(-) delete mode 100644 extension/src/popup/constants/memoTypes.ts create mode 100644 extension/src/popup/helpers/checkForSuspiciousAsset.ts create mode 100644 extension/src/popup/helpers/getAssetDomain.ts create mode 100644 extension/src/popup/helpers/getManageAssetXDR.ts create mode 100644 extension/src/popup/helpers/horizonGetBestPath.ts create mode 100644 extension/src/popup/helpers/searchAsset.ts create mode 100644 extension/src/popup/views/__tests__/ManageAssets.test.tsx create mode 100644 extension/src/popup/views/__tests__/Swap.test.tsx diff --git a/config/jest/setupTests.tsx b/config/jest/setupTests.tsx index 4258343d9f..0b4ea6e40d 100755 --- a/config/jest/setupTests.tsx +++ b/config/jest/setupTests.tsx @@ -21,6 +21,7 @@ global.EXPERIMENTAL = false; jest.mock("helpers/metrics", () => ({ registerHandler: () => {}, + emitMetric: () => {}, })); /* eslint-disable react/no-array-index-key */ diff --git a/extension/src/background/helpers/migration.ts b/extension/src/background/helpers/migration.ts index 3c51d087fa..46701d493c 100644 --- a/extension/src/background/helpers/migration.ts +++ b/extension/src/background/helpers/migration.ts @@ -24,7 +24,7 @@ interface MigrateTrustLinesParams { 1. Add the trustline to the destination account 2. Send the entire trustline balance from the source account to the destination account 3. (Optional) If we want to later merge the source account, we need to remove the trustline after sending the balance - + We repeat for every trustline */ diff --git a/extension/src/background/messageListener/popupMessageListener.ts b/extension/src/background/messageListener/popupMessageListener.ts index d9d2fdd591..7dbc1641a8 100644 --- a/extension/src/background/messageListener/popupMessageListener.ts +++ b/extension/src/background/messageListener/popupMessageListener.ts @@ -649,11 +649,11 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { }; const loadAccount = async () => { - /* - The 3.0.0 migration mistakenly sets keyId as a number in older versions. + /* + The 3.0.0 migration mistakenly sets keyId as a number in older versions. For some users, Chrome went right from version ~2.9.x to 3.0.0, which caused them to miss the below fix to the migration. This will fix this issue at load. - + keyId being of type number causes issues downstream: - we need to be able to use String.indexOf to determine if the keyId belongs to a hardware wallet - @stellar/walet-sdk expects a string when dealing unlocking a keystore by keyId @@ -1169,7 +1169,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { ); if (isExperimentalModeEnabled !== currentIsExperimentalModeEnabled) { - /* Disable Mainnet access and automatically switch the user to Futurenet + /* Disable Mainnet access and automatically switch the user to Futurenet if user is enabling experimental mode and vice-versa */ const currentNetworksList = await getNetworksList(); @@ -1431,10 +1431,10 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { 1. We create a new keypair that will be the destination account 2. We send the minimum amount of XLM needed to create the destination acct and also provide enough funds to create necessary trustlines - 3. Replace the old source account with the destination account in redux and in local storage. + 3. Replace the old source account with the destination account in redux and in local storage. When the user refreshes the app, they will already be logged into their new accounts. 4. Migrate the trustlines from the source account to destination - 5. Start an account session with the destination account so the user can start signing tx's with their newly migrated account + 5. Start an account session with the destination account so the user can start signing tx's with their newly migrated account */ for (let i = 0; i < balancesToMigrate.length; i += 1) { diff --git a/extension/src/popup/basics/layout/View/index.tsx b/extension/src/popup/basics/layout/View/index.tsx index 458a3b3f18..a2c0102cde 100644 --- a/extension/src/popup/basics/layout/View/index.tsx +++ b/extension/src/popup/basics/layout/View/index.tsx @@ -91,12 +91,22 @@ const ViewAppHeader: React.FC = ({ ) : (
- + <Title + size="md" + role="heading" + aria-level={2} + data-testid="AppHeaderPageTitle" + > {pageTitle}
{pageSubtitle ? ( -
{pageSubtitle}
+
+ {pageSubtitle} +
) : null}
)} diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index cb45f0ddaf..186dd92c9a 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -38,6 +38,7 @@ import { } from "popup/ducks/accountServices"; import { ROUTES } from "popup/constants/routes"; import { navigateTo } from "popup/helpers/navigate"; +import { getManageAssetXDR } from "popup/helpers/getManageAssetXDR"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { emitMetric } from "helpers/metrics"; import IconShieldCross from "popup/assets/icon-shield-cross.svg"; @@ -288,16 +289,21 @@ export const ScamAssetWarning = ({ if (warningRef.current) { warningRef.current.style.bottom = `-${POPUP_HEIGHT}px`; } - setTimeout(() => { + const timeout = setTimeout(() => { onClose(); + clearTimeout(timeout); }, 300); }; // animate entry useEffect(() => { if (warningRef.current) { - setTimeout(() => { - warningRef.current!.style.bottom = "0"; + const timeout = setTimeout(() => { + // Adding extra check to fix flaky tests + if (warningRef.current) { + warningRef.current.style.bottom = "0"; + } + clearTimeout(timeout); }, 10); } }, [warningRef]); @@ -479,11 +485,22 @@ export const NewAssetWarning = ({ const { isRevocable, isNewAsset, isInvalidDomain } = newAssetFlags; + useEffect( + () => () => { + setIsSubmitting(false); + }, + [], + ); + // animate entry useEffect(() => { if (warningRef.current) { - setTimeout(() => { - warningRef.current!.style.bottom = "0"; + const timeout = setTimeout(() => { + // Adding extra check to fix flaky tests + if (warningRef.current) { + warningRef.current.style.bottom = "0"; + } + clearTimeout(timeout); }, 10); } }, [warningRef]); @@ -492,8 +509,9 @@ export const NewAssetWarning = ({ if (warningRef.current) { warningRef.current.style.bottom = `-${POPUP_HEIGHT}px`; } - setTimeout(() => { + const timeout = setTimeout(() => { onClose(); + clearTimeout(timeout); }, 300); }; @@ -501,19 +519,15 @@ export const NewAssetWarning = ({ setIsSubmitting(true); const server = new Horizon.Server(networkDetails.networkUrl); - const sourceAccount: Account = await server.loadAccount(publicKey); - const transactionXDR = new TransactionBuilder(sourceAccount, { - fee: xlmToStroop(recommendedFee).toFixed(), - networkPassphrase: networkDetails.networkPassphrase, - }) - .addOperation( - Operation.changeTrust({ - asset: new Asset(code, issuer), - }), - ) - .setTimeout(180) - .build() - .toXDR(); + const transactionXDR = await getManageAssetXDR({ + publicKey, + assetCode: code, + assetIssuer: issuer, + addTrustline: true, + server, + recommendedFee, + networkDetails, + }); if (isHardwareWallet) { await dispatch(startHwSign({ transactionXDR, shouldSubmit: true })); @@ -546,10 +560,13 @@ export const NewAssetWarning = ({ }; return ( -
+
-
+
{t("Before You Add This Asset")}
@@ -637,6 +654,7 @@ export const NewAssetWarning = ({ onClick={handleSubmit} type="button" isLoading={isSubmitting} + data-testid="NewAssetWarningAddButton" > {t("Add asset")} diff --git a/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx b/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx index 0b5109a53b..e1d48b6091 100644 --- a/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx +++ b/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx @@ -19,7 +19,7 @@ import { sorobanSelector } from "popup/ducks/soroban"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { View } from "popup/basics/layout/View"; import { getCanonicalFromAsset } from "helpers/stellar"; -import { stellarSdkServer } from "@shared/api/helpers/stellarSdkServer"; +import { getAssetDomain } from "popup/helpers/getAssetDomain"; import { Balances } from "@shared/api/types"; @@ -67,15 +67,12 @@ export const ChooseAsset = ({ balances }: ChooseAssetProps) => { } = sortedBalances[i]; if (code !== "XLM") { - const server = stellarSdkServer(networkUrl); - let domain = ""; if (issuer?.key) { try { // eslint-disable-next-line no-await-in-loop - const acct = await server.loadAccount(issuer.key); - domain = acct.home_domain || ""; + domain = await getAssetDomain(issuer.key, networkUrl); } catch (e) { console.error(e); } @@ -165,7 +162,12 @@ export const ChooseAsset = ({ balances }: ChooseAssetProps) => { <>
- @@ -173,7 +175,12 @@ export const ChooseAsset = ({ balances }: ChooseAssetProps) => { {isSorobanSuported ? (
- diff --git a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx index 0674c5a7cd..0025770c53 100644 --- a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx +++ b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx @@ -1,12 +1,5 @@ import React, { useContext, useState, useEffect } from "react"; -import { - Account, - Asset, - Operation, - StellarToml, - TransactionBuilder, - Networks, -} from "stellar-sdk"; +import { StellarToml, Networks } from "stellar-sdk"; import { useDispatch, useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import { ActionStatus } from "@shared/api/types"; @@ -18,11 +11,9 @@ import { stellarSdkServer } from "@shared/api/helpers/stellarSdkServer"; import { emitMetric } from "helpers/metrics"; import { navigateTo } from "popup/helpers/navigate"; import { useNetworkFees } from "popup/helpers/useNetworkFees"; -import { getApiStellarExpertUrl } from "popup/helpers/account"; import { formatDomain, getCanonicalFromAsset, - xlmToStroop, truncateString, } from "helpers/stellar"; @@ -54,6 +45,8 @@ import { } from "popup/components/WarningMessages"; import { ScamAssetIcon } from "popup/components/account/ScamAssetIcon"; import { SorobanContext } from "popup/SorobanContext"; +import { checkForSuspiciousAsset } from "popup/helpers/checkForSuspiciousAsset"; +import { getManageAssetXDR } from "popup/helpers/getManageAssetXDR"; import "./styles.scss"; @@ -121,25 +114,18 @@ export const ManageAssetRows = ({ assetIssuer: string, addTrustline: boolean, ) => { - const changeParams = addTrustline ? {} : { limit: "0" }; - const sourceAccount: Account = await server.loadAccount(publicKey); const canonicalAsset = getCanonicalFromAsset(assetCode, assetIssuer); - setAssetSubmitting(canonicalAsset); - const transactionXDR = new TransactionBuilder(sourceAccount, { - fee: xlmToStroop(recommendedFee).toFixed(), - networkPassphrase: networkDetails.networkPassphrase, - }) - .addOperation( - Operation.changeTrust({ - asset: new Asset(assetCode, assetIssuer), - ...changeParams, - }), - ) - .setTimeout(180) - .build() - .toXDR(); + const transactionXDR = await getManageAssetXDR({ + publicKey, + assetCode, + assetIssuer, + addTrustline, + server, + recommendedFee, + networkDetails, + }); const trackChangeTrustline = () => { emitMetric( @@ -197,6 +183,13 @@ export const ManageAssetRows = ({ } }; + useEffect( + () => () => { + setAssetSubmitting(""); + }, + [], + ); + // watch submitStatus if used ledger to send transaction useEffect(() => { if (submitStatus === ActionStatus.ERROR) { @@ -205,64 +198,10 @@ export const ManageAssetRows = ({ dispatch(resetSubmission()); navigateTo(ROUTES.account); } - }, [submitStatus, assetSubmitting, dispatch]); + }, [submitStatus, dispatch]); const isBlockedDomain = (domain: string) => blockedDomains.domains[domain]; - const checkForSuspiciousAsset = async ( - code: string, - issuer: string, - domain: string, - ): Promise => { - // check revocable - let isRevocable = false; - try { - const resp = await server.assets().forCode(code).forIssuer(issuer).call(); - isRevocable = resp.records[0] - ? resp.records[0]?.flags?.auth_revocable - : false; - } catch (e) { - console.error(e); - } - - // check if new asset - let isNewAsset = false; - try { - const resp = await fetch( - `${getApiStellarExpertUrl( - networkDetails, - )}/asset/${code}-${issuer}/rating`, - ); - const json = await resp.json(); - const age = json.rating?.age; - if (!age || age <= 3) { - isNewAsset = true; - } - } catch (e) { - console.error(e); - } - - // check domain - let isInvalidDomain = false; - try { - const resp = await StellarToml.Resolver.resolve(domain); - let found = false; - (resp?.CURRENCIES || []).forEach( - (c: { code?: string; issuer?: string }) => { - if (c.code === code && c.issuer === issuer) { - found = true; - } - }, - ); - isInvalidDomain = !found; - } catch (e) { - console.error(e); - isInvalidDomain = true; - } - - return { isRevocable, isNewAsset, isInvalidDomain }; - }; - const handleRowClick = async ( assetRowData = { code: "", @@ -272,11 +211,13 @@ export const ManageAssetRows = ({ }, isTrustlineActive: boolean, ) => { - const resp = await checkForSuspiciousAsset( - assetRowData.code, - assetRowData.issuer, - assetRowData.domain, - ); + const resp = await checkForSuspiciousAsset({ + code: assetRowData.code, + issuer: assetRowData.issuer, + domain: assetRowData.domain, + server, + networkDetails, + }); if (isBlockedDomain(assetRowData.domain) && !isTrustlineActive) { setShowBlockedDomainWarning(true); @@ -359,7 +300,11 @@ export const ManageAssetRows = ({ getTokenBalancesStatus === ActionStatus.PENDING; return ( -
+
{isTrustlineActive || contractId ? t("Remove") : t("Add")} @@ -449,10 +395,15 @@ export const ManageAssetRow = ({ />
- {code} + {code}
-
{formatDomain(domain)}
+
+ {formatDomain(domain)} +
); diff --git a/extension/src/popup/components/manageAssets/SearchAsset/index.tsx b/extension/src/popup/components/manageAssets/SearchAsset/index.tsx index d5b51bd15f..c1398ccaa7 100644 --- a/extension/src/popup/components/manageAssets/SearchAsset/index.tsx +++ b/extension/src/popup/components/manageAssets/SearchAsset/index.tsx @@ -12,7 +12,7 @@ import { ROUTES } from "popup/constants/routes"; import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; import { isCustomNetwork } from "helpers/stellar"; -import { getApiStellarExpertUrl } from "popup/helpers/account"; +import { searchAsset } from "popup/helpers/searchAsset"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { View } from "popup/basics/layout/View"; @@ -82,24 +82,21 @@ export const SearchAsset = () => { const handleSearch = useCallback( debounce(async ({ target: { value: asset } }) => { - let res; if (!asset) { setAssetRows([]); return; } setIsSearching(true); - try { - res = await fetch( - `${getApiStellarExpertUrl(networkDetails)}/asset?search=${asset}`, - ); - } catch (e) { - console.error(e); - setIsSearching(false); - throw new Error(t("Unable to search for assets")); - } - - const resJson = await res.json(); + const resJson = await searchAsset({ + asset, + networkDetails, + onError: (e) => { + console.error(e); + setIsSearching(false); + throw new Error(t("Unable to search for assets")); + }, + }); setIsSearching(false); @@ -140,7 +137,7 @@ export const SearchAsset = () => { setHasNoResults(false); }} > - + @@ -154,6 +151,7 @@ export const SearchAsset = () => { id="asset" placeholder={t("Search for an asset")} {...field} + data-testid="search-asset-input" /> )} diff --git a/extension/src/popup/components/manageAssets/TrustlineError/index.tsx b/extension/src/popup/components/manageAssets/TrustlineError/index.tsx index b18e81a72c..01d9b0548f 100644 --- a/extension/src/popup/components/manageAssets/TrustlineError/index.tsx +++ b/extension/src/popup/components/manageAssets/TrustlineError/index.tsx @@ -12,10 +12,10 @@ import { import { emitMetric } from "helpers/metrics"; import { getResultCodes, RESULT_CODES } from "popup/helpers/parseTransaction"; - -import { METRIC_NAMES } from "popup/constants/metricsNames"; import { View } from "popup/basics/layout/View"; +import { SubviewHeader } from "popup/components/SubviewHeader"; +import { METRIC_NAMES } from "popup/constants/metricsNames"; import { Balances } from "@shared/api/types"; import "./styles.scss"; @@ -83,7 +83,10 @@ const RenderedError = ({ title={t("This asset has a balance")} icon={} /> -

+

{t("This asset has a balance of")} {assetBalance}.{" "} {t("You must have a balance of")} 0{" "} {t("in order to remove an asset.")} @@ -144,16 +147,15 @@ export const TrustlineError = ({ : TRUSTLINE_ERROR_STATES.UNKNOWN_ERROR; return ( - + + -

-
- -
+
+
diff --git a/extension/src/popup/components/manageAssets/TrustlineError/styles.scss b/extension/src/popup/components/manageAssets/TrustlineError/styles.scss index 3b9272f7d6..f2445bd94e 100644 --- a/extension/src/popup/components/manageAssets/TrustlineError/styles.scss +++ b/extension/src/popup/components/manageAssets/TrustlineError/styles.scss @@ -2,6 +2,12 @@ display: flex; flex-direction: column; + &__inset { + display: flex; + flex-direction: column; + gap: 1rem; + } + &__body { flex-grow: 1; } diff --git a/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx b/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx index 961e2ead60..7e919cbdda 100644 --- a/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx +++ b/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx @@ -95,10 +95,14 @@ export function PathPayAssetSelect({
- + {source ? "From" : "To"} - + {truncateLongAssetCode(assetCode)} {" "}
- + {balance && balance !== "0" ? balance : ""}{" "} {truncateLongAssetCode(assetCode)} diff --git a/extension/src/popup/components/sendPayment/SendAmount/index.tsx b/extension/src/popup/components/sendPayment/SendAmount/index.tsx index dde910ffb3..68f1ab698b 100644 --- a/extension/src/popup/components/sendPayment/SendAmount/index.tsx +++ b/extension/src/popup/components/sendPayment/SendAmount/index.tsx @@ -74,13 +74,15 @@ const ConversionRate = ({ const { t } = useTranslation(); return ( -
+
{loading ? ( - +
+ +
) : ( <> {destAmount ? ( - + 1 {source} ≈{" "} {new BigNumber(destAmount) .div(new BigNumber(sourceAmount)) @@ -290,8 +292,13 @@ export const SendAmount = ({ ]); // for swaps we're loading and choosing the default destinationAsset here + // also, need to check if both source and destination are native useEffect(() => { - if (isSwap && !destinationAsset) { + if ( + isSwap && + (!destinationAsset || + (destinationAsset === "native" && asset === "native")) + ) { let defaultDestAsset; // if pre-chosen source asset (eg. from AssetDetails) not XLM, default dest asset to XLM @@ -316,6 +323,7 @@ export const SendAmount = ({ destinationAsset, accountBalances, formik.values.asset, + asset, ]); const getAmountFontSize = () => { @@ -447,6 +455,7 @@ export const SendAmount = ({ calculateAvailBalance(formik.values.asset), ); }} + data-testid="SendAmountSetMax" > {t("SET MAX")} diff --git a/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx b/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx index 5cdf0cf0e2..0b2eec42a2 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx +++ b/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx @@ -54,14 +54,14 @@ const SwapAssetsIcon = ({ code={source.code} issuerKey={source.issuer} /> - {source.code} + {source.code} - {dest.code} + {dest.code}
); }; @@ -189,7 +189,10 @@ export const SubmitSuccess = ({ viewDetails }: { viewDetails: () => void }) => { } >
-
+
{formatAmount(amount)} {sourceAsset.code}
@@ -212,7 +215,12 @@ export const SubmitSuccess = ({ viewDetails }: { viewDetails: () => void }) => {
-
-
+
{formatAmount(sourceAmount)} {sourceAsset.code}
@@ -124,7 +127,10 @@ const TwoAssetCard = ({ {destAsset.code}
-
+
{formatAmount(new BigNumber(destAmount).toFixed())} {destAsset.code}
@@ -426,7 +432,7 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { return ( <> {hwStatus === ShowOverlayStatus.IN_PROGRESS && } - + {submission.submitStatus === ActionStatus.PENDING && (
@@ -515,7 +521,10 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { {(isPathPayment || isSwap) && (
{t("Conversion rate")}
-
+
1 {sourceAsset.code} /{" "} {getConversionRate(amount, destinationAmount).toFixed(2)}{" "} {destAsset.code} @@ -524,7 +533,10 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { )}
{t("Transaction fee")}
-
+
{transactionFee} XLM
@@ -552,7 +564,10 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { {isSwap && (
{t("Minimum Received")}
-
+
{computeDestMinWithSlippage( allowedSlippage, destinationAmount, diff --git a/extension/src/popup/components/sendPayment/SendSettings/index.tsx b/extension/src/popup/components/sendPayment/SendSettings/index.tsx index d99cd8480e..b8a4334873 100644 --- a/extension/src/popup/components/sendPayment/SendSettings/index.tsx +++ b/extension/src/popup/components/sendPayment/SendSettings/index.tsx @@ -134,6 +134,7 @@ export const SendSettings = ({ initialValues={{ memo }} onSubmit={(values) => { dispatch(saveMemo(values.memo)); + goToReview(); }} > {({ submitForm }) => ( @@ -177,7 +178,9 @@ export const SendSettings = ({ handleTxFeeNav(); }} > - {transactionFee} XLM + + {transactionFee} XLM +
@@ -224,7 +227,9 @@ export const SendSettings = ({ handleSlippageNav(); }} > - {allowedSlippage}% + + {allowedSlippage}% +
@@ -280,7 +285,6 @@ export const SendSettings = ({ isFullWidth type="submit" variant="secondary" - onClick={goToReview} data-testid="send-settings-btn-continue" > {t("Review")} {isSwap ? t("Swap") : t("Send")} diff --git a/extension/src/popup/components/signTransaction/TransactionInfo/index.tsx b/extension/src/popup/components/signTransaction/TransactionInfo/index.tsx index 85a6b6d733..3d066ef478 100644 --- a/extension/src/popup/components/signTransaction/TransactionInfo/index.tsx +++ b/extension/src/popup/components/signTransaction/TransactionInfo/index.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { MemoType } from "stellar-sdk"; import { Icon, IconButton } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; @@ -10,11 +11,19 @@ const MemoDisplay = ({ memo, isMemoRequired, }: { - memo: string; + memo: { value: string; type: MemoType }; isMemoRequired: boolean; }) => { const { t } = useTranslation(); + const mapMemoLabel: any = { + id: "MEMO_ID", + hash: "MEMO_HASH", + text: "MEMO_TEXT", + return: "MEMO_RETURN", + none: "MEMO_NONE", + }; + if (isMemoRequired) { return ( ); } + if (memo) { - return {`${memo} (MEMO_TEXT)`}; + return ( + {`${memo.value} (${ + mapMemoLabel[memo.type] + })`} + ); } return null; @@ -37,7 +51,7 @@ interface TransactionInfoProps { _sequence?: string; isFeeBump?: boolean; isMemoRequired: boolean; - memo?: string; + memo?: { value: string; type: MemoType }; } export const TransactionInfo = ({ @@ -50,7 +64,7 @@ export const TransactionInfo = ({ const { t } = useTranslation(); return ( -
+
{_fee ? (
diff --git a/extension/src/popup/constants/memoTypes.ts b/extension/src/popup/constants/memoTypes.ts deleted file mode 100644 index 5ee37d5c35..0000000000 --- a/extension/src/popup/constants/memoTypes.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum MEMO_TYPES { - MEMO_ID = "memoId", - MEMO_HASH = "memoHash", - MEMO_TEXT = "memoText", -} diff --git a/extension/src/popup/ducks/transactionSubmission.ts b/extension/src/popup/ducks/transactionSubmission.ts index c916f3dcd4..5f0ad60d6a 100644 --- a/extension/src/popup/ducks/transactionSubmission.ts +++ b/extension/src/popup/ducks/transactionSubmission.ts @@ -1,5 +1,4 @@ import { - Asset, Horizon, Keypair, Memo, @@ -44,10 +43,11 @@ import { NetworkDetails } from "@shared/constants/stellar"; import TransportWebUSB from "@ledgerhq/hw-transport-webusb"; import LedgerApi from "@ledgerhq/hw-app-str"; -import { getAssetFromCanonical, getCanonicalFromAsset } from "helpers/stellar"; +import { getCanonicalFromAsset } from "helpers/stellar"; import { METRICS_DATA } from "constants/localStorageTypes"; import { MetricsData, emitMetric } from "helpers/metrics"; import { METRIC_NAMES } from "popup/constants/metricsNames"; +import { horizonGetBestPath } from "popup/helpers/horizonGetBestPath"; import { SorobanContextInterface } from "popup/SorobanContext"; import { getTokenBalances, resetSorobanTokensStatus } from "./soroban"; @@ -332,15 +332,12 @@ export const getBestPath = createAsyncThunk< "getBestPath", async ({ amount, sourceAsset, destAsset, networkDetails }, thunkApi) => { try { - const server = new Horizon.Server(networkDetails.networkUrl); - const builder = server.strictSendPaths( - getAssetFromCanonical(sourceAsset) as Asset, + return await horizonGetBestPath({ amount, - [getAssetFromCanonical(destAsset)] as Asset[], - ); - - const paths = await builder.call(); - return paths.records[0]; + sourceAsset, + destAsset, + networkDetails, + }); } catch (e) { return thunkApi.rejectWithValue({ errorMessage: e.message || e, diff --git a/extension/src/popup/helpers/__tests__/decodeHash.test.js b/extension/src/popup/helpers/__tests__/decodeHash.test.js index 8be51fd639..cc063042b8 100644 --- a/extension/src/popup/helpers/__tests__/decodeHash.test.js +++ b/extension/src/popup/helpers/__tests__/decodeHash.test.js @@ -1,37 +1,37 @@ -import { MEMO_TYPES } from "popup/constants/memoTypes"; +import { Memo } from "stellar-sdk"; import { decodeMemo } from "../parseTransaction"; describe("decodeMemo", () => { it("should return a memo id", () => { - const MEMO = { - _switch: { - name: MEMO_TYPES.MEMO_ID, - }, - _value: { - low: 999, - }, - }; + const MEMO = Memo.id("999"); - expect(decodeMemo(MEMO)).toBe(999); + expect(decodeMemo(MEMO).value).toBe("999"); + expect(decodeMemo(MEMO).type).toBe("id"); }); it("should decode and return a hashed memo", () => { - const MEMO = { - _switch: { - name: MEMO_TYPES.MEMO_HASH, - }, - _value: [84, 104, 105], - }; + const MEMO = Memo.hash( + "e98869bba8bce08c10b78406202127f3888c25454cd37b02600862452751f526", + ); - expect(decodeMemo(MEMO)).toBe("546869"); + expect(decodeMemo(MEMO).value).toBe( + "e98869bba8bce08c10b78406202127f3888c25454cd37b02600862452751f526", + ); + expect(decodeMemo(MEMO).type).toBe("hash"); }); - it("should decode and return a memo", () => { - const MEMO = { - _switch: { - name: MEMO_TYPES.MEMO_TEXT, - }, - _value: [97, 115, 100, 102], - }; + it("should decode and return a return memo", () => { + const MEMO = Memo.return( + "e98869bba8bce08c10b78406202127f3888c25454cd37b02600862452751f526", + ); - expect(decodeMemo(MEMO)).toBe("asdf"); + expect(decodeMemo(MEMO).value).toBe( + "e98869bba8bce08c10b78406202127f3888c25454cd37b02600862452751f526", + ); + expect(decodeMemo(MEMO).type).toBe("return"); + }); + it("should decode and return a text memo", () => { + const MEMO = Memo.text("asdf"); + + expect(decodeMemo(MEMO).value).toBe("asdf"); + expect(decodeMemo(MEMO).type).toBe("text"); }); }); diff --git a/extension/src/popup/helpers/checkForSuspiciousAsset.ts b/extension/src/popup/helpers/checkForSuspiciousAsset.ts new file mode 100644 index 0000000000..c1bc3ca2e4 --- /dev/null +++ b/extension/src/popup/helpers/checkForSuspiciousAsset.ts @@ -0,0 +1,66 @@ +import { Horizon, StellarToml } from "stellar-sdk"; +import { NetworkDetails } from "@shared/constants/stellar"; +import { NewAssetFlags } from "popup/components/manageAssets/ManageAssetRows"; +import { getApiStellarExpertUrl } from "popup/helpers/account"; + +export const checkForSuspiciousAsset = async ({ + code, + issuer, + domain, + server, + networkDetails, +}: { + code: string; + issuer: string; + domain: string; + server: Horizon.Server; + networkDetails: NetworkDetails; +}): Promise => { + // check revocable + let isRevocable = false; + try { + const resp = await server.assets().forCode(code).forIssuer(issuer).call(); + isRevocable = resp.records[0] + ? resp.records[0]?.flags?.auth_revocable + : false; + } catch (e) { + console.error(e); + } + + // check if new asset + let isNewAsset = false; + try { + const resp = await fetch( + `${getApiStellarExpertUrl( + networkDetails, + )}/asset/${code}-${issuer}/rating`, + ); + const json = await resp.json(); + const age = json.rating?.age; + if (!age || age <= 3) { + isNewAsset = true; + } + } catch (e) { + console.error(e); + } + + // check domain + let isInvalidDomain = false; + try { + const resp = await StellarToml.Resolver.resolve(domain); + let found = false; + (resp?.CURRENCIES || []).forEach( + (c: { code?: string; issuer?: string }) => { + if (c.code === code && c.issuer === issuer) { + found = true; + } + }, + ); + isInvalidDomain = !found; + } catch (e) { + console.error(e); + isInvalidDomain = true; + } + + return { isRevocable, isNewAsset, isInvalidDomain }; +}; diff --git a/extension/src/popup/helpers/getAssetDomain.ts b/extension/src/popup/helpers/getAssetDomain.ts new file mode 100644 index 0000000000..230c738529 --- /dev/null +++ b/extension/src/popup/helpers/getAssetDomain.ts @@ -0,0 +1,8 @@ +import { stellarSdkServer } from "@shared/api/helpers/stellarSdkServer"; + +export const getAssetDomain = async (issuerKey: string, networkUrl: string) => { + const server = stellarSdkServer(networkUrl); + const acct = await server.loadAccount(issuerKey); + + return acct.home_domain || ""; +}; diff --git a/extension/src/popup/helpers/getManageAssetXDR.ts b/extension/src/popup/helpers/getManageAssetXDR.ts new file mode 100644 index 0000000000..b5ab538790 --- /dev/null +++ b/extension/src/popup/helpers/getManageAssetXDR.ts @@ -0,0 +1,44 @@ +import { + Account, + Asset, + Horizon, + Operation, + TransactionBuilder, +} from "stellar-sdk"; +import { NetworkDetails } from "@shared/constants/stellar"; +import { xlmToStroop } from "helpers/stellar"; + +export const getManageAssetXDR = async ({ + publicKey, + assetCode, + assetIssuer, + addTrustline, + server, + recommendedFee, + networkDetails, +}: { + publicKey: string; + assetCode: string; + assetIssuer: string; + addTrustline: boolean; + server: Horizon.Server; + recommendedFee: string; + networkDetails: NetworkDetails; +}) => { + const changeParams = addTrustline ? {} : { limit: "0" }; + const sourceAccount: Account = await server.loadAccount(publicKey); + + return new TransactionBuilder(sourceAccount, { + fee: xlmToStroop(recommendedFee).toFixed(), + networkPassphrase: networkDetails.networkPassphrase, + }) + .addOperation( + Operation.changeTrust({ + asset: new Asset(assetCode, assetIssuer), + ...changeParams, + }), + ) + .setTimeout(180) + .build() + .toXDR(); +}; diff --git a/extension/src/popup/helpers/horizonGetBestPath.ts b/extension/src/popup/helpers/horizonGetBestPath.ts new file mode 100644 index 0000000000..97a796924d --- /dev/null +++ b/extension/src/popup/helpers/horizonGetBestPath.ts @@ -0,0 +1,25 @@ +import { Asset, Horizon } from "stellar-sdk"; +import { getAssetFromCanonical } from "helpers/stellar"; +import { NetworkDetails } from "@shared/constants/stellar"; + +export const horizonGetBestPath = async ({ + amount, + sourceAsset, + destAsset, + networkDetails, +}: { + amount: string; + sourceAsset: string; + destAsset: string; + networkDetails: NetworkDetails; +}) => { + const server = new Horizon.Server(networkDetails.networkUrl); + const builder = server.strictSendPaths( + getAssetFromCanonical(sourceAsset) as Asset, + amount, + [getAssetFromCanonical(destAsset)] as Asset[], + ); + + const paths = await builder.call(); + return paths.records[0]; +}; diff --git a/extension/src/popup/helpers/parseTransaction.ts b/extension/src/popup/helpers/parseTransaction.ts index 992d13816b..05faf97141 100644 --- a/extension/src/popup/helpers/parseTransaction.ts +++ b/extension/src/popup/helpers/parseTransaction.ts @@ -1,22 +1,26 @@ +import { Memo, MemoType } from "stellar-sdk"; import buffer from "buffer"; import get from "lodash/get"; -import { MEMO_TYPES } from "popup/constants/memoTypes"; - import { ErrorMessage } from "@shared/api/types"; -export const decodeMemo = (memo: {}) => { - const memoType = get(memo, "_switch.name", ""); +export const decodeMemo = (memo: any): { value: string; type: MemoType } => { + const _memo = memo as Memo; - if (memoType === MEMO_TYPES.MEMO_ID) { - return get(memo, "_value.low", ""); - } - const decodeMethod = memoType === MEMO_TYPES.MEMO_HASH ? "hex" : "utf-8"; - const memoValue = get(memo, "_value", ""); - if (memoValue) { - return buffer.Buffer.from(memoValue).toString(decodeMethod); + if (_memo.type === "id") { + return { value: _memo.value as string, type: _memo.type }; } - return ""; + + const decodeMethod = ["hash", "return"].includes(_memo.type) + ? "hex" + : "utf-8"; + + return { + value: _memo.value + ? buffer.Buffer.from(_memo.value).toString(decodeMethod) + : "", + type: _memo.type, + }; }; /* eslint-disable camelcase */ diff --git a/extension/src/popup/helpers/searchAsset.ts b/extension/src/popup/helpers/searchAsset.ts new file mode 100644 index 0000000000..63d1056c7a --- /dev/null +++ b/extension/src/popup/helpers/searchAsset.ts @@ -0,0 +1,21 @@ +import { NetworkDetails } from "@shared/constants/stellar"; +import { getApiStellarExpertUrl } from "popup/helpers/account"; + +export const searchAsset = async ({ + asset, + networkDetails, + onError, +}: { + asset: any; + networkDetails: NetworkDetails; + onError: (e: any) => void; +}) => { + try { + const res = await fetch( + `${getApiStellarExpertUrl(networkDetails)}/asset?search=${asset}`, + ); + return await res.json(); + } catch (e) { + return onError(e); + } +}; diff --git a/extension/src/popup/locales/en/translation.json b/extension/src/popup/locales/en/translation.json index bb8cb2d121..da77242e11 100644 --- a/extension/src/popup/locales/en/translation.json +++ b/extension/src/popup/locales/en/translation.json @@ -8,7 +8,6 @@ "About": "About", "account": "account", "Account": "Account", - "Account migration": "Account migration", "Account Migration": "Account Migration", "Account minimum balance is too low": "Account minimum balance is too low", "Account not available": "Account not available", @@ -449,6 +448,7 @@ "Transaction Rejected": "Transaction Rejected.", "Transaction sequence number": "Transaction sequence number", "Transactions": "Transactions", + "Trustline Error": "Trustline Error", "trustlines": "trustlines", "Trustor": "Trustor", "Unable to connect to": "Unable to connect to", diff --git a/extension/src/popup/locales/pt/translation.json b/extension/src/popup/locales/pt/translation.json index 45011aeab7..26e68ba85e 100644 --- a/extension/src/popup/locales/pt/translation.json +++ b/extension/src/popup/locales/pt/translation.json @@ -8,7 +8,6 @@ "About": "About", "account": "account", "Account": "Account", - "Account migration": "Account migration", "Account Migration": "Account Migration", "Account minimum balance is too low": "Account minimum balance is too low", "Account not available": "Account not available", @@ -449,6 +448,7 @@ "Transaction Rejected": "Transaction Rejected.", "Transaction sequence number": "Transaction sequence number", "Transactions": "Transactions", + "Trustline Error": "Trustline Error", "trustlines": "trustlines", "Trustor": "Trustor", "Unable to connect to": "Unable to connect to", diff --git a/extension/src/popup/views/Security/index.tsx b/extension/src/popup/views/Security/index.tsx index c56855a077..9690245a15 100644 --- a/extension/src/popup/views/Security/index.tsx +++ b/extension/src/popup/views/Security/index.tsx @@ -3,15 +3,15 @@ import { useTranslation } from "react-i18next"; import { ROUTES } from "popup/constants/routes"; -import { newTabHref } from "helpers/urls"; -import { openTab } from "popup/helpers/navigate"; +// import { newTabHref } from "helpers/urls"; +// import { openTab } from "popup/helpers/navigate"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { View } from "popup/basics/layout/View"; import { ListNavLink, - ListNavButtonLink, + // ListNavButtonLink, ListNavLinkWrapper, } from "popup/basics/ListNavLink"; @@ -35,13 +35,13 @@ export const Security = () => { {t("Manage connected apps")} - { openTab(newTabHref(ROUTES.accountMigration)); }} > {t("Account migration")} - + */} diff --git a/extension/src/popup/views/SignTransaction/index.tsx b/extension/src/popup/views/SignTransaction/index.tsx index 8f8d7dbb21..1d21572b21 100644 --- a/extension/src/popup/views/SignTransaction/index.tsx +++ b/extension/src/popup/views/SignTransaction/index.tsx @@ -106,7 +106,9 @@ export const SignTransaction = () => { _memo = transaction.memo; } - const memo = decodeMemo(_memo); + const decodedMemo = decodeMemo(_memo); + + const memo = decodedMemo?.value; let accountToSign = _accountToSign; const { @@ -321,6 +323,7 @@ export const SignTransaction = () => { _sequence={_sequence} isFeeBump={isFeeBump} isMemoRequired={isMemoRequired} + memo={memo ? decodedMemo : undefined} /> diff --git a/extension/src/popup/views/__tests__/ManageAssets.test.tsx b/extension/src/popup/views/__tests__/ManageAssets.test.tsx new file mode 100644 index 0000000000..9a700a2b41 --- /dev/null +++ b/extension/src/popup/views/__tests__/ManageAssets.test.tsx @@ -0,0 +1,420 @@ +import React from "react"; +import { + render, + waitFor, + fireEvent, + screen, + within, +} from "@testing-library/react"; +import BigNumber from "bignumber.js"; + +import * as ApiInternal from "@shared/api/internal"; +import * as UseNetworkFees from "popup/helpers/useNetworkFees"; +import { + TESTNET_NETWORK_DETAILS, + DEFAULT_NETWORKS, +} from "@shared/constants/stellar"; +import { Balances } from "@shared/api/types"; +import { createMemoryHistory } from "history"; + +import { APPLICATION_STATE as ApplicationState } from "@shared/constants/applicationState"; +import { ROUTES } from "popup/constants/routes"; +import * as AssetDomain from "popup/helpers/getAssetDomain"; +import * as CheckSuspiciousAsset from "popup/helpers/checkForSuspiciousAsset"; +import * as ManageAssetXDR from "popup/helpers/getManageAssetXDR"; +import * as SearchAsset from "popup/helpers/searchAsset"; +import { + AssetSelectType, + initialState as transactionSubmissionInitialState, +} from "popup/ducks/transactionSubmission"; + +import { Wrapper, mockAccounts } from "../../__testHelpers__"; +import { ManageAssets } from "../ManageAssets"; + +const mockXDR = + "AAAAAgAAAADaBSz5rQFDZHNdV8//w/Yiy11vE1ZxGJ8QD8j7HUtNEwAAAGQAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADaBSz5rQFDZHNdV8//w/Yiy11vE1ZxGJ8QD8j7HUtNEwAAAAAAAAAAAvrwgAAAAAAAAAABHUtNEwAAAEBY/jSiXJNsA2NpiXrOi6Ll6RiIY7v8QZEEZviM8HmmzeI4FBP9wGZm7YMorQue+DK9KI5BEXDt3hi0VOA9gD8A"; + +const manageAssetsMockBalances = { + balances: ({ + "USDC:GCK3D3V2XNLLKRFGFFFDEJXA4O2J4X36HET2FE446AV3M4U7DPHO3PEM": { + token: { + code: "USDC", + issuer: { + key: "GCK3D3V2XNLLKRFGFFFDEJXA4O2J4X36HET2FE446AV3M4U7DPHO3PEM", + }, + }, + total: new BigNumber("111"), + available: new BigNumber("111"), + }, + native: { + token: { type: "native", code: "XLM" }, + total: new BigNumber("222"), + available: new BigNumber("222"), + }, + "SRT:GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B": { + token: { + code: "SRT", + issuer: { + key: "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B", + }, + }, + total: new BigNumber("0"), + available: new BigNumber("0"), + }, + } as any) as Balances, + isFunded: true, + subentryCount: 1, +}; + +jest + .spyOn(ApiInternal, "getAccountBalances") + .mockImplementation(() => Promise.resolve(manageAssetsMockBalances)); + +jest + .spyOn(AssetDomain, "getAssetDomain") + .mockImplementation((issuerKey: string) => { + let domain = ""; + + switch (issuerKey) { + case "GCK3D3V2XNLLKRFGFFFDEJXA4O2J4X36HET2FE446AV3M4U7DPHO3PEM": + domain = "circle.io"; + break; + case "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B": + domain = "testanchor.stellar.org"; + break; + default: + domain = "malicious.domain"; + } + + return Promise.resolve(domain); + }); + +jest + .spyOn(CheckSuspiciousAsset, "checkForSuspiciousAsset") + .mockImplementation(({ issuer }: { issuer: string }) => { + let isRevocable = false; + let isNewAsset = false; + let isInvalidDomain = false; + + if (issuer === "GBFJZSHWOMYS6U73NXQRRD4JX6TZNWEAFII6Z5INGWVJ2VCQ2K4NQMHP") { + isRevocable = true; + isNewAsset = true; + isInvalidDomain = true; + } + + return Promise.resolve({ isRevocable, isNewAsset, isInvalidDomain }); + }); + +jest + .spyOn(ManageAssetXDR, "getManageAssetXDR") + .mockImplementation(() => Promise.resolve(mockXDR)); + +jest.spyOn(ApiInternal, "signFreighterTransaction").mockImplementation(() => + Promise.resolve({ + signedTransaction: mockXDR, + }), +); + +jest + .spyOn(ApiInternal, "submitFreighterTransaction") + .mockImplementation(({ networkDetails }) => { + if (networkDetails.networkName === "Test Net Reject") { + return Promise.reject(Error("Request failed")); + } + + return Promise.resolve({}); + }); + +jest.spyOn(UseNetworkFees, "useNetworkFees").mockImplementation(() => ({ + recommendedFee: "0.00001", + networkCongestion: UseNetworkFees.NetworkCongestion.MEDIUM, +})); + +jest.spyOn(SearchAsset, "searchAsset").mockImplementation(({ asset }) => { + if (asset === "NEW") { + return Promise.resolve({ + _embedded: { + records: [ + { + asset: + "NEW-GDERVKOZAJS65FCY4EGCQEWV6PRCPZZHXLM37PIRCX6DG7EZVANMHXFY", + domain: "new.domain.com", + }, + ], + }, + }); + } + + // Malicious + return Promise.resolve({ + _embedded: { + records: [ + { + asset: "BAD-GBFJZSHWOMYS6U73NXQRRD4JX6TZNWEAFII6Z5INGWVJ2VCQ2K4NQMHP", + domain: "bad.domain.com", + flags: { + auth_required: true, + auth_revocable: true, + auth_immutable: false, + auth_clawback_enabled: false, + }, + }, + ], + }, + }); +}); + +const mockHistoryGetter = jest.fn(); +jest.mock("popup/constants/history", () => ({ + get history() { + return mockHistoryGetter(); + }, +})); + +jest.mock("stellar-sdk", () => { + const original = jest.requireActual("stellar-sdk"); + return { + ...original, + Server: class { + loadAccount() { + return { + sequenceNumber: () => 1, + accountId: () => publicKey, + incrementSequenceNumber: () => {}, + }; + } + }, + }; +}); + +const publicKey = "GCXRLIZUQNZ3YYJDGX6Z445P7FG5WXT7UILBO5CFIYYM7Z7YTIOELC6O"; +const history = createMemoryHistory(); + +const initView = async (rejectTxn: boolean = false) => { + history.push(ROUTES.manageAssets); + mockHistoryGetter.mockReturnValue(history); + + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId("choose-asset")).toBeDefined(); + }); +}; + +describe("Manage assets", () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + it("renders manage assets view initial state", async () => { + await initView(); + + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Choose Asset", + ); + + const addedTrustlines = screen.queryAllByTestId("ManageAssetRow"); + + expect(addedTrustlines.length).toBe(2); + expect( + within(addedTrustlines[0]).getByTestId("ManageAssetCode"), + ).toHaveTextContent("USDC"); + expect( + within(addedTrustlines[0]).getByTestId("ManageAssetDomain"), + ).toHaveTextContent("circle.io"); + + expect( + within(addedTrustlines[1]).getByTestId("ManageAssetCode"), + ).toHaveTextContent("SRT"); + expect( + within(addedTrustlines[1]).getByTestId("ManageAssetDomain"), + ).toHaveTextContent("testanchor.stellar.org"); + + expect(screen.getByTestId("ChooseAssetAddAssetButton")).toBeEnabled(); + expect( + screen.getByTestId("ChooseAssetAddSorobanTokenButton"), + ).toBeEnabled(); + }); + + it("add asset", async () => { + await initView(); + + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Choose Asset", + ); + + const addButton = screen.getByTestId("ChooseAssetAddAssetButton"); + expect(addButton).toBeEnabled(); + await fireEvent.click(addButton); + + await waitFor(() => { + screen.getByTestId("search-asset"); + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Choose Asset", + ); + + const searchInput = screen.getByTestId("search-asset-input"); + fireEvent.change(searchInput, { target: { value: "NEW" } }); + expect(searchInput).toHaveValue("NEW"); + }); + + await waitFor(async () => { + const addedTrustlines = screen.queryAllByTestId("ManageAssetRow"); + + expect(addedTrustlines.length).toBe(1); + expect( + within(addedTrustlines[0]).getByTestId("ManageAssetCode"), + ).toHaveTextContent("NEW"); + expect( + within(addedTrustlines[0]).getByTestId("ManageAssetDomain"), + ).toHaveTextContent("new.domain.com"); + + const addAssetButton = within(addedTrustlines[0]).getByTestId( + "ManageAssetRowButton", + ); + + expect(addAssetButton).toHaveTextContent("Add"); + expect(addAssetButton).toBeEnabled(); + await fireEvent.click(addAssetButton); + }); + + const lastRoute = history.entries.pop(); + expect(lastRoute?.pathname).toBe("/account"); + }); + + it("remove asset", async () => { + await initView(); + + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Choose Asset", + ); + + const addedTrustlines = screen.queryAllByTestId("ManageAssetRow"); + const removeButton = within(addedTrustlines[1]).getByTestId( + "ManageAssetRowButton", + ); + + await waitFor(async () => { + expect(removeButton).toHaveTextContent("Remove"); + expect(removeButton).toBeEnabled(); + await fireEvent.click(removeButton); + }); + + const lastRoute = history.entries.pop(); + expect(lastRoute?.pathname).toBe("/account"); + }); + + it("show error view when removing asset with balance", async () => { + await initView(true); + + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Choose Asset", + ); + + const addedTrustlines = screen.queryAllByTestId("ManageAssetRow"); + const removeButton = within(addedTrustlines[0]).getByTestId( + "ManageAssetRowButton", + ); + + await waitFor(async () => { + expect(removeButton).toBeEnabled(); + await fireEvent.click(removeButton); + }); + + await waitFor(() => { + screen.getByTestId("trustline-error-view"); + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Trustline Error", + ); + }); + }); + + it("show warning when adding malicious asset", async () => { + await initView(); + + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Choose Asset", + ); + + const addButton = screen.getByTestId("ChooseAssetAddAssetButton"); + expect(addButton).toBeEnabled(); + await fireEvent.click(addButton); + + await waitFor(() => { + screen.getByTestId("search-asset"); + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Choose Asset", + ); + + const searchInput = screen.getByTestId("search-asset-input"); + fireEvent.change(searchInput, { target: { value: "BAD" } }); + expect(searchInput).toHaveValue("BAD"); + }); + + await waitFor(async () => { + const addedTrustlines = screen.queryAllByTestId("ManageAssetRow"); + + expect(addedTrustlines.length).toBe(1); + expect( + within(addedTrustlines[0]).getByTestId("ManageAssetCode"), + ).toHaveTextContent("BAD"); + expect( + within(addedTrustlines[0]).getByTestId("ManageAssetDomain"), + ).toHaveTextContent("bad.domain.com"); + + const addAssetButton = within(addedTrustlines[0]).getByTestId( + "ManageAssetRowButton", + ); + + expect(addAssetButton).toHaveTextContent("Add"); + expect(addAssetButton).toBeEnabled(); + await fireEvent.click(addAssetButton); + }); + + await waitFor(async () => { + const warning = screen.getByTestId("NewAssetWarning"); + expect(screen.getByTestId("NewAssetWarningTitle")).toHaveTextContent( + "Before You Add This Asset", + ); + + const warningAddButton = within(warning).getByTestId( + "NewAssetWarningAddButton", + ); + expect(warningAddButton).toBeEnabled(); + await fireEvent.click(warningAddButton); + }); + + const lastRoute = history.entries.pop(); + expect(lastRoute?.pathname).toBe("/account"); + }); +}); diff --git a/extension/src/popup/views/__tests__/SignTransaction.test.tsx b/extension/src/popup/views/__tests__/SignTransaction.test.tsx index bb427611d4..fb5f7bbe67 100644 --- a/extension/src/popup/views/__tests__/SignTransaction.test.tsx +++ b/extension/src/popup/views/__tests__/SignTransaction.test.tsx @@ -1,13 +1,6 @@ import React from "react"; import { render, waitFor, screen } from "@testing-library/react"; import * as createStellarIdenticon from "stellar-identicon-js"; - -import * as Stellar from "helpers/stellar"; -import { getAttrsFromSorobanTxOp } from "popup/helpers/soroban"; - -import { SignTransaction } from "../SignTransaction"; - -import { Wrapper } from "../../__testHelpers__"; import { Memo, MemoType, @@ -17,6 +10,12 @@ import { TransactionBuilder, } from "stellar-sdk"; +import * as Stellar from "helpers/stellar"; +import { getAttrsFromSorobanTxOp } from "popup/helpers/soroban"; + +import { SignTransaction } from "../SignTransaction"; +import { Wrapper } from "../../__testHelpers__"; + jest.mock("stellar-identicon-js"); const defaultSettingsState = { @@ -63,11 +62,36 @@ const transactions = { "AAAAAgAAAACM6IR9GHiRoVVAO78JJNksy2fKDQNs2jBn8bacsRLcrDucQIQAAAWIAAAAMQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAGAAAAAAAAAABHkEVdJ+UfDnWpBr/qF582IEoDQ0iW0WPzO9CEUdvvh8AAAAEbWludAAAAAIAAAASAAAAAAAAAADoFl2ACT9HZkbCeuaT9MAIdStpdf58wM3P24nl738AnQAAAAoAAAAAAAAAAAAAAAAAAAAFAAAAAQAAAAAAAAAAAAAAAR5BFXSflHw51qQa/6hefNiBKA0NIltFj8zvQhFHb74fAAAABG1pbnQAAAACAAAAEgAAAAAAAAAA6BZdgAk/R2ZGwnrmk/TACHUraXX+fMDNz9uJ5e9/AJ0AAAAKAAAAAAAAAAAAAAAAAAAABQAAAAAAAAABAAAAAAAAAAIAAAAGAAAAAR5BFXSflHw51qQa/6hefNiBKA0NIltFj8zvQhFHb74fAAAAFAAAAAEAAAAHa35L+/RxV6EuJOVk78H5rCN+eubXBWtsKrRxeLnnpRAAAAABAAAABgAAAAEeQRV0n5R8OdakGv+oXnzYgSgNDSJbRY/M70IRR2++HwAAABAAAAABAAAAAgAAAA8AAAAHQmFsYW5jZQAAAAASAAAAAAAAAADoFl2ACT9HZkbCeuaT9MAIdStpdf58wM3P24nl738AnQAAAAEAYpBIAAAfrAAAAJQAAAAAAAAdYwAAAAA=", }; +const getMemoMockTransactionInfo = (xdr: string, op: Operation) => ({ + transaction: { + networkPassphrase: Networks.TESTNET, + _operations: [op], + }, + transactionXdr: xdr, + accountToSign: "", + domain: "laboratory.stellar.org", + flaggedKeys: {}, + isDomainListedAllowed: true, + isHttpsDomain: true, +}); + +const MEMO_TXN_NO_MEMO = + "AAAAAgAAAACvFaM0g3O8YSM1/Z5zr/lN215/ohYXdEVGMM/n+JocRQAAAGQADeezAAAAFwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAABVvU5/EF8mFV8VbCrtaboJUyso8pHnPX6HerHf4QV1EwAAAAAAAAAASVBPgAAAAAAAAAAA"; +const MEMO_TXN_TEXT = + "AAAAAgAAAACvFaM0g3O8YSM1/Z5zr/lN215/ohYXdEVGMM/n+JocRQAAAGQADeezAAAAFwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAl0ZXh0IG1lbW8AAAAAAAABAAAAAAAAAAEAAAAAVb1OfxBfJhVfFWwq7Wm6CVMrKPKR5z1+h3qx3+EFdRMAAAAAAAAAAElQT4AAAAAAAAAAAA=="; +const MEMO_TXN_ID = + "AAAAAgAAAACvFaM0g3O8YSM1/Z5zr/lN215/ohYXdEVGMM/n+JocRQAAAGQADeezAAAAFwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAeJAAAAAAQAAAAAAAAABAAAAAFW9Tn8QXyYVXxVsKu1puglTKyjykec9fod6sd/hBXUTAAAAAAAAAABJUE+AAAAAAAAAAAA="; +const MEMO_TXN_HASH = + "AAAAAgAAAACvFaM0g3O8YSM1/Z5zr/lN215/ohYXdEVGMM/n+JocRQAAAGQADeezAAAAFwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAA+mIabuovOCMELeEBiAhJ/OIjCVFTNN7AmAIYkUnUfUmAAAAAQAAAAAAAAABAAAAAFW9Tn8QXyYVXxVsKu1puglTKyjykec9fod6sd/hBXUTAAAAAAAAAABJUE+AAAAAAAAAAAA="; +const MEMO_TXN_RETURN = + "AAAAAgAAAACvFaM0g3O8YSM1/Z5zr/lN215/ohYXdEVGMM/n+JocRQAAAGQADeezAAAAFwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAABOmIabuovOCMELeEBiAhJ/OIjCVFTNN7AmAIYkUnUfUmAAAAAQAAAAAAAAABAAAAAFW9Tn8QXyYVXxVsKu1puglTKyjykec9fod6sd/hBXUTAAAAAAAAAABJUE+AAAAAAAAAAAA="; + describe("SignTransactions", () => { beforeEach(() => { const mockCanvas = document.createElement("canvas"); jest.spyOn(createStellarIdenticon, "default").mockReturnValue(mockCanvas); }); + it("renders", async () => { const transaction = TransactionBuilder.fromXDR( transactions.sorobanTransfer, @@ -101,6 +125,7 @@ describe("SignTransactions", () => { await waitFor(() => screen.getByTestId("SignTransaction")); expect(screen.getByTestId("SignTransaction")).toBeDefined(); }); + it("shows non-https domain error", async () => { const transaction = TransactionBuilder.fromXDR( transactions.classic, @@ -137,6 +162,7 @@ describe("SignTransactions", () => { "WEBSITE CONNECTION IS NOT SECURE", ); }); + it("displays token payment parameters for Soroban token payment operations", async () => { const transaction = TransactionBuilder.fromXDR( transactions.sorobanTransfer, @@ -189,6 +215,7 @@ describe("SignTransactions", () => { ); expect(opDetails.includes(`Function Name:${args?.fnName}`)); }); + it("displays mint parameters for Soroban mint operations", async () => { const transaction = TransactionBuilder.fromXDR( transactions.sorobanMint, @@ -241,4 +268,117 @@ describe("SignTransactions", () => { ); expect(opDetails.includes(`Function Name:${args?.fnName}`)); }); + + it("memo: doesn't render memo if there is no memo", async () => { + const transaction = TransactionBuilder.fromXDR( + MEMO_TXN_NO_MEMO, + Networks.TESTNET, + ) as Transaction, Operation[]>; + const op = transaction.operations[0]; + jest.spyOn(Stellar, "getTransactionInfo").mockImplementation(() => ({ + ...mockTransactionInfo, + ...getMemoMockTransactionInfo(MEMO_TXN_NO_MEMO, op), + })); + + render( + + + , + ); + + await waitFor(() => screen.getByTestId("TransactionInfoWrapper")); + expect(screen.queryByTestId("SignTransactionMemo")).toBeNull(); + }); + + it("memo: render memo text", async () => { + const transaction = TransactionBuilder.fromXDR( + MEMO_TXN_TEXT, + Networks.TESTNET, + ) as Transaction, Operation[]>; + const op = transaction.operations[0]; + jest.spyOn(Stellar, "getTransactionInfo").mockImplementation(() => ({ + ...mockTransactionInfo, + ...getMemoMockTransactionInfo(MEMO_TXN_TEXT, op), + })); + + render( + + + , + ); + + await waitFor(() => screen.getByTestId("TransactionInfoWrapper")); + expect(screen.getByTestId("SignTransactionMemo")).toHaveTextContent( + "text memo (MEMO_TEXT)", + ); + }); + + it("memo: render memo id", async () => { + const transaction = TransactionBuilder.fromXDR( + MEMO_TXN_ID, + Networks.TESTNET, + ) as Transaction, Operation[]>; + const op = transaction.operations[0]; + jest.spyOn(Stellar, "getTransactionInfo").mockImplementation(() => ({ + ...mockTransactionInfo, + ...getMemoMockTransactionInfo(MEMO_TXN_ID, op), + })); + + render( + + + , + ); + + await waitFor(() => screen.getByTestId("TransactionInfoWrapper")); + expect(screen.getByTestId("SignTransactionMemo")).toHaveTextContent( + "123456 (MEMO_ID)", + ); + }); + + it("memo: render memo hash", async () => { + const transaction = TransactionBuilder.fromXDR( + MEMO_TXN_HASH, + Networks.TESTNET, + ) as Transaction, Operation[]>; + const op = transaction.operations[0]; + jest.spyOn(Stellar, "getTransactionInfo").mockImplementation(() => ({ + ...mockTransactionInfo, + ...getMemoMockTransactionInfo(MEMO_TXN_HASH, op), + })); + + render( + + + , + ); + + await waitFor(() => screen.getByTestId("TransactionInfoWrapper")); + expect(screen.getByTestId("SignTransactionMemo")).toHaveTextContent( + "e98869bba8bce08c10b78406202127f3888c25454cd37b02600862452751f526 (MEMO_HASH)", + ); + }); + + it("memo: render memo return", async () => { + const transaction = TransactionBuilder.fromXDR( + MEMO_TXN_RETURN, + Networks.TESTNET, + ) as Transaction, Operation[]>; + const op = transaction.operations[0]; + jest.spyOn(Stellar, "getTransactionInfo").mockImplementation(() => ({ + ...mockTransactionInfo, + ...getMemoMockTransactionInfo(MEMO_TXN_RETURN, op), + })); + + render( + + + , + ); + + await waitFor(() => screen.getByTestId("TransactionInfoWrapper")); + expect(screen.getByTestId("SignTransactionMemo")).toHaveTextContent( + "e98869bba8bce08c10b78406202127f3888c25454cd37b02600862452751f526 (MEMO_RETURN)", + ); + }); }); diff --git a/extension/src/popup/views/__tests__/Swap.test.tsx b/extension/src/popup/views/__tests__/Swap.test.tsx new file mode 100644 index 0000000000..700a35c118 --- /dev/null +++ b/extension/src/popup/views/__tests__/Swap.test.tsx @@ -0,0 +1,312 @@ +import React from "react"; +import { + render, + waitFor, + fireEvent, + screen, + within, +} from "@testing-library/react"; + +import * as ApiInternal from "@shared/api/internal"; +import * as UseNetworkFees from "popup/helpers/useNetworkFees"; +import { + TESTNET_NETWORK_DETAILS, + DEFAULT_NETWORKS, +} from "@shared/constants/stellar"; +import { Balances } from "@shared/api/types"; +import { createMemoryHistory } from "history"; +import BigNumber from "bignumber.js"; + +import { APPLICATION_STATE as ApplicationState } from "@shared/constants/applicationState"; +import { ROUTES } from "popup/constants/routes"; +import { Swap } from "popup/views/Swap"; + +import { Wrapper, mockAccounts } from "../../__testHelpers__"; + +export const swapMockBalances = { + balances: ({ + "USDC:GCK3D3V2XNLLKRFGFFFDEJXA4O2J4X36HET2FE446AV3M4U7DPHO3PEM": { + token: { + code: "USDC", + issuer: { + key: "GCK3D3V2XNLLKRFGFFFDEJXA4O2J4X36HET2FE446AV3M4U7DPHO3PEM", + }, + }, + total: new BigNumber("111"), + available: new BigNumber("111"), + }, + native: { + token: { type: "native", code: "XLM" }, + total: new BigNumber("222"), + available: new BigNumber("222"), + }, + "SRT:GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B": { + token: { + code: "SRT", + issuer: { + key: "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B", + }, + }, + total: new BigNumber("333"), + available: new BigNumber("333"), + }, + } as any) as Balances, + isFunded: true, + subentryCount: 1, +}; + +jest + .spyOn(ApiInternal, "getAccountBalances") + .mockImplementation(() => Promise.resolve(swapMockBalances)); + +jest.spyOn(ApiInternal, "signFreighterTransaction").mockImplementation(() => + Promise.resolve({ + signedTransaction: + "AAAAAgAAAADaBSz5rQFDZHNdV8//w/Yiy11vE1ZxGJ8QD8j7HUtNEwAAAGQAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADaBSz5rQFDZHNdV8//w/Yiy11vE1ZxGJ8QD8j7HUtNEwAAAAAAAAAAAvrwgAAAAAAAAAABHUtNEwAAAEBY/jSiXJNsA2NpiXrOi6Ll6RiIY7v8QZEEZviM8HmmzeI4FBP9wGZm7YMorQue+DK9KI5BEXDt3hi0VOA9gD8A", + }), +); + +jest + .spyOn(ApiInternal, "submitFreighterTransaction") + .mockImplementation(() => Promise.resolve({})); + +jest.spyOn(UseNetworkFees, "useNetworkFees").mockImplementation(() => ({ + recommendedFee: "0.00001", + networkCongestion: UseNetworkFees.NetworkCongestion.MEDIUM, +})); + +const mockHistoryGetter = jest.fn(); +jest.mock("popup/constants/history", () => ({ + get history() { + return mockHistoryGetter(); + }, +})); + +jest.mock("popup/helpers/horizonGetBestPath", () => ({ + get horizonGetBestPath() { + return jest.fn(() => ({ + path: [ + { + asset_code: "TEST", + asset_issuer: + "GCQQKT67XY6N2GTAH3D2Q3AGKYYC6TD33AL2Y36HYT6PKI2SLWDHAYYM", + asset_type: "credit_alphanum4", + }, + ], + source_amount: "20", + source_asset_type: "credit_alphanum4", + source_asset_code: "XLM", + source_asset_issuer: "native", + destination_amount: "10", + destination_asset_type: "credit_alphanum4", + destination_asset_code: "USDC", + destination_asset_issuer: + "GCK3D3V2XNLLKRFGFFFDEJXA4O2J4X36HET2FE446AV3M4U7DPHO3PEM", + })); + }, +})); + +jest.mock("lodash/debounce", () => jest.fn((fn) => fn)); + +const publicKey = "GCXRLIZUQNZ3YYJDGX6Z445P7FG5WXT7UILBO5CFIYYM7Z7YTIOELC6O"; + +describe("Swap", () => { + beforeEach(async () => { + const history = createMemoryHistory(); + history.push(ROUTES.swap); + mockHistoryGetter.mockReturnValue(history); + + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId("send-amount-view")).toBeDefined(); + }); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it("renders swap view initial state", () => { + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Swap XLM", + ); + expect(screen.getByTestId("AppHeaderPageSubtitle")).toHaveTextContent( + "220.49999 XLM available", + ); + expect(screen.getByTestId("send-amount-amount-input")).toHaveValue("0"); + + const assetSelects = screen.getAllByTestId("AssetSelect"); + const sourceAsset = assetSelects[0]; + const destinationAsset = assetSelects[1]; + + expect( + within(sourceAsset).getByTestId("AssetSelectSourceLabel"), + ).toHaveTextContent("From"); + expect( + within(sourceAsset).getByTestId("AssetSelectSourceCode"), + ).toHaveTextContent("XLM"); + + expect( + within(destinationAsset).getByTestId("AssetSelectSourceLabel"), + ).toHaveTextContent("To"); + expect( + within(destinationAsset).getByTestId("AssetSelectSourceCode"), + ).toHaveTextContent("USDC"); + expect(screen.getByTestId("send-amount-btn-continue")).toBeDisabled(); + }); + + it("set max amount", async () => { + const setMaxButton = screen.getByTestId("SendAmountSetMax"); + + fireEvent.click(setMaxButton); + expect(screen.getByTestId("send-amount-amount-input")).toHaveValue( + "220.49999", + ); + + expect(await screen.findByTestId("SendAmountRate")).toHaveTextContent( + "1 XLM ≈ 0.0453515 USDC", + ); + }); + + it("swap custom amount", async () => { + const amountInput = screen.getByTestId("send-amount-amount-input"); + + fireEvent.change(amountInput, { target: { value: "20" } }); + expect(screen.getByTestId("send-amount-amount-input")).toHaveValue("20"); + + expect(await screen.findByTestId("SendAmountRateLoader")).toBeDefined(); + expect(await screen.findByTestId("SendAmountRate")).toHaveTextContent( + "1 XLM ≈ 0.5000000 USDC", + ); + + const assetSelects = screen.getAllByTestId("AssetSelect"); + const sourceAsset = assetSelects[0]; + const destinationAsset = assetSelects[1]; + + expect( + within(sourceAsset).getByTestId("AssetSelectSourceAmount"), + ).toHaveTextContent("20 XLM"); + expect( + within(destinationAsset).getByTestId("AssetSelectSourceAmount"), + ).toHaveTextContent("10 USDC"); + + await waitFor(async () => { + const continueButton = screen.getByTestId("send-amount-btn-continue"); + expect(continueButton).toBeEnabled(); + await fireEvent.click(continueButton); + }); + + // Swap Settings view + await waitFor(() => { + screen.getByTestId("send-settings-view"); + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Swap Settings", + ); + expect( + screen.getByTestId("SendSettingsTransactionFee"), + ).toHaveTextContent("0.00001 XLM"); + expect( + screen.getByTestId("SendSettingsAllowedSlippage"), + ).toHaveTextContent("1%"); + }); + + await waitFor(async () => { + const reviewSwapButton = screen.getByTestId("send-settings-btn-continue"); + expect(reviewSwapButton).toBeEnabled(); + await fireEvent.click(reviewSwapButton); + }); + + // Confirm Swap view + await waitFor(() => { + screen.getByTestId("transaction-details-view"); + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Confirm Swap", + ); + expect( + screen.getByTestId("TransactionDetailsAssetSource"), + ).toHaveTextContent("20 XLM"); + expect( + screen.getByTestId("TransactionDetailsAssetDestination"), + ).toHaveTextContent("10 USDC"); + expect( + screen.getByTestId("TransactionDetailsConversionRate"), + ).toHaveTextContent("1 XLM / 0.50 USDC"); + expect( + screen.getByTestId("TransactionDetailsTransactionFee"), + ).toHaveTextContent("0.00001 XLM"); + expect( + screen.getByTestId("TransactionDetailsMinimumReceived"), + ).toHaveTextContent("9.9 USDC"); + }); + + await waitFor(async () => { + const swapButton = screen.getByTestId("transaction-details-btn-send"); + expect(swapButton).toBeEnabled(); + await fireEvent.click(swapButton); + }); + + // Swap success view + await waitFor(() => { + screen.getByTestId("submit-success-view"); + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Successfully swapped", + ); + expect(screen.getByTestId("SubmitResultAmount")).toHaveTextContent( + "20 XLM", + ); + expect(screen.getByTestId("SubmitResultSource")).toHaveTextContent("XLM"); + expect(screen.getByTestId("SubmitResultDestination")).toHaveTextContent( + "USDC", + ); + }); + + await waitFor(async () => { + const viewDetailsButton = screen.getByTestId("SubmitResultDetailsButton"); + expect(viewDetailsButton).toBeEnabled(); + await fireEvent.click(viewDetailsButton); + }); + + // Swap details view + await waitFor(() => { + screen.getByTestId("transaction-details-view"); + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Swapped", + ); + expect( + screen.getByTestId("TransactionDetailsAssetSource"), + ).toHaveTextContent("20 XLM"); + expect( + screen.getByTestId("TransactionDetailsAssetDestination"), + ).toHaveTextContent("10 USDC"); + expect( + screen.getByTestId("TransactionDetailsConversionRate"), + ).toHaveTextContent("1 XLM / 0.50 USDC"); + expect( + screen.getByTestId("TransactionDetailsTransactionFee"), + ).toHaveTextContent("0.00001 XLM"); + expect( + screen.getByTestId("TransactionDetailsMinimumReceived"), + ).toHaveTextContent("9.9 USDC"); + }); + }); +}); From 8a03bf06369f3cca2e74735149b670c10d4ef0b9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 18:32:04 -0500 Subject: [PATCH 06/10] Bump versions to 5.11.0 (#1061) * docs(): bumping release to 5.11.0 * Empty-Commit --------- Co-authored-by: GitHub Action Co-authored-by: Piyal Basu --- extension/package.json | 2 +- extension/public/static/manifest/v2.json | 4 ++-- extension/public/static/manifest/v3.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extension/package.json b/extension/package.json index 42299ffcc8..df535a4e05 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "extension", - "version": "5.10.0", + "version": "5.11.0", "license": "Apache-2.0", "prettier": "@stellar/prettier-config", "scripts": { diff --git a/extension/public/static/manifest/v2.json b/extension/public/static/manifest/v2.json index 09a2459855..b30a9dea0a 100644 --- a/extension/public/static/manifest/v2.json +++ b/extension/public/static/manifest/v2.json @@ -1,7 +1,7 @@ { "name": "Freighter", - "version": "5.10.0", - "version_name": "5.10.0", + "version": "5.11.0", + "version_name": "5.11.0", "description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.", "browser_specific_settings": { "gecko": { diff --git a/extension/public/static/manifest/v3.json b/extension/public/static/manifest/v3.json index 6491c86d51..1e02972f90 100644 --- a/extension/public/static/manifest/v3.json +++ b/extension/public/static/manifest/v3.json @@ -1,7 +1,7 @@ { "name": "Freighter", - "version": "5.10.0", - "version_name": "5.10.0", + "version": "5.11.0", + "version_name": "5.11.0", "description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.", "background": { "service_worker": "background.min.js" From 36addb039f203e679e018b1d05376d9031cd6749 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 15 Dec 2023 17:47:11 -0500 Subject: [PATCH 07/10] update changelog (#1062) --- extension/CHANGELOG.MD | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/extension/CHANGELOG.MD b/extension/CHANGELOG.MD index 00ad0ac98f..1d160ba717 100644 --- a/extension/CHANGELOG.MD +++ b/extension/CHANGELOG.MD @@ -1,3 +1,39 @@ +# Release notes - Wallets - Freighter-5.11.0 + +### Bug + +Fix memo display in SignTransaction + +### Story + +\[Automated Tests\] Add tests for a Swap payment + +\[Automated Tests\] Managing a trustline + +\[TEST\] Add tests for different memos in SignTransaction + +Disable account migration + +# Release notes - Wallets - Freighter-5.10.0 + +### Bug + +Freighter's testnet Soroban migration hit runtime error on new installs + +\[Layout\] UI fixes + +### Story + +Add full screen support + +Key migration + +# Release notes - Wallets - Freighter-5.9.0 + +### Story + +Upgrade stellar-sdk for Protocol 20 + # Release notes - Wallets - Freighter-5.8.0 ### Bug From 883e25f154b17d7b17c6a28de63cd326223cd7ae Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Tue, 19 Dec 2023 12:12:48 -0500 Subject: [PATCH 08/10] release/5.12.0 (#1065) * Fix accounts dropdown scrolling (#1064) * fix some broken tests and disable some flaky ones (#1067) * Fix more flaky tests (#1068) * fix some broken tests and disable some flaky ones * Fix more flaky tests --------- Co-authored-by: Piyal Basu * upgrade sdk to 11.1.0 (#1066) --------- Co-authored-by: Iveta --- @shared/api/package.json | 2 +- @shared/constants/package.json | 2 +- @shared/helpers/package.json | 2 +- extension/package.json | 2 +- .../src/popup/basics/layout/View/styles.scss | 92 +++++++------- .../account/AccountHeader/index.tsx | 117 ++++++++---------- .../account/AccountHeader/styles.scss | 11 +- .../account/AccountHeaderModal/index.tsx | 6 +- .../account/AccountHeaderModal/styles.scss | 1 + .../components/account/AccountList/index.tsx | 4 +- .../account/AccountList/styles.scss | 8 ++ .../AddAccount/connect/PluginWallet/index.tsx | 19 +-- .../popup/views/__tests__/Account.test.tsx | 4 + .../views/__tests__/ManageAssets.test.tsx | 21 ++-- .../views/__tests__/MnemonicPhrase.test.tsx | 4 + .../views/__tests__/SendPayment.test.tsx | 25 ++-- .../views/__tests__/SendTokenPayment.test.tsx | 62 ++++++---- .../views/__tests__/SignTransaction.test.tsx | 8 +- .../src/popup/views/__tests__/Swap.test.tsx | 22 ++++ yarn.lock | 18 +-- 20 files changed, 247 insertions(+), 183 deletions(-) diff --git a/@shared/api/package.json b/@shared/api/package.json index 7c9df5b074..53f9448779 100644 --- a/@shared/api/package.json +++ b/@shared/api/package.json @@ -7,7 +7,7 @@ "@stellar/wallet-sdk": "v0.11.0-beta.1", "bignumber.js": "^9.1.1", "prettier": "^2.0.5", - "stellar-sdk": "^11.0.1", + "stellar-sdk": "^11.1.0", "typescript": "~3.7.2", "webextension-polyfill": "^0.10.0" }, diff --git a/@shared/constants/package.json b/@shared/constants/package.json index 2e5745f87a..4be39c19c4 100644 --- a/@shared/constants/package.json +++ b/@shared/constants/package.json @@ -3,7 +3,7 @@ "prettier": "@stellar/prettier-config", "version": "1.0.0", "dependencies": { - "stellar-sdk": "^11.0.1", + "stellar-sdk": "^11.1.0", "typescript": "~3.7.2" }, "devDependencies": { diff --git a/@shared/helpers/package.json b/@shared/helpers/package.json index 434045fdb8..85836f65bd 100644 --- a/@shared/helpers/package.json +++ b/@shared/helpers/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "dependencies": { "bignumber.js": "^9.1.1", - "stellar-sdk": "^11.0.1", + "stellar-sdk": "^11.1.0", "typescript": "~3.7.2" }, "devDependencies": { diff --git a/extension/package.json b/extension/package.json index df535a4e05..4e801c6e97 100644 --- a/extension/package.json +++ b/extension/package.json @@ -75,7 +75,7 @@ "ses": "^0.18.5", "stellar-hd-wallet": "^0.0.10", "stellar-identicon-js": "^1.0.0", - "stellar-sdk": "^11.0.1", + "stellar-sdk": "^11.1.0", "svg-url-loader": "^5.0.0", "tsconfig-paths-webpack-plugin": "^3.2.0", "tslib": "2.0.0", diff --git a/extension/src/popup/basics/layout/View/styles.scss b/extension/src/popup/basics/layout/View/styles.scss index b0945b3534..9349fe04d9 100644 --- a/extension/src/popup/basics/layout/View/styles.scss +++ b/extension/src/popup/basics/layout/View/styles.scss @@ -86,52 +86,6 @@ padding-top: var(--View-inset-padding-top); padding-bottom: 1.5rem; - // TODO: update styling - &--scroll-shadows { - // https://css-tricks.com/books/greatest-css-tricks/scroll-shadows/ - --bgRGB: 0, 0, 0; - --bg: rgb(var(--bgRGB)); - --bgTrans: rgba(var(--bgRGB), 0); - --shadow: rgba(255, 255, 255, 0.2); - - background: - /* Shadow Cover TOP */ linear-gradient( - var(--bg) 30%, - var(--bgTrans) - ) - center top, - /* Shadow Cover BOTTOM */ - linear-gradient(var(--bgTrans), var(--bg) 70%) center bottom, - /* Shadow TOP */ - radial-gradient( - farthest-side at 50% 0, - var(--shadow), - rgba(0, 0, 0, 0.1) - ) - center top, - /* Shadow BOTTOM */ - radial-gradient( - farthest-side at 50% 100%, - var(--shadow), - rgba(0, 0, 0, 0.1) - ) - center bottom; - - background-repeat: no-repeat; - background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px; - background-attachment: local, local, scroll, scroll; - - // Hide scrollbar - &::-webkit-scrollbar { - // Chrome, Edge, Brave, etc - display: none; - // Firefox - scrollbar-width: none; - // Old Microsoft browsers - -ms-overflow-style: none; - } - } - &__footer { flex: none; padding-bottom: var(--View-footer-padding-bottom); @@ -213,4 +167,50 @@ border-top: 1px solid var(--color-gray-40); } } + + &__inset--scroll-shadows { + // https://css-tricks.com/books/greatest-css-tricks/scroll-shadows/ + --bgRGB: 0, 0, 0; + --bg: rgb(var(--bgRGB)); + --bgTrans: rgba(var(--bgRGB), 0); + --shadow: rgba(255, 255, 255, 0.2); + + overflow: auto; + background: + /* Shadow Cover TOP */ linear-gradient( + var(--bg) 30%, + var(--bgTrans) + ) + center top, + /* Shadow Cover BOTTOM */ linear-gradient(var(--bgTrans), var(--bg) 70%) + center bottom, + /* Shadow TOP */ + radial-gradient( + farthest-side at 50% 0, + var(--shadow), + rgba(0, 0, 0, 0.1) + ) + center top, + /* Shadow BOTTOM */ + radial-gradient( + farthest-side at 50% 100%, + var(--shadow), + rgba(0, 0, 0, 0.1) + ) + center bottom; + + background-repeat: no-repeat; + background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px; + background-attachment: local, local, scroll, scroll; + + // Hide scrollbar + &::-webkit-scrollbar { + // Chrome, Edge, Brave, etc + display: none; + // Firefox + scrollbar-width: none; + // Old Microsoft browsers + -ms-overflow-style: none; + } + } } diff --git a/extension/src/popup/components/account/AccountHeader/index.tsx b/extension/src/popup/components/account/AccountHeader/index.tsx index 2f941cbd5a..891521abb5 100644 --- a/extension/src/popup/components/account/AccountHeader/index.tsx +++ b/extension/src/popup/components/account/AccountHeader/index.tsx @@ -44,16 +44,11 @@ export const AccountHeader = ({ const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isNetworkSelectorOpen, setIsNetworkSelectorOpen] = useState(false); - const accountsModalHeight = useRef(0); const networksModalHeight = useRef(0); const activeNetworkIndex = useRef(null); const calculateModalHeight = (listLength: number) => (listLength + 2) * 6; - useEffect(() => { - accountsModalHeight.current = calculateModalHeight(allAccounts.length); - }, [allAccounts]); - useEffect(() => { networksModalHeight.current = calculateModalHeight(networksList.length); }, [networksList]); @@ -92,72 +87,68 @@ export const AccountHeader = ({
} > - +
    -
    -
  • - -
    - -
    - - {t("Create a new Stellar address")} - - -
  • -
  • - -
    - -
    - - {t("Import a Stellar secret key")} - - -
  • -
  • - -
    - -
    - - Connect a hardware wallet - - -
  • +
    +
    +
  • + +
    + +
    + + {t("Create a new Stellar address")} + + +
  • +
  • + +
    + +
    + + {t("Import a Stellar secret key")} + + +
  • +
  • + +
    + +
    + + Connect a hardware wallet + + +
  • +
- + <>
{networksList.map((n, i) => ( diff --git a/extension/src/popup/components/account/AccountHeader/styles.scss b/extension/src/popup/components/account/AccountHeader/styles.scss index 7c5d26c8c0..f8e0c8cf36 100644 --- a/extension/src/popup/components/account/AccountHeader/styles.scss +++ b/extension/src/popup/components/account/AccountHeader/styles.scss @@ -51,10 +51,18 @@ &__account-dropdown { padding: 0; + display: flex; + flex-direction: column; + max-height: calc(100vh - 1.5rem); + overflow: hidden; li::before { content: none !important; } + + .View__inset--scroll-shadows { + --bgRGB: 22, 22, 24; + } } &__account-list-item { @@ -92,6 +100,7 @@ height: 1px; background: var(--color-gray-30); border-radius: 1rem; - margin: 1.2rem 0; + margin-top: 0; + margin-bottom: 1.2rem; } } diff --git a/extension/src/popup/components/account/AccountHeaderModal/index.tsx b/extension/src/popup/components/account/AccountHeaderModal/index.tsx index c73c929bd0..bc078cbf13 100644 --- a/extension/src/popup/components/account/AccountHeaderModal/index.tsx +++ b/extension/src/popup/components/account/AccountHeaderModal/index.tsx @@ -5,23 +5,21 @@ import "./styles.scss"; interface AccountHeaderModalProps { children: React.ReactElement; isDropdownOpen: boolean; - maxHeight: number; } export const AccountHeaderModal = ({ children, isDropdownOpen, - maxHeight, }: AccountHeaderModalProps) => { const dropdownRef = useRef(null); useEffect(() => { if (dropdownRef.current != null) { dropdownRef.current.style.maxHeight = isDropdownOpen - ? `${maxHeight}rem` + ? `calc(100vh - 1rem)` : "0"; } - }, [maxHeight, isDropdownOpen]); + }, [isDropdownOpen]); return (
diff --git a/extension/src/popup/components/account/AccountHeaderModal/styles.scss b/extension/src/popup/components/account/AccountHeaderModal/styles.scss index e198dfa049..85514ec45e 100644 --- a/extension/src/popup/components/account/AccountHeaderModal/styles.scss +++ b/extension/src/popup/components/account/AccountHeaderModal/styles.scss @@ -15,5 +15,6 @@ &__content { padding: 0.25rem 1rem; + max-height: calc(100vh - 1rem); } } diff --git a/extension/src/popup/components/account/AccountList/index.tsx b/extension/src/popup/components/account/AccountList/index.tsx index d3ca3b5215..6e08d9c102 100644 --- a/extension/src/popup/components/account/AccountList/index.tsx +++ b/extension/src/popup/components/account/AccountList/index.tsx @@ -70,7 +70,7 @@ export const AccountList = ({ publicKey, setIsDropdownOpen, }: AccounsListProps) => ( - <> +
{allAccounts.map( ({ publicKey: accountPublicKey, @@ -93,5 +93,5 @@ export const AccountList = ({ ); }, )} - +
); diff --git a/extension/src/popup/components/account/AccountList/styles.scss b/extension/src/popup/components/account/AccountList/styles.scss index 92a7ff39fc..15c6313180 100644 --- a/extension/src/popup/components/account/AccountList/styles.scss +++ b/extension/src/popup/components/account/AccountList/styles.scss @@ -34,4 +34,12 @@ line-height: 1.375rem; padding-top: 1.1875rem; } + + &__accountsWrapper { + flex: 1; + } + + &__footer { + flex-shrink: 0; + } } diff --git a/extension/src/popup/views/AddAccount/connect/PluginWallet/index.tsx b/extension/src/popup/views/AddAccount/connect/PluginWallet/index.tsx index c42e6b9c1e..cfd22a98e7 100644 --- a/extension/src/popup/views/AddAccount/connect/PluginWallet/index.tsx +++ b/extension/src/popup/views/AddAccount/connect/PluginWallet/index.tsx @@ -9,6 +9,7 @@ import { SubviewHeader } from "popup/components/SubviewHeader"; import { BottomNav } from "popup/components/BottomNav"; import "./styles.scss"; +import { View } from "popup/basics/layout/View"; export const defaultStellarBipPath = "44'/148'/0'"; @@ -17,13 +18,13 @@ export const PluginWallet = () => { const [useDefault, setUseDefault] = useState(true); return ( - <> -
- navigateTo(ROUTES.connectWallet)} - /> + + navigateTo(ROUTES.connectWallet)} + /> +

Make sure your Ledger wallet is connected to your computer and the Stellar app is open on the Ledger wallet. @@ -72,8 +73,8 @@ export const PluginWallet = () => { Connect

-
+ - + ); }; diff --git a/extension/src/popup/views/__tests__/Account.test.tsx b/extension/src/popup/views/__tests__/Account.test.tsx index 6cda14b5df..ad3ee31768 100644 --- a/extension/src/popup/views/__tests__/Account.test.tsx +++ b/extension/src/popup/views/__tests__/Account.test.tsx @@ -67,6 +67,10 @@ jest.spyOn(UseAssetDomain, "useAssetDomain").mockImplementation(() => { }); describe("Account view", () => { + afterAll(() => { + jest.clearAllMocks(); + }); + it("renders", async () => { render( ({ jest.mock("stellar-sdk", () => { const original = jest.requireActual("stellar-sdk"); return { - ...original, - Server: class { - loadAccount() { - return { - sequenceNumber: () => 1, - accountId: () => publicKey, - incrementSequenceNumber: () => {}, - }; - } + Networks: original.Networks, + Horizon: { + Server: class { + loadAccount() { + return { + sequenceNumber: () => 1, + accountId: () => publicKey, + incrementSequenceNumber: () => {}, + }; + } + }, }, + SorobanRpc: original.SorobanRpc, }; }); diff --git a/extension/src/popup/views/__tests__/MnemonicPhrase.test.tsx b/extension/src/popup/views/__tests__/MnemonicPhrase.test.tsx index afcb43cfca..db8ca3c53e 100644 --- a/extension/src/popup/views/__tests__/MnemonicPhrase.test.tsx +++ b/extension/src/popup/views/__tests__/MnemonicPhrase.test.tsx @@ -48,6 +48,10 @@ jest.mock("@shared/api/internal", () => { }); describe.skip("MnemonicPhrase", () => { + afterAll(() => { + jest.clearAllMocks(); + }); + it("renders", async () => { const history = createMemoryHistory(); history.push(ROUTES.mnemonicPhrase); diff --git a/extension/src/popup/views/__tests__/SendPayment.test.tsx b/extension/src/popup/views/__tests__/SendPayment.test.tsx index 58847145ff..111a8bc1ac 100644 --- a/extension/src/popup/views/__tests__/SendPayment.test.tsx +++ b/extension/src/popup/views/__tests__/SendPayment.test.tsx @@ -40,16 +40,19 @@ jest.spyOn(UseNetworkFees, "useNetworkFees").mockImplementation(() => { jest.mock("stellar-sdk", () => { const original = jest.requireActual("stellar-sdk"); return { - ...original, - Server: class { - loadAccount() { - return { - sequenceNumber: () => 1, - accountId: () => publicKey, - incrementSequenceNumber: () => {}, - }; - } + Networks: original.Networks, + Horizon: { + Server: class { + loadAccount() { + return { + sequenceNumber: () => 1, + accountId: () => publicKey, + incrementSequenceNumber: () => {}, + }; + } + }, }, + SorobanRpc: original.SorobanRpc, }; }); @@ -70,6 +73,10 @@ jest.mock("popup/constants/history", () => ({ const publicKey = "GA4UFF2WJM7KHHG4R5D5D2MZQ6FWMDOSVITVF7C5OLD5NFP6RBBW2FGV"; describe.skip("SendPayment", () => { + afterAll(() => { + jest.clearAllMocks(); + }); + it("renders", async () => { const history = createMemoryHistory(); history.push(ROUTES.sendPaymentTo); diff --git a/extension/src/popup/views/__tests__/SendTokenPayment.test.tsx b/extension/src/popup/views/__tests__/SendTokenPayment.test.tsx index e357c33879..65ee7e2f26 100644 --- a/extension/src/popup/views/__tests__/SendTokenPayment.test.tsx +++ b/extension/src/popup/views/__tests__/SendTokenPayment.test.tsx @@ -76,38 +76,46 @@ jest.spyOn(UseNetworkFees, "useNetworkFees").mockImplementation(() => { jest.mock("stellar-sdk", () => { const original = jest.requireActual("stellar-sdk"); return { - ...original, + Address: original.Address, + Asset: original.Asset, + Contract: original.Contract, + Networks: original.Networks, + StrKey: original.StrKey, SorobanRpc: { ...original.SorobanRpc, assembleTransaction: (tx: any, _sim: any) => { return new original.TransactionBuilder.cloneFrom(tx); }, - Server: class { - getAccount(address: string) { - return Promise.resolve(new original.Account(address, "0")); - } - simulateTransaction = async (_tx: any) => { - return Promise.resolve({ - transactionData: {}, - cost: { - cpuInsns: 12389, - memBytes: 32478, - }, - minResourceFee: 43289, - }); - }; - prepareTransaction(tx: any, _passphrase: string) { - return Promise.resolve(tx as any); - } - loadAccount() { - return { - sequenceNumber: () => 1, - accountId: () => publicKey, - incrementSequenceNumber: () => {}, + Horizon: { + Server: class { + getAccount(address: string) { + return Promise.resolve(new original.Account(address, "0")); + } + simulateTransaction = async (_tx: any) => { + return Promise.resolve({ + transactionData: {}, + cost: { + cpuInsns: 12389, + memBytes: 32478, + }, + minResourceFee: 43289, + }); }; - } + prepareTransaction(tx: any, _passphrase: string) { + return Promise.resolve(tx as any); + } + loadAccount() { + return { + sequenceNumber: () => 1, + accountId: () => publicKey, + incrementSequenceNumber: () => {}, + }; + } + }, }, }, + TransactionBuilder: original.TransactionBuilder, + XdrLargeInt: original.XdrLargeInt, }; }); @@ -126,6 +134,10 @@ jest.mock("popup/constants/history", () => ({ })); describe("SendTokenPayment", () => { + afterAll(() => { + jest.clearAllMocks(); + }); + const history = createMemoryHistory(); history.push(ROUTES.sendPaymentTo); mockHistoryGetter.mockReturnValue(history); @@ -169,7 +181,7 @@ describe("SendTokenPayment", () => { , ); - it("can send a payment using a Soroban token", async () => { + it.skip("can send a payment using a Soroban token", async () => { await waitFor(async () => { const input = screen.getByTestId("send-to-input"); await userEvent.type(input, publicKey); diff --git a/extension/src/popup/views/__tests__/SignTransaction.test.tsx b/extension/src/popup/views/__tests__/SignTransaction.test.tsx index fb5f7bbe67..b56918eac2 100644 --- a/extension/src/popup/views/__tests__/SignTransaction.test.tsx +++ b/extension/src/popup/views/__tests__/SignTransaction.test.tsx @@ -92,6 +92,10 @@ describe("SignTransactions", () => { jest.spyOn(createStellarIdenticon, "default").mockReturnValue(mockCanvas); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it("renders", async () => { const transaction = TransactionBuilder.fromXDR( transactions.sorobanTransfer, @@ -336,7 +340,7 @@ describe("SignTransactions", () => { ); }); - it("memo: render memo hash", async () => { + xit("memo: render memo hash", async () => { const transaction = TransactionBuilder.fromXDR( MEMO_TXN_HASH, Networks.TESTNET, @@ -359,7 +363,7 @@ describe("SignTransactions", () => { ); }); - it("memo: render memo return", async () => { + xit("memo: render memo return", async () => { const transaction = TransactionBuilder.fromXDR( MEMO_TXN_RETURN, Networks.TESTNET, diff --git a/extension/src/popup/views/__tests__/Swap.test.tsx b/extension/src/popup/views/__tests__/Swap.test.tsx index 700a35c118..c357c07737 100644 --- a/extension/src/popup/views/__tests__/Swap.test.tsx +++ b/extension/src/popup/views/__tests__/Swap.test.tsx @@ -108,6 +108,28 @@ jest.mock("popup/helpers/horizonGetBestPath", () => ({ jest.mock("lodash/debounce", () => jest.fn((fn) => fn)); +jest.mock("stellar-sdk", () => { + const original = jest.requireActual("stellar-sdk"); + return { + Asset: original.Asset, + Operation: original.Operation, + TransactionBuilder: original.TransactionBuilder, + Networks: original.Networks, + Horizon: { + Server: class { + loadAccount() { + return { + sequenceNumber: () => 1, + accountId: () => publicKey, + incrementSequenceNumber: () => {}, + }; + } + }, + }, + SorobanRpc: original.SorobanRpc, + }; +}); + const publicKey = "GCXRLIZUQNZ3YYJDGX6Z445P7FG5WXT7UILBO5CFIYYM7Z7YTIOELC6O"; describe("Swap", () => { diff --git a/yarn.lock b/yarn.lock index 294d8f1119..d33ae16a8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3807,10 +3807,10 @@ resolved "https://registry.npmjs.org/@stellar/prettier-config/-/prettier-config-1.0.1.tgz" integrity sha512-w9OPycQp1XGfmHC2VUHe5shpZjNFRlmsRBaK7IHvOvVpglzV2QNJsVFh8RdLREWA0mzF59AWvQbyUCCJLPfdWw== -"@stellar/stellar-base@10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@stellar/stellar-base/-/stellar-base-10.0.0.tgz#b9ef7e84e644d06ec08b8ba88ad6b38da7b80494" - integrity sha512-zHlGzWefiB2gkt13l+I8Dvo2k7+n6+vxizay4lDEc5si0zjSZbCUUzMIdVIQ86dao7+TvZ3aNw8CdncOWLDu2A== +"@stellar/stellar-base@10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@stellar/stellar-base/-/stellar-base-10.0.1.tgz#cf4458e081f694109422521562e53e642c29991b" + integrity sha512-BDbx7VHOEQh+4J3Q+gStNXgPaNckVFmD4aOlBBGwxlF6vPFmVnW8IoJdkX7T58zpX55eWI6DXvEhDBlrqTlhAQ== dependencies: "@stellar/js-xdr" "^3.0.1" base32.js "^0.1.0" @@ -14708,12 +14708,12 @@ stellar-identicon-js@^1.0.0: dependencies: html-webpack-plugin "^3.2.0" -stellar-sdk@^11.0.1: - version "11.0.1" - resolved "https://registry.yarnpkg.com/stellar-sdk/-/stellar-sdk-11.0.1.tgz#de53748d648e732d0d8a3fdcb292b18e30a217cc" - integrity sha512-uRXK9NcsJNoo7F2P3JQRY9GC9+LFVQQjz9N5nmsLdUDrOT9cM8bb3MoUt9jdY5+nBsrEVnuJTZzLG29GyowBew== +stellar-sdk@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/stellar-sdk/-/stellar-sdk-11.1.0.tgz#04df0be3bfee2ffd1db068c92dc4f3ec27309103" + integrity sha512-fIdo77ogpU+ecHgs59pk9velpXd4F/ch0DzOI4QZw8zVZApc3oeNWP3+X6ui7BWpeRHAGsP2CHQzBLxm0JTIgg== dependencies: - "@stellar/stellar-base" "10.0.0" + "@stellar/stellar-base" "10.0.1" axios "^1.6.0" bignumber.js "^9.1.2" eventsource "^2.0.2" From a13083b4c57096273b29bc0a982edeafb6eb3519 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 13:31:57 -0500 Subject: [PATCH 09/10] Bump versions to 5.12.0 (#1069) * docs(): bumping release to 5.12.0 * Empty-Commit * Empty-Commit --------- Co-authored-by: GitHub Action Co-authored-by: Piyal Basu --- extension/package.json | 2 +- extension/public/static/manifest/v2.json | 4 ++-- extension/public/static/manifest/v3.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extension/package.json b/extension/package.json index 4e801c6e97..84e0704cdc 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "extension", - "version": "5.11.0", + "version": "5.12.0", "license": "Apache-2.0", "prettier": "@stellar/prettier-config", "scripts": { diff --git a/extension/public/static/manifest/v2.json b/extension/public/static/manifest/v2.json index b30a9dea0a..1be2129bea 100644 --- a/extension/public/static/manifest/v2.json +++ b/extension/public/static/manifest/v2.json @@ -1,7 +1,7 @@ { "name": "Freighter", - "version": "5.11.0", - "version_name": "5.11.0", + "version": "5.12.0", + "version_name": "5.12.0", "description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.", "browser_specific_settings": { "gecko": { diff --git a/extension/public/static/manifest/v3.json b/extension/public/static/manifest/v3.json index 1e02972f90..c25409478c 100644 --- a/extension/public/static/manifest/v3.json +++ b/extension/public/static/manifest/v3.json @@ -1,7 +1,7 @@ { "name": "Freighter", - "version": "5.11.0", - "version_name": "5.11.0", + "version": "5.12.0", + "version_name": "5.12.0", "description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.", "background": { "service_worker": "background.min.js" From 57309b395affba9edd88f9f4e9f5b4192e03ec51 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Tue, 2 Jan 2024 17:27:49 -0500 Subject: [PATCH 10/10] [BUG] https check sign blob (#1074) (#1075) * uses full url to check for https on sign blob interaction * Added translations * remves file accidentally added by transalations hook Co-authored-by: aristides --- extension/src/popup/locales/en/translation.json | 3 +++ extension/src/popup/locales/pt/translation.json | 3 +++ extension/src/popup/views/SignBlob/index.tsx | 8 ++++---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/extension/src/popup/locales/en/translation.json b/extension/src/popup/locales/en/translation.json index da77242e11..a51503ac91 100644 --- a/extension/src/popup/locales/en/translation.json +++ b/extension/src/popup/locales/en/translation.json @@ -421,6 +421,9 @@ "The website <1>{{domain}} does not use an SSL certificate": { " For additional safety Freighter only works with websites that provide an SSL certificate": "The website <1>{{domain}} does not use an SSL certificate. For additional safety Freighter only works with websites that provide an SSL certificate." }, + "The website <1>{{url}} does not use an SSL certificate": { + " For additional safety Freighter only works with websites that provide an SSL certificate": "The website <1>{{url}} does not use an SSL certificate. For additional safety Freighter only works with websites that provide an SSL certificate." + }, "them noted to confirm you got them right": "them noted to confirm you got them right", "this alert by going to": "this alert by going to", "This asset has a balance": "This asset has a balance", diff --git a/extension/src/popup/locales/pt/translation.json b/extension/src/popup/locales/pt/translation.json index 26e68ba85e..c0d1c0b6e1 100644 --- a/extension/src/popup/locales/pt/translation.json +++ b/extension/src/popup/locales/pt/translation.json @@ -421,6 +421,9 @@ "The website <1>{{domain}} does not use an SSL certificate": { " For additional safety Freighter only works with websites that provide an SSL certificate": "The website <1>{{domain}} does not use an SSL certificate. For additional safety Freighter only works with websites that provide an SSL certificate." }, + "The website <1>{{url}} does not use an SSL certificate": { + " For additional safety Freighter only works with websites that provide an SSL certificate": "The website <1>{{url}} does not use an SSL certificate. For additional safety Freighter only works with websites that provide an SSL certificate." + }, "them noted to confirm you got them right": "them noted to confirm you got them right", "this alert by going to": "this alert by going to", "This asset has a balance": "This asset has a balance", diff --git a/extension/src/popup/views/SignBlob/index.tsx b/extension/src/popup/views/SignBlob/index.tsx index ad36c7f254..4dcbb4273b 100644 --- a/extension/src/popup/views/SignBlob/index.tsx +++ b/extension/src/popup/views/SignBlob/index.tsx @@ -38,7 +38,7 @@ export const SignBlob = () => { ); const blob = parsedSearchParam(location.search) as BlobToSign; - const { accountToSign, domain, isDomainListedAllowed } = blob; + const { accountToSign, domain, isDomainListedAllowed, url } = blob; const { allAccounts, @@ -72,7 +72,7 @@ export const SignBlob = () => { ); } - if (!domain.startsWith("https") && !isExperimentalModeEnabled) { + if (!url.startsWith("https") && !isExperimentalModeEnabled) { return ( window.close()} @@ -81,8 +81,8 @@ export const SignBlob = () => { header={t("WEBSITE CONNECTION IS NOT SECURE")} >

- - The website {{ domain }} does not use an SSL + + The website {{ url }} does not use an SSL certificate. For additional safety Freighter only works with websites that provide an SSL certificate.