diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index d8f15918a9c2..7c5f89eeeced 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -4653,15 +4653,6 @@ "message": "Kontaktieren Sie die Ersteller von $1 für weitere Unterstützung.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Bei Aktivierung dieser Funktion haben Sie die Möglichkeit, ein Solana-Konto zu Ihrer MetaMask-Erweiterung hinzuzufügen, das von Ihrer bestehenden geheimen Wiederherstellungsphrase abgeleitet ist. Hierbei handelt es sich um eine experimentelle Beta-Funktion, deren Nutzung also auf eigene Gefahr erfolgt." - }, - "solanaSupportToggleTitle": { - "message": "Aktivierung der Funktion „Ein neues Solana-Konto hinzufügen (Beta)“" - }, "somethingDoesntLookRight": { "message": "Scheint irgendetwas nicht in Ordnung zu sein? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 21ce8d173eaf..f9c0ff48b49e 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -4653,15 +4653,6 @@ "message": "Επικοινωνήστε με τους διαχειριστές του $1 για περαιτέρω υποστήριξη.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Η ενεργοποίηση αυτής της λειτουργίας θα σας δώσει τη δυνατότητα να προσθέσετε έναν λογαριασμό Solana στην επέκταση του MetaMask που προέρχεται από την υπάρχουσα Μυστική Φράση Ανάκτησης. Πρόκειται για μια πειραματική λειτουργία Beta, οπότε θα πρέπει να τη χρησιμοποιήσετε με δική σας ευθύνη." - }, - "solanaSupportToggleTitle": { - "message": "Ενεργοποίηση της λειτουργίας \"Προσθήκη νέου λογαριασμού Solana (Beta)\"" - }, "somethingDoesntLookRight": { "message": "Κάτι δεν φαίνεται σωστό; $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 8fcd4dd849f4..426924b0b4b6 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -4994,15 +4994,6 @@ "message": "Contact the creators of $1 for further support.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Turning on this feature will give you the option to add a Solana Account to your MetaMask Extension derived from your existing Secret Recovery Phrase. This is an experimental Beta feature, so you should use it at your own risk." - }, - "solanaSupportToggleTitle": { - "message": "Enable \"Add a new Solana account (Beta)\"" - }, "someNetworks": { "message": "$1 networks" }, diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 8fcd4dd849f4..426924b0b4b6 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -4994,15 +4994,6 @@ "message": "Contact the creators of $1 for further support.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Turning on this feature will give you the option to add a Solana Account to your MetaMask Extension derived from your existing Secret Recovery Phrase. This is an experimental Beta feature, so you should use it at your own risk." - }, - "solanaSupportToggleTitle": { - "message": "Enable \"Add a new Solana account (Beta)\"" - }, "someNetworks": { "message": "$1 networks" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 4ff395f5e8b3..56085f47c6c3 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -4653,15 +4653,6 @@ "message": "Póngase en contacto con los creadores de $1 para obtener más ayuda.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Activar esta función le dará la opción de añadir una cuenta de Solana a su extensión MetaMask derivada de su frase secreta de recuperación existente. Esta es una característica Beta experimental, por lo que debe utilizarla bajo su propio riesgo." - }, - "solanaSupportToggleTitle": { - "message": "Activar \"Añadir una nueva cuenta de Solana (Beta)\"" - }, "somethingDoesntLookRight": { "message": "Algo no se ve bien, ¿cierto? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index c971c85b3c4d..d69e1a049346 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -4653,15 +4653,6 @@ "message": "L’interface utilisateur (IU) spécifiée par le snap n’est pas valide.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "En activant cette fonctionnalité, vous aurez la possibilité d’ajouter un compte Solana à votre extension MetaMask dérivée de votre phrase secrète de récupération existante. Toute utilisation de cette fonctionnalité bêta expérimentale se fait à vos risques et périls." - }, - "solanaSupportToggleTitle": { - "message": "Activer « Ajouter un nouveau compte Solana (Bêta) »" - }, "somethingDoesntLookRight": { "message": "On dirait que quelque chose ne va pas ? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 7c3e4ca5d331..df3b01792817 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -4653,15 +4653,6 @@ "message": "अधिक सहायता के लिए $1 के निर्माताओं से कॉन्टेक्ट करें।", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "सोलाना" - }, - "solanaSupportToggleDescription": { - "message": "इस फीचर को चालू करने से आपको अपने मौजूदा सीक्रेट रिकवरी फ्रेज़ से प्राप्त MetaMask एक्सटेंशन में एक सोलाना अकाउंट जोड़ने का विकल्प मिलेगा। यह एक प्रायोगिक बीटा फीचर है, इसलिए आपको इसका उपयोग अपने जोखिम पर करना होगा।" - }, - "solanaSupportToggleTitle": { - "message": "\"एक नया सोलाना अकाउंट जोड़ें (बीटा)\" को चालू करें" - }, "somethingDoesntLookRight": { "message": "कुछ तो गड़बड़ है? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 4c150fe5dca4..1b25e03c859b 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -4653,15 +4653,6 @@ "message": "Hubungi pembuat $1 untuk dukungan lebih lanjut.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Mengaktifkan fitur ini akan memberi Anda opsi untuk menambahkan Akun Solana ke Ekstensi MetaMask yang berasal dari Frasa Pemulihan Rahasia yang sudah ada. Ini merupakan fitur Beta eksperimental, jadi Anda harus menggunakannya dengan risiko yang ditanggung sendiri." - }, - "solanaSupportToggleTitle": { - "message": "Aktifkan \"Tambahkan akun Solana baru (Beta)\"" - }, "somethingDoesntLookRight": { "message": "Ada yang tidak beres? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index afad5e264d27..664b61342d65 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -4653,15 +4653,6 @@ "message": "今後のサポートは、$1の作成者にお問い合わせください。", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "この機能をオンにすると、既存のシークレットリカバリーフレーズで導出したSolanaアカウントをMetaMask Extensionに追加できるようになります。これは試験運用中のベータ機能であるため、自己責任でご使用ください。" - }, - "solanaSupportToggleTitle": { - "message": "「新規Solanaアカウントの追加 (ベータ)」を有効にする" - }, "somethingDoesntLookRight": { "message": "何か不審な点があれば、$1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index ce513201b803..b48b9655dad0 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -4653,15 +4653,6 @@ "message": "$1 작성자에게 연락하여 향후 지원을 요청하세요.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "솔라나" - }, - "solanaSupportToggleDescription": { - "message": "이 기능을 켜면 기존 비밀복구구문에서 파생된 솔라나 계정을 MetaMask 확장 프로그램에 추가하는 옵션이 제공됩니다. 이는 실험적 베타 기능이므로 사용자의 책임하에 사용해야 합니다." - }, - "solanaSupportToggleTitle": { - "message": "'새 솔라나 계정 추가(베타)' 활성화" - }, "somethingDoesntLookRight": { "message": "무언가 잘못되었나요? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 6b88ee8d4eeb..16c868f2a3d0 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -4656,15 +4656,6 @@ "message": "Contate os criadores de $1 para receber mais suporte.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Ativar este recurso lhe dará a opção de adicionar uma conta Solana à sua extensão da MetaMask derivada de sua Frase de Recuperação Secreta existente. Este é um recurso beta experimental, portanto seu uso será por sua conta e risco." - }, - "solanaSupportToggleTitle": { - "message": "Ative \"Adicionar uma nova conta Solana (Beta)\"" - }, "somethingDoesntLookRight": { "message": "Alguma coisa não parece certa? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 70e439f6ceab..db1f8ac5e683 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -4653,15 +4653,6 @@ "message": "Свяжитесь с авторами $1 для получения дополнительной поддержки.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Включение этой функции даст вам возможность добавить в ваше расширение MetaMask счет Solana, созданный на основе вашей существующей секретной фразы для восстановления. Это экспериментальная бета-функция, которая используется вами на ваш страх и риск." - }, - "solanaSupportToggleTitle": { - "message": "Включить «Добавить новый счет Solana (бета-версия)»" - }, "somethingDoesntLookRight": { "message": "Что-то не так? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index ad6348143a82..6f859d53b154 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -4653,15 +4653,6 @@ "message": "Makipag-ugnayan sa mga tagalikha ng $1 para sa karagdagang suporta.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Kapag in-on ang feature na ito, magkakaroon ka ng opsyon na magdagdag ng Solana Account sa MetaMask extension mo mula sa dati mo nang Lihim na Parirala sa Pagbawi. Isa itong eksperimental na Beta feature kaya gamitin ito nang may pag-iingat." - }, - "solanaSupportToggleTitle": { - "message": "Paganahin ang \"Magdagdag ng bagong Solana account (Beta)\"" - }, "somethingDoesntLookRight": { "message": "Mayroon bang hindi tama? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 15d2e042f8a2..129b9632c1ef 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -4653,15 +4653,6 @@ "message": "Daha fazla destek için $1 oluşturucuları ile iletişime geçin.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Bu özelliği açtığınızda mevcut Gizli Kurtarma İfadenizden türetilen MetaMask Uzantınıza bir Solana Hesabı ekleme seçeneğiniz olacaktır. Bu, deneysel bir Beta özelliğidir, bu yüzden riski kendinize ait olarak kullanmalısınız." - }, - "solanaSupportToggleTitle": { - "message": "\"Yeni bir Solana hesabı ekle (Beta)\" seçeneğini etkinleştir" - }, "somethingDoesntLookRight": { "message": "Doğru görünmeyen bir şeyler mi var? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 5db87583d8eb..e3f1ae9fd72b 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -4653,15 +4653,6 @@ "message": "Liên hệ với những người tạo ra $1 để được hỗ trợ thêm.", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "Bật tính năng này sẽ cung cấp cho bạn tùy chọn thêm Tài khoản Solana vào Tiện ích mở rộng MetaMask bắt nguồn từ Cụm từ khôi phục bí mật hiện có của bạn. Đây là một tính năng Beta thử nghiệm, nên bạn phải tự chịu rủi ro khi sử dụng nó." - }, - "solanaSupportToggleTitle": { - "message": "Bật \"Thêm tài khoản Solana mới (Beta)\"" - }, "somethingDoesntLookRight": { "message": "Có gì đó không ổn? $1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 2a66fcd269d5..8e1e936963cc 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -4653,15 +4653,6 @@ "message": "联系 $1 的创建者以获得进一步支持。", "description": "This is shown when the insight snap throws an error. $1 is the snap name" }, - "solanaSupportSectionTitle": { - "message": "Solana" - }, - "solanaSupportToggleDescription": { - "message": "启用此功能后,您可以选择将 Solana 账户添加到衍生自现有私钥助记词的 MetaMask Extension 中。这是一个实验性的测试版功能,因此您应自行承担使用风险。" - }, - "solanaSupportToggleTitle": { - "message": "启用“添加新的 Solana 账户(测试版)”" - }, "somethingDoesntLookRight": { "message": "有什么不对劲吗?$1", "description": "A false positive message for users to contact support. $1 is a link to the support page." diff --git a/app/scripts/controllers/permissions/specifications.js b/app/scripts/controllers/permissions/specifications.js index 24db69e64130..a921b00477b0 100644 --- a/app/scripts/controllers/permissions/specifications.js +++ b/app/scripts/controllers/permissions/specifications.js @@ -180,6 +180,7 @@ export const unrestrictedMethods = Object.freeze([ 'snap_scheduleBackgroundEvent', 'snap_cancelBackgroundEvent', 'snap_getBackgroundEvents', + 'snap_experimentalProviderRequest', ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) 'metamaskinstitutional_authenticate', 'metamaskinstitutional_reauthenticate', diff --git a/app/scripts/controllers/preferences-controller.test.ts b/app/scripts/controllers/preferences-controller.test.ts index 54818fc796d3..db29bb891c5d 100644 --- a/app/scripts/controllers/preferences-controller.test.ts +++ b/app/scripts/controllers/preferences-controller.test.ts @@ -875,19 +875,4 @@ describe('preferences controller', () => { ); }); }); - - describe('setSolanaSupportEnabled', () => { - const { controller } = setupController({}); - it('has the default value as false', () => { - expect(controller.state.solanaSupportEnabled).toStrictEqual(false); - }); - - it('sets the solanaSupportEnabled property in state to true and then false', () => { - controller.setSolanaSupportEnabled(true); - expect(controller.state.solanaSupportEnabled).toStrictEqual(true); - - controller.setSolanaSupportEnabled(false); - expect(controller.state.solanaSupportEnabled).toStrictEqual(false); - }); - }); }); diff --git a/app/scripts/controllers/preferences-controller.ts b/app/scripts/controllers/preferences-controller.ts index 7f03823a7627..5a8f956907bc 100644 --- a/app/scripts/controllers/preferences-controller.ts +++ b/app/scripts/controllers/preferences-controller.ts @@ -142,9 +142,6 @@ export type PreferencesControllerState = Omit< ///: BEGIN:ONLY_INCLUDE_IF(build-flask) watchEthereumAccountEnabled: boolean; ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(solana) - solanaSupportEnabled: boolean; - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(bitcoin) bitcoinSupportEnabled: boolean; bitcoinTestnetSupportEnabled: boolean; @@ -189,9 +186,6 @@ export const getDefaultPreferencesControllerState = openSeaEnabled: true, securityAlertsEnabled: true, watchEthereumAccountEnabled: false, - ///: BEGIN:ONLY_INCLUDE_IF(solana) - solanaSupportEnabled: false, - ///: END:ONLY_INCLUDE_IF bitcoinSupportEnabled: false, bitcoinTestnetSupportEnabled: false, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) @@ -342,10 +336,6 @@ const controllerMetadata = { persist: true, anonymous: false, }, - solanaSupportEnabled: { - persist: true, - anonymous: false, - }, bitcoinSupportEnabled: { persist: true, anonymous: false, @@ -665,20 +655,6 @@ export class PreferencesController extends BaseController< } ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(solana) - /** - * Setter for the `solanaSupportEnabled` property. - * - * @param solanaSupportEnabled - Whether or not the user wants to - * enable the "Add a new Solana account" button. - */ - setSolanaSupportEnabled(solanaSupportEnabled: boolean): void { - this.update((state) => { - state.solanaSupportEnabled = solanaSupportEnabled; - }); - } - ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(bitcoin) /** * Setter for the `bitcoinSupportEnabled` property. diff --git a/app/scripts/lib/snap-keyring/snap-keyring.ts b/app/scripts/lib/snap-keyring/snap-keyring.ts index 43491bc54b1b..4325c597d13b 100644 --- a/app/scripts/lib/snap-keyring/snap-keyring.ts +++ b/app/scripts/lib/snap-keyring/snap-keyring.ts @@ -14,6 +14,7 @@ import MetamaskController from '../../metamask-controller'; // eslint-disable-next-line import/no-restricted-paths import { IconName } from '../../../../ui/components/component-library/icon'; import MetaMetricsController from '../../controllers/metametrics-controller'; +import { getUniqueAccountName } from '../../../../shared/lib/accounts'; import { isBlockedUrl } from './utils/isBlockedUrl'; import { showError, showSuccess } from './utils/showResult'; import { SnapKeyringBuilderMessenger } from './types'; @@ -201,23 +202,62 @@ class SnapKeyringImpl implements SnapKeyringCallbacks { } } + /** + * Get the account name from the user through a dialog. + * + * @param snapId - ID of the Snap that created the account. + * @param accountNameSuggestion - Suggested name for the account. + * @returns The name that should be used for the account. + */ + async #getAccountNameFromDialog( + snapId: SnapId, + accountNameSuggestion: string, + ): Promise<{ success: boolean; accountName?: string }> { + const { success, name: accountName } = + await showAccountNameSuggestionDialog( + snapId, + this.#messenger, + accountNameSuggestion, + ); + + return { success, accountName }; + } + + /** + * Use the account name suggestion to decide the name of the account. + * + * @param accountNameSuggestion - Suggested name for the account. + * @returns The name that should be used for the account. + */ + async #getAccountNameFromSuggestion( + accountNameSuggestion: string, + ): Promise<{ success: boolean; accountName?: string }> { + const accounts = await this.#messenger.call( + 'AccountsController:listMultichainAccounts', + ); + const accountName = getUniqueAccountName(accounts, accountNameSuggestion); + return { success: true, accountName }; + } + async #addAccountConfirmations({ snapId, - skipConfirmation, + skipConfirmationDialog, + skipAccountNameSuggestionDialog, handleUserInput, accountNameSuggestion, }: { snapId: SnapId; - skipConfirmation: boolean; + skipConfirmationDialog: boolean; + skipAccountNameSuggestionDialog: boolean; accountNameSuggestion: string; handleUserInput: (accepted: boolean) => Promise; }): Promise<{ accountName?: string }> { return await this.#withApprovalFlow(async (_) => { - // 1. Show the account **creation** confirmation dialog. + // 1. Show the account CREATION confirmation dialog. { // If confirmation dialog are skipped, we consider the account creation to be confirmed until the account name dialog is closed const success = - skipConfirmation || + skipConfirmationDialog || (await showAccountCreationDialog(snapId, this.#messenger)); if (!success) { @@ -228,24 +268,19 @@ class SnapKeyringImpl implements SnapKeyringCallbacks { } } - // 2. Show the account **renaming** confirmation dialog. + // 2. Show the account RENAMING confirmation dialog. Note that + // pre-installed Snaps can skip this dialog. { - const { success, name: accountName } = - await showAccountNameSuggestionDialog( - snapId, - this.#messenger, - accountNameSuggestion, - ); + const { success, accountName } = skipAccountNameSuggestionDialog + ? await this.#getAccountNameFromSuggestion(accountNameSuggestion) + : await this.#getAccountNameFromDialog(snapId, accountNameSuggestion); - if (!success) { - // User has cancelled account creation - await handleUserInput(success); + await handleUserInput(success); + if (!success) { throw new Error('User denied account creation'); } - await handleUserInput(success); - return { accountName }; } }); @@ -254,13 +289,13 @@ class SnapKeyringImpl implements SnapKeyringCallbacks { async #addAccountFinalize({ address, snapId, - skipConfirmation, + skipConfirmationDialog, accountName, onceSaved, }: { address: string; snapId: SnapId; - skipConfirmation: boolean; + skipConfirmationDialog: boolean; onceSaved: Promise; accountName?: string; }) { @@ -305,7 +340,7 @@ class SnapKeyringImpl implements SnapKeyringCallbacks { ); } - if (!skipConfirmation) { + if (!skipConfirmationDialog) { // TODO: Add events tracking to the dialog itself, so that events are more // "linked" to UI actions // User should now see the "Successfuly added account" page @@ -366,17 +401,24 @@ class SnapKeyringImpl implements SnapKeyringCallbacks { onceSaved: Promise, accountNameSuggestion: string = '', displayConfirmation: boolean = false, + displayAccountNameSuggestion: boolean = true, ) { assertIsValidSnapId(snapId); // If Snap is preinstalled and does not request confirmation, skip the confirmation dialog. - const skipConfirmation = isSnapPreinstalled(snapId) && !displayConfirmation; + const skipConfirmationDialog = + isSnapPreinstalled(snapId) && !displayConfirmation; + + // Only pre-installed Snaps can skip the account name suggestion dialog. + const skipAccountNameSuggestionDialog = + isSnapPreinstalled(snapId) && !displayAccountNameSuggestion; // First part of the flow, which includes confirmation dialogs (if not skipped). // Once confirmed, we resume the Snap execution. const { accountName } = await this.#addAccountConfirmations({ snapId, - skipConfirmation, + skipConfirmationDialog, + skipAccountNameSuggestionDialog, accountNameSuggestion, handleUserInput, }); @@ -388,7 +430,7 @@ class SnapKeyringImpl implements SnapKeyringCallbacks { void this.#addAccountFinalize({ address, snapId, - skipConfirmation, + skipConfirmationDialog, accountName, onceSaved, }); diff --git a/app/scripts/lib/snap-keyring/types.ts b/app/scripts/lib/snap-keyring/types.ts index db1e4e6c56f1..6e1314f7a6e8 100644 --- a/app/scripts/lib/snap-keyring/types.ts +++ b/app/scripts/lib/snap-keyring/types.ts @@ -4,6 +4,7 @@ import type { KeyringControllerGetAccountsAction } from '@metamask/keyring-contr import { GetSubjectMetadata } from '@metamask/permission-controller'; import { AccountsControllerGetAccountByAddressAction, + AccountsControllerListMultichainAccountsAction, AccountsControllerSetAccountNameAction, AccountsControllerSetSelectedAccountAction, } from '@metamask/accounts-controller'; @@ -34,6 +35,7 @@ export type SnapKeyringBuilderAllowActions = | AccountsControllerSetSelectedAccountAction | AccountsControllerGetAccountByAddressAction | AccountsControllerSetAccountNameAction + | AccountsControllerListMultichainAccountsAction | HandleSnapRequest | GetSnap | PreferencesControllerGetStateAction; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b021ce2ae41c..0f54a6801734 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1125,6 +1125,7 @@ export default class MetamaskController extends EventEmitter { 'AccountsController:setSelectedAccount', 'AccountsController:getAccountByAddress', 'AccountsController:setAccountName', + 'AccountsController:listMultichainAccounts', 'SnapController:handleRequest', 'SnapController:get', 'PreferencesController:getState', @@ -3227,12 +3228,6 @@ export default class MetamaskController extends EventEmitter { preferencesController, ), ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(solana) - setSolanaSupportEnabled: - preferencesController.setSolanaSupportEnabled.bind( - preferencesController, - ), - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(bitcoin) setBitcoinSupportEnabled: preferencesController.setBitcoinSupportEnabled.bind( @@ -6460,6 +6455,14 @@ export default class MetamaskController extends EventEmitter { 'CronjobController:getBackgroundEvents', origin, ), + getNetworkConfigurationByChainId: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'NetworkController:getNetworkConfigurationByChainId', + ), + getNetworkClientById: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'NetworkController:getNetworkClientById', + ), ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) handleSnapRpcRequest: (args) => this.handleSnapRequest({ ...args, origin }), diff --git a/package.json b/package.json index 232bc7d46962..89c36e385136 100644 --- a/package.json +++ b/package.json @@ -273,7 +273,7 @@ "@metamask/eth-json-rpc-middleware": "^15.1.2", "@metamask/eth-ledger-bridge-keyring": "^8.0.3", "@metamask/eth-sig-util": "^7.0.1", - "@metamask/eth-snap-keyring": "^11.0.0", + "@metamask/eth-snap-keyring": "^11.1.0", "@metamask/eth-token-tracker": "^10.0.2", "@metamask/eth-trezor-keyring": "^6.0.0", "@metamask/etherscan-link": "^3.0.0", diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index f247f507285d..d9e3ab05a95a 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -651,7 +651,6 @@ export enum MetaMetricsEventName { BridgeLinkClicked = 'Bridge Link Clicked', BitcoinSupportToggled = 'Bitcoin Support Toggled', BitcoinTestnetSupportToggled = 'Bitcoin Testnet Support Toggled', - SolanaSupportToggled = 'Solana Support Toggled', CurrentCurrency = 'Current Currency', DappViewed = 'Dapp Viewed', DecryptionApproved = 'Decryption Approved', diff --git a/shared/lib/accounts/accounts.ts b/shared/lib/accounts/accounts.ts new file mode 100644 index 000000000000..a6385a44f5a8 --- /dev/null +++ b/shared/lib/accounts/accounts.ts @@ -0,0 +1,27 @@ +import { InternalAccount } from '@metamask/keyring-internal-api'; + +/** + * Get the next available account name based on the suggestion and the list of + * accounts. + * + * @param accounts - The list of accounts to check for name availability + * @param nameSuggestion - The suggested name for the account + * @returns The next available account name based on the suggestion + */ +export function getUniqueAccountName( + accounts: InternalAccount[], + nameSuggestion: string, +): string { + let suffix = 1; + let candidateName = nameSuggestion; + + const isNameTaken = (name: string) => + accounts.some((account) => account.metadata.name === name); + + while (isNameTaken(candidateName)) { + suffix += 1; + candidateName = `${nameSuggestion} ${suffix}`; + } + + return candidateName; +} diff --git a/shared/lib/accounts/index.ts b/shared/lib/accounts/index.ts new file mode 100644 index 000000000000..5fcdc110a743 --- /dev/null +++ b/shared/lib/accounts/index.ts @@ -0,0 +1,4 @@ +export * from './accounts'; +export * from './bitcoin-wallet-snap'; +export * from './snaps'; +export * from './solana-wallet-snap'; diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 1496522b9118..fc1463745df4 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -1961,7 +1961,6 @@ "watchEthereumAccountEnabled": false, "bitcoinSupportEnabled": false, "bitcoinTestnetSupportEnabled": false, - "solanaSupportEnabled": false, "pendingApprovals": { "testApprovalId": { "id": "testApprovalId", diff --git a/test/e2e/flask/solana/check-balance.spec.ts b/test/e2e/flask/solana/check-balance.spec.ts index 9a1633cfdbee..aed18d52a9cd 100644 --- a/test/e2e/flask/solana/check-balance.spec.ts +++ b/test/e2e/flask/solana/check-balance.spec.ts @@ -8,7 +8,6 @@ describe('Check balance', function (this: Suite) { await withSolanaAccountSnap( { title: this.test?.fullTitle(), - solanaSupportEnabled: true, showNativeTokenAsMainBalance: true, }, async (driver) => { @@ -22,7 +21,6 @@ describe('Check balance', function (this: Suite) { await withSolanaAccountSnap( { title: this.test?.fullTitle(), - solanaSupportEnabled: true, showNativeTokenAsMainBalance: false, }, async (driver) => { @@ -36,7 +34,6 @@ describe('Check balance', function (this: Suite) { await withSolanaAccountSnap( { title: this.test?.fullTitle(), - solanaSupportEnabled: true, showNativeTokenAsMainBalance: true, mockCalls: true, }, @@ -51,7 +48,6 @@ describe('Check balance', function (this: Suite) { await withSolanaAccountSnap( { title: this.test?.fullTitle(), - solanaSupportEnabled: true, showNativeTokenAsMainBalance: false, mockCalls: true, }, diff --git a/test/e2e/flask/solana/common-solana.ts b/test/e2e/flask/solana/common-solana.ts index bd67296334f2..5d29a7bc18fb 100644 --- a/test/e2e/flask/solana/common-solana.ts +++ b/test/e2e/flask/solana/common-solana.ts @@ -439,14 +439,12 @@ export async function mockGetFeeForMessage(mockServer: Mockttp) { export async function withSolanaAccountSnap( { title, - solanaSupportEnabled, showNativeTokenAsMainBalance, mockCalls, mockSendTransaction, importAccount, }: { title?: string; - solanaSupportEnabled?: boolean; showNativeTokenAsMainBalance?: boolean; mockCalls?: boolean; mockSendTransaction?: boolean; @@ -455,9 +453,7 @@ export async function withSolanaAccountSnap( test: (driver: Driver, mockServer: Mockttp) => Promise, ) { console.log('Starting withSolanaAccountSnap'); - let fixtures = new FixtureBuilder().withPreferencesController({ - solanaSupportEnabled: solanaSupportEnabled ?? true, - }); + let fixtures = new FixtureBuilder(); if (!showNativeTokenAsMainBalance) { fixtures = fixtures.withPreferencesControllerShowNativeTokenAsMainBalanceDisabled(); diff --git a/ui/components/app/assets/nfts/nft-details/nft-details.stories.js b/ui/components/app/assets/nfts/nft-details/nft-details.stories.js index 76554d40d43e..a20eadb7b303 100644 --- a/ui/components/app/assets/nfts/nft-details/nft-details.stories.js +++ b/ui/components/app/assets/nfts/nft-details/nft-details.stories.js @@ -1,5 +1,5 @@ import React from 'react'; -import NftDetails from './nft-details'; +import { NftDetailsComponent } from './nft-details'; const nft = { name: 'Catnip Spicywright', @@ -18,6 +18,8 @@ const nft = { }, }; +const nftChainId = '0x1'; + export default { title: 'Components/App/NftsDetail', @@ -28,17 +30,18 @@ export default { }, args: { nft, + nftChainId, }, }; export const DefaultStory = (args) => { - return ; + return ; }; DefaultStory.storyName = 'Default'; export const NoImage = (args) => { - return ; + return ; }; NoImage.args = { diff --git a/ui/components/app/assets/nfts/nft-details/nft-details.test.js b/ui/components/app/assets/nfts/nft-details/nft-details.test.js index e378fb4bb2bf..c93a294ab5a3 100644 --- a/ui/components/app/assets/nfts/nft-details/nft-details.test.js +++ b/ui/components/app/assets/nfts/nft-details/nft-details.test.js @@ -1,5 +1,6 @@ import { fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; +import { useParams } from 'react-router-dom'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import copyToClipboard from 'copy-to-clipboard'; @@ -39,6 +40,7 @@ jest.mock('react-router-dom', () => ({ useHistory: () => ({ push: mockHistoryPush, }), + useParams: jest.fn(), })); jest.mock('../../../../../ducks/send/index.js', () => ({ @@ -72,6 +74,7 @@ describe('NFT Details', () => { }); it('should match minimal props and state snapshot', async () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.GOERLI }); getAssetImageURL.mockResolvedValue( 'https://bafybeiclzx7zfjvuiuwobn5ip3ogc236bjqfjzoblumf4pau4ep6dqramu.ipfs.dweb.link', ); @@ -88,6 +91,7 @@ describe('NFT Details', () => { }); it(`should route to '/' route when the back button is clicked`, () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET }); const { queryByTestId } = renderWithProvider( , mockStore, @@ -101,6 +105,7 @@ describe('NFT Details', () => { }); it(`should call removeAndIgnoreNFT with proper nft details and route to '/' when removing nft`, async () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET }); const { queryByTestId } = renderWithProvider( , mockStore, @@ -122,6 +127,7 @@ describe('NFT Details', () => { }); it(`should call setRemoveNftMessage with error when removeAndIgnoreNft fails and route to '/'`, async () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET }); const { queryByTestId } = renderWithProvider( , mockStore, @@ -145,6 +151,7 @@ describe('NFT Details', () => { }); it('should copy nft address', async () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET }); const { queryByTestId } = renderWithProvider( , mockStore, @@ -157,6 +164,7 @@ describe('NFT Details', () => { }); it('should navigate to draft transaction send route with ERC721 data', async () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET }); const nftProps = { nft: nfts[5], }; @@ -180,6 +188,7 @@ describe('NFT Details', () => { }); it('should not render send button if isCurrentlyOwned is false', () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET }); const sixthNftProps = { nft: nfts[6], }; @@ -195,6 +204,7 @@ describe('NFT Details', () => { }); it('should render send button if it is an ERC1155', () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET }); const nftProps = { nft: nfts[1], }; @@ -211,6 +221,7 @@ describe('NFT Details', () => { describe(`Alternative Networks' OpenSea Links`, () => { it('should open opeasea link with goeli testnet chainId', async () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.GOERLI }); global.platform = { openTab: jest.fn() }; const { queryByTestId } = renderWithProvider( @@ -234,6 +245,7 @@ describe('NFT Details', () => { }); it('should open tab to mainnet opensea url with nft info', async () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET }); global.platform = { openTab: jest.fn() }; const mainnetState = { @@ -266,11 +278,15 @@ describe('NFT Details', () => { }); it('should open tab to polygon opensea url with nft info', async () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.POLYGON }); const polygonState = { ...mockState, metamask: { ...mockState.metamask, - ...mockNetworkState({ chainId: CHAIN_IDS.POLYGON }), + ...mockNetworkState({ + chainId: CHAIN_IDS.POLYGON, + nickname: 'polygon', + }), }, }; const polygonMockStore = configureMockStore([thunk])(polygonState); @@ -296,11 +312,15 @@ describe('NFT Details', () => { }); it('should open tab to sepolia opensea url with nft info', async () => { + useParams.mockReturnValue({ chainId: CHAIN_IDS.SEPOLIA }); const sepoliaState = { ...mockState, metamask: { ...mockState.metamask, - ...mockNetworkState({ chainId: CHAIN_IDS.SEPOLIA }), + ...mockNetworkState({ + chainId: CHAIN_IDS.SEPOLIA, + nickname: 'sepolia', + }), }, }; const sepoliaMockStore = configureMockStore([thunk])(sepoliaState); @@ -326,6 +346,7 @@ describe('NFT Details', () => { }); it('should not render opensea redirect button', async () => { + useParams.mockReturnValue({ chainId: '0x99' }); const randomNetworkState = { ...mockState, metamask: { diff --git a/ui/components/app/assets/nfts/nft-details/nft-details.tsx b/ui/components/app/assets/nfts/nft-details/nft-details.tsx index a0de608a0e56..28e7ea9f0f95 100644 --- a/ui/components/app/assets/nfts/nft-details/nft-details.tsx +++ b/ui/components/app/assets/nfts/nft-details/nft-details.tsx @@ -1,9 +1,10 @@ import React, { useEffect, useContext } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useParams } from 'react-router-dom'; import { isEqual } from 'lodash'; import { getTokenTrackerLink, getAccountLink } from '@metamask/etherscan-link'; import { Nft } from '@metamask/assets-controllers'; +import { Hex } from '@metamask/utils'; import { TextColor, IconColor, @@ -19,8 +20,15 @@ import { import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { shortenAddress } from '../../../../../helpers/utils/util'; import { getNftImageAlt } from '../../../../../helpers/utils/nfts'; -import { getCurrentChainId } from '../../../../../../shared/modules/selectors/networks'; -import { getCurrentNetwork, getIpfsGateway } from '../../../../../selectors'; +import { + getCurrentChainId, + getNetworkConfigurationsByChainId, +} from '../../../../../../shared/modules/selectors/networks'; +import { + getCurrentNetwork, + getIpfsGateway, + getNetworkConfigurationIdByChainId, +} from '../../../../../selectors'; import { ASSET_ROUTE, DEFAULT_ROUTE, @@ -31,6 +39,8 @@ import { removeAndIgnoreNft, setRemoveNftMessage, setNewNftAddedMessage, + setActiveNetworkWithError, + setSwitchedNetworkDetails, } from '../../../../../store/actions'; import { CHAIN_IDS } from '../../../../../../shared/constants/network'; import NftOptions from '../nft-options/nft-options'; @@ -71,13 +81,20 @@ import { Numeric } from '../../../../../../shared/modules/Numeric'; // eslint-disable-next-line import/no-restricted-paths import { addUrlProtocolPrefix } from '../../../../../../app/scripts/lib/util'; import useGetAssetImageUrl from '../../../../../hooks/useGetAssetImageUrl'; +import { getImageForChainId } from '../../../../../selectors/multichain'; import NftDetailInformationRow from './nft-detail-information-row'; import NftDetailInformationFrame from './nft-detail-information-frame'; import NftDetailDescription from './nft-detail-description'; const MAX_TOKEN_ID_LENGTH = 15; -export default function NftDetails({ nft }: { nft: Nft }) { +export function NftDetailsComponent({ + nft, + nftChainId, +}: { + nft: Nft; + nftChainId: string; +}) { const { image, imageOriginal, @@ -104,6 +121,14 @@ export default function NftDetails({ nft }: { nft: Nft }) { const currency = useSelector(getCurrentCurrency); const selectedNativeConversionRate = useSelector(getConversionRate); + const nftNetworkConfigs = useSelector(getNetworkConfigurationsByChainId); + const nftChainNetwork = nftNetworkConfigs[nftChainId as Hex]; + const nftChainImage = getImageForChainId(nftChainId as string); + const networks = useSelector(getNetworkConfigurationIdByChainId) as Record< + string, + string + >; + const [addressCopied, handleAddressCopy] = useCopyToClipboard(); const nftImageAlt = getNftImageAlt(nft); @@ -240,7 +265,28 @@ export default function NftDetails({ nft }: { nft: Nft }) { const sendDisabled = standard !== TokenStandard.ERC721 && standard !== TokenStandard.ERC1155; + const setCorrectChain = async () => { + // If we aren't presently on the chain of the nft, change to it + if (nftChainId !== currentChain.chainId) { + try { + const networkConfigurationId = networks[nftChainId as Hex]; + await dispatch(setActiveNetworkWithError(networkConfigurationId)); + await dispatch( + setSwitchedNetworkDetails({ + networkClientId: networkConfigurationId, + }), + ); + } catch (err) { + console.error(`Failed to switch chains for NFT. + Target chainId: ${nftChainId}, Current chainId: ${currentChain.chainId}. + ${err}`); + throw err; + } + } + }; + const onSend = async () => { + await setCorrectChain(); await dispatch( startNewDraftTransaction({ type: AssetType.NFT, @@ -350,8 +396,8 @@ export default function NftDetails({ nft }: { nft: Nft }) { ); } + +function NftDetails({ nft }: { nft: Nft }) { + const { chainId } = useParams(); + + return ; +} + +export default NftDetails; diff --git a/ui/components/multichain/create-named-snap-account/create-named-snap-account.tsx b/ui/components/multichain/create-named-snap-account/create-named-snap-account.tsx index d04763902247..fd6ae6eb7cdb 100644 --- a/ui/components/multichain/create-named-snap-account/create-named-snap-account.tsx +++ b/ui/components/multichain/create-named-snap-account/create-named-snap-account.tsx @@ -8,6 +8,7 @@ import { Box, ModalHeader } from '../../component-library'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { getMostRecentOverviewPage } from '../../../ducks/history/history'; import { getNextAvailableAccountName } from '../../../store/actions'; +import { getUniqueAccountName } from '../../../../shared/lib/accounts'; export type CreateNamedSnapAccountProps = { /** @@ -44,25 +45,9 @@ export const CreateNamedSnapAccount: React.FC = ({ const getNextAccountName = useCallback( async (accounts: InternalAccount[]): Promise => { // If a snap-suggested account name exists, use it as a base - if (snapSuggestedAccountName) { - let suffix = 1; - let candidateName = snapSuggestedAccountName; - - // Check if the name is already taken - const isNameTaken = (name: string) => - accounts.some((account) => account.metadata.name === name); - - // Keep incrementing suffix until we find an available name - while (isNameTaken(candidateName)) { - suffix += 1; - candidateName = `${snapSuggestedAccountName} ${suffix}`; - } - - return candidateName; - } - - // If no snap-suggested name, use the next available account name - return getNextAvailableAccountName(KeyringTypes.snap); + return snapSuggestedAccountName + ? getUniqueAccountName(accounts, snapSuggestedAccountName) + : getNextAvailableAccountName(KeyringTypes.snap); }, [], ); diff --git a/ui/pages/confirmations/components/confirm/footer/footer.tsx b/ui/pages/confirmations/components/confirm/footer/footer.tsx index 095caaee88c3..bd028a6e617e 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.tsx @@ -1,7 +1,9 @@ -import { TransactionMeta } from '@metamask/transaction-controller'; import { providerErrors, serializeError } from '@metamask/rpc-errors'; +import { TransactionMeta } from '@metamask/transaction-controller'; import React, { useCallback, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { MetaMetricsEventLocation } from '../../../../../../shared/constants/metametrics'; +import { isCorrectDeveloperTransactionType } from '../../../../../../shared/lib/confirmation.utils'; import { ConfirmAlertModal } from '../../../../../components/app/alert-system/confirm-alert-modal'; import { Button, @@ -10,13 +12,15 @@ import { IconName, } from '../../../../../components/component-library'; import { Footer as PageFooter } from '../../../../../components/multichain/pages/page'; +import { Alert } from '../../../../../ducks/confirm-alerts/confirm-alerts'; +import { clearConfirmTransaction } from '../../../../../ducks/confirm-transaction/confirm-transaction.duck'; +import { Severity } from '../../../../../helpers/constants/design-system'; +import useAlerts from '../../../../../hooks/useAlerts'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { doesAddressRequireLedgerHidConnection, getCustomNonceValue, } from '../../../../../selectors'; - -import useAlerts from '../../../../../hooks/useAlerts'; import { rejectPendingApproval, resolvePendingApproval, @@ -26,14 +30,10 @@ import { ///: END:ONLY_INCLUDE_IF updateCustomNonce, } from '../../../../../store/actions'; -import { isSignatureTransactionType } from '../../../utils'; import { useConfirmContext } from '../../../context/confirm'; -import { getConfirmationSender } from '../utils'; -import { MetaMetricsEventLocation } from '../../../../../../shared/constants/metametrics'; -import { Alert } from '../../../../../ducks/confirm-alerts/confirm-alerts'; -import { Severity } from '../../../../../helpers/constants/design-system'; -import { isCorrectDeveloperTransactionType } from '../../../../../../shared/lib/confirmation.utils'; import { useOriginThrottling } from '../../../hooks/useOriginThrottling'; +import { isSignatureTransactionType } from '../../../utils'; +import { getConfirmationSender } from '../utils'; import OriginThrottleModal from './origin-throttle-modal'; export type OnCancelHandler = ({ @@ -178,6 +178,12 @@ const Footer = () => { (!isScrollToBottomCompleted && !isSignature) || hardwareWalletRequiresConnection; + const resetTransactionState = () => { + dispatch(updateCustomNonce('')); + dispatch(setNextNonce('')); + dispatch(clearConfirmTransaction()); + }; + const onCancel = useCallback( ({ location }: { location?: MetaMetricsEventLocation }) => { if (!currentConfirmation) { @@ -190,8 +196,7 @@ const Footer = () => { dispatch( rejectPendingApproval(currentConfirmation.id, serializeError(error)), ); - dispatch(updateCustomNonce('')); - dispatch(setNextNonce('')); + resetTransactionState(); }, [currentConfirmation], ); @@ -223,8 +228,7 @@ const Footer = () => { } else { dispatch(resolvePendingApproval(currentConfirmation.id, undefined)); } - dispatch(updateCustomNonce('')); - dispatch(setNextNonce('')); + resetTransactionState(); }, [currentConfirmation, customNonceValue]); const handleFooterCancel = useCallback(() => { diff --git a/ui/pages/settings/experimental-tab/experimental-tab.component.tsx b/ui/pages/settings/experimental-tab/experimental-tab.component.tsx index 2fb39913cca8..f7989219cef7 100644 --- a/ui/pages/settings/experimental-tab/experimental-tab.component.tsx +++ b/ui/pages/settings/experimental-tab/experimental-tab.component.tsx @@ -36,10 +36,6 @@ import { SurveyUrl } from '../../../../shared/constants/urls'; type ExperimentalTabProps = { watchAccountEnabled: boolean; setWatchAccountEnabled: (value: boolean) => void; - ///: BEGIN:ONLY_INCLUDE_IF(solana) - solanaSupportEnabled: boolean; - setSolanaSupportEnabled: (value: boolean) => void; - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(bitcoin) bitcoinSupportEnabled: boolean; setBitcoinSupportEnabled: (value: boolean) => void; @@ -323,46 +319,6 @@ export default class ExperimentalTab extends PureComponent } ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(solana) - renderSolanaSupport() { - const { t, trackEvent } = this.context; - const { solanaSupportEnabled, setSolanaSupportEnabled } = this.props; - - return ( - <> - - {t('solanaSupportSectionTitle')} - - {this.renderToggleSection({ - title: t('solanaSupportToggleTitle'), - description: t('solanaSupportToggleDescription'), - toggleValue: solanaSupportEnabled, - toggleCallback: (value) => { - trackEvent({ - event: MetaMetricsEventName.SolanaSupportToggled, - category: MetaMetricsEventCategory.Settings, - properties: { - enabled: !value, - }, - }); - setSolanaSupportEnabled(!value); - }, - toggleContainerDataTestId: 'solana-support-toggle-div', - toggleDataTestId: 'solana-support-toggle', - toggleOffLabel: t('off'), - toggleOnLabel: t('on'), - })} - - ); - } - ///: END:ONLY_INCLUDE_IF - render() { return (
@@ -387,11 +343,6 @@ export default class ExperimentalTab extends PureComponent this.renderBitcoinSupport() ///: END:ONLY_INCLUDE_IF } - { - ///: BEGIN:ONLY_INCLUDE_IF(solana) - this.renderSolanaSupport() - ///: END:ONLY_INCLUDE_IF - }
); } diff --git a/ui/pages/settings/experimental-tab/experimental-tab.container.ts b/ui/pages/settings/experimental-tab/experimental-tab.container.ts index 8bc4fc8aec1d..c3f9377597b1 100644 --- a/ui/pages/settings/experimental-tab/experimental-tab.container.ts +++ b/ui/pages/settings/experimental-tab/experimental-tab.container.ts @@ -12,14 +12,8 @@ import { setPetnamesEnabled, setFeatureNotificationsEnabled, setWatchEthereumAccountEnabled, - ///: BEGIN:ONLY_INCLUDE_IF(solana) - setSolanaSupportEnabled, - ///: END:ONLY_INCLUDE_IF } from '../../../store/actions'; import { - ///: BEGIN:ONLY_INCLUDE_IF(solana) - getIsSolanaSupportEnabled, - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(bitcoin) getIsBitcoinSupportEnabled, getIsBitcoinTestnetSupportEnabled, @@ -41,9 +35,6 @@ const mapStateToProps = (state: MetaMaskReduxState) => { const petnamesEnabled = getPetnamesEnabled(state); const featureNotificationsEnabled = getFeatureNotificationsEnabled(state); return { - ///: BEGIN:ONLY_INCLUDE_IF(solana) - solanaSupportEnabled: getIsSolanaSupportEnabled(state), - ///: END:ONLY_INCLUDE_IF watchAccountEnabled: getIsWatchEthereumAccountEnabled(state), ///: BEGIN:ONLY_INCLUDE_IF(bitcoin) bitcoinSupportEnabled: getIsBitcoinSupportEnabled(state), @@ -61,9 +52,6 @@ const mapDispatchToProps = (dispatch: MetaMaskReduxDispatch) => { return { setWatchAccountEnabled: (value: boolean) => setWatchEthereumAccountEnabled(value), - ///: BEGIN:ONLY_INCLUDE_IF(solana) - setSolanaSupportEnabled: (value: boolean) => setSolanaSupportEnabled(value), - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(bitcoin) setBitcoinSupportEnabled: (value: boolean) => setBitcoinSupportEnabled(value), diff --git a/ui/pages/settings/experimental-tab/experimental-tab.test.js b/ui/pages/settings/experimental-tab/experimental-tab.test.js index 7bf4b38644e8..9df1678ccb06 100644 --- a/ui/pages/settings/experimental-tab/experimental-tab.test.js +++ b/ui/pages/settings/experimental-tab/experimental-tab.test.js @@ -30,7 +30,7 @@ describe('ExperimentalTab', () => { const { getAllByRole } = render(); const toggle = getAllByRole('checkbox'); - expect(toggle).toHaveLength(6); + expect(toggle).toHaveLength(5); }); it('enables add account snap', async () => { @@ -90,21 +90,4 @@ describe('ExperimentalTab', () => { expect(setBitcoinSupportEnabled).toHaveBeenNthCalledWith(1, true); }); }); - - it('enables the experimental solana account feature', async () => { - const setSolanaSupportEnabled = jest.fn(); - const { getByTestId } = render( - {}, - { - setSolanaSupportEnabled, - solanaSupportEnabled: false, - }, - ); - const toggle = getByTestId('solana-support-toggle'); - - fireEvent.click(toggle); - await waitFor(() => { - expect(setSolanaSupportEnabled).toHaveBeenNthCalledWith(1, true); - }); - }); }); diff --git a/ui/selectors/multichain/networks.test.ts b/ui/selectors/multichain/networks.test.ts index b5efe28b7173..c45afaf3fa62 100644 --- a/ui/selectors/multichain/networks.test.ts +++ b/ui/selectors/multichain/networks.test.ts @@ -14,6 +14,7 @@ import { MOCK_ACCOUNT_BIP122_P2WPKH, MOCK_ACCOUNT_SOLANA_MAINNET, } from '../../../test/data/mock-accounts'; +import { RemoteFeatureFlagsState } from '../remote-feature-flags'; import { type MultichainNetworkControllerState, getNonEvmMultichainNetworkConfigurationsByChainId, @@ -25,8 +26,9 @@ import { type TestState = AccountsState & MultichainNetworkControllerState & - NetworkState & { - metamask: { solanaSupportEnabled: boolean; bitcoinSupportEnabled: boolean }; + NetworkState & + RemoteFeatureFlagsState & { + metamask: { bitcoinSupportEnabled: boolean }; }; const mockNonEvmNetworks: Record = @@ -103,7 +105,9 @@ const mockEvmNetworksWithOldConfig: Record = { const mockState: TestState = { metamask: { - solanaSupportEnabled: true, + remoteFeatureFlags: { + addSolanaAccount: true, + }, bitcoinSupportEnabled: true, multichainNetworkConfigurationsByChainId: { ...mockNonEvmNetworks, @@ -159,7 +163,10 @@ describe('Multichain network selectors', () => { ...mockState, metamask: { ...mockState.metamask, - solanaSupportEnabled: false, + remoteFeatureFlags: { + ...mockState.metamask.remoteFeatureFlags, + addSolanaAccount: false, + }, }, }; @@ -197,7 +204,10 @@ describe('Multichain network selectors', () => { ...mockState, metamask: { ...mockState.metamask, - solanaSupportEnabled: false, + remoteFeatureFlags: { + ...mockState.metamask.remoteFeatureFlags, + addSolanaAccount: false, + }, bitcoinSupportEnabled: false, }, }; @@ -214,7 +224,10 @@ describe('Multichain network selectors', () => { ...mockState, metamask: { ...mockState.metamask, - solanaSupportEnabled: false, + remoteFeatureFlags: { + ...mockState.metamask.remoteFeatureFlags, + addSolanaAccount: false, + }, bitcoinSupportEnabled: false, internalAccounts: { ...mockState.metamask.internalAccounts, @@ -241,7 +254,10 @@ describe('Multichain network selectors', () => { ...mockState, metamask: { ...mockState.metamask, - solanaSupportEnabled: false, + remoteFeatureFlags: { + ...mockState.metamask.remoteFeatureFlags, + addSolanaAccount: false, + }, bitcoinSupportEnabled: false, internalAccounts: { ...mockState.metamask.internalAccounts, @@ -268,7 +284,10 @@ describe('Multichain network selectors', () => { ...mockState, metamask: { ...mockState.metamask, - solanaSupportEnabled: false, + remoteFeatureFlags: { + ...mockState.metamask.remoteFeatureFlags, + addSolanaAccount: false, + }, bitcoinSupportEnabled: false, internalAccounts: { ...mockState.metamask.internalAccounts, diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 44e192ca86e6..13dc2eee642a 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -127,6 +127,7 @@ import { getMultichainBalances, getMultichainNetworkProviders, } from './multichain'; +import { getRemoteFeatureFlags } from './remote-feature-flags'; /** `appState` slice selectors */ @@ -2798,15 +2799,14 @@ export function getIsBitcoinTestnetSupportEnabled(state) { } /** - * Get the state of the `solanaSupportEnabled` flag. + * Get the state of the `solanaSupportEnabled` remote feature flag. * * @param {*} state - * @returns The state of the `solanaSupportEnabled` flag. + * @returns The state of the `solanaSupportEnabled` remote feature flag. */ export function getIsSolanaSupportEnabled(state) { - // See `getIsBitcoinSupportEnabled` for details. - const { solanaSupportEnabled } = state.metamask; - return Boolean(solanaSupportEnabled); + const { addSolanaAccount } = getRemoteFeatureFlags(state); + return Boolean(addSolanaAccount); } export function getIsCustomNetwork(state) { diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 394d5d8868c4..dc201bb8730c 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -5134,16 +5134,6 @@ export async function setBitcoinTestnetSupportEnabled(value: boolean) { } ///: END:ONLY_INCLUDE_IF -///: BEGIN:ONLY_INCLUDE_IF(solana) -export async function setSolanaSupportEnabled(value: boolean) { - try { - await submitRequestToBackground('setSolanaSupportEnabled', [value]); - } catch (error) { - logErrorWithMessage(error); - } -} -///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) export async function setAddSnapAccountEnabled(value: boolean): Promise { try { diff --git a/yarn.lock b/yarn.lock index 5e71c764f82d..5b99cead6c52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5329,7 +5329,7 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-snap-keyring@npm:^11.0.0, @metamask/eth-snap-keyring@npm:^11.1.0": +"@metamask/eth-snap-keyring@npm:^11.1.0": version: 11.1.0 resolution: "@metamask/eth-snap-keyring@npm:11.1.0" dependencies: @@ -26953,7 +26953,7 @@ __metadata: "@metamask/eth-json-rpc-provider": "npm:^4.1.6" "@metamask/eth-ledger-bridge-keyring": "npm:^8.0.3" "@metamask/eth-sig-util": "npm:^7.0.1" - "@metamask/eth-snap-keyring": "npm:^11.0.0" + "@metamask/eth-snap-keyring": "npm:^11.1.0" "@metamask/eth-token-tracker": "npm:^10.0.2" "@metamask/eth-trezor-keyring": "npm:^6.0.0" "@metamask/etherscan-link": "npm:^3.0.0"