diff --git a/packages/examples/README.md b/packages/examples/README.md index cee01f973d..02631122bb 100644 --- a/packages/examples/README.md +++ b/packages/examples/README.md @@ -66,9 +66,6 @@ The following is a list of the snaps in this directory. - [**`packages/notifications`**](./packages/notifications): This snap demonstrates how to use the `snap_notify` method to display notifications to the user, either as a MetaMask notification or as a desktop notification. -- [**`packages/settings-page`**](./packages/settings-page): - This snap demonstrates how to use `endowment:page-settings` permission, - showing a settings page to the user. - [**`packages/transaction-insights`**](./packages/transaction-insights): This snap demonstrates how to use `endowment:transaction-insights` permission, and provide transaction insights to the user. @@ -90,7 +87,8 @@ The following is a list of the snaps in this directory. how the Snaps platform handles errors thrown by snaps. - [**`packages/preinstalled`**](./packages/preinstalled): This snap demonstrates preinstalled snaps, i.e., snaps that are installed in the MetaMask extension - by default. + by default. It also demonstrates the use of the `endowment:page-settings` permission, + showing a settings page to the user. - [**`packages/send-flow`**](./packages/send-flow): This snap demonstrates a simple send flow using custom UI. - [**`packages/wasm`**](./packages/wasm): This snap demonstrates how diff --git a/packages/examples/packages/preinstalled/README.md b/packages/examples/packages/preinstalled/README.md index f286529d83..39e9ea052f 100644 --- a/packages/examples/packages/preinstalled/README.md +++ b/packages/examples/packages/preinstalled/README.md @@ -5,3 +5,26 @@ MetaMask extension by default. > [!NOTE] > Preinstalled Snaps are primarily for internal use by MetaMask. + +This snap also demonstrates how to use the `endowment:page-settings` permission to show a settings page that leverages custom UI components. + +> [!NOTE] +> This endowment is initially restricted to preinstalled snaps only. + +## Snap manifest + +The manifest of this snap includes the `endowment:page-settings` permission: + +```json +{ + "initialPermissions": { + "endowment:page-settings": {} + } +} +``` + +This permission does not require any additional configuration. + +## Snap usage + +This snap exposes an `onSettingsPage` handler, which returns the UI to be shown. diff --git a/packages/examples/packages/preinstalled/snap.manifest.json b/packages/examples/packages/preinstalled/snap.manifest.json index 601b19bbb8..defc45de84 100644 --- a/packages/examples/packages/preinstalled/snap.manifest.json +++ b/packages/examples/packages/preinstalled/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "WF9+BfnWRznf3otVkb8p0M38ULIIPywefZdHiAAk7jM=", + "shasum": "uDEC4wnc/rEN1OKqXidpOvejRN3h3dR85cvQLnLmJB8=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -20,7 +20,9 @@ "endowment:rpc": { "dapps": true }, - "snap_dialog": {} + "snap_dialog": {}, + "endowment:page-settings": {}, + "snap_manageState": {} }, "platformVersion": "6.13.0", "manifestVersion": "0.1" diff --git a/packages/examples/packages/preinstalled/src/components/dialog.tsx b/packages/examples/packages/preinstalled/src/components/Dialog.tsx similarity index 100% rename from packages/examples/packages/preinstalled/src/components/dialog.tsx rename to packages/examples/packages/preinstalled/src/components/Dialog.tsx diff --git a/packages/examples/packages/preinstalled/src/components/result.tsx b/packages/examples/packages/preinstalled/src/components/Result.tsx similarity index 100% rename from packages/examples/packages/preinstalled/src/components/result.tsx rename to packages/examples/packages/preinstalled/src/components/Result.tsx diff --git a/packages/examples/packages/settings-page/src/components/SettingsPage.tsx b/packages/examples/packages/preinstalled/src/components/Settings.tsx similarity index 93% rename from packages/examples/packages/settings-page/src/components/SettingsPage.tsx rename to packages/examples/packages/preinstalled/src/components/Settings.tsx index 9bcb37c471..e66f78e14d 100644 --- a/packages/examples/packages/settings-page/src/components/SettingsPage.tsx +++ b/packages/examples/packages/preinstalled/src/components/Settings.tsx @@ -10,7 +10,7 @@ import { type SnapComponent, } from '@metamask/snaps-sdk/jsx'; -export type SettingsPageProps = { +export type SettingsProps = { setting1?: boolean; setting2?: 'option1' | 'option2'; setting3?: 'option1' | 'option2'; @@ -25,7 +25,7 @@ export type SettingsPageProps = { * @param param.setting3 - The third setting. * @returns The settings page component. */ -export const SettingsPage: SnapComponent = ({ +export const Settings: SnapComponent = ({ setting1, setting2, setting3, diff --git a/packages/examples/packages/preinstalled/src/components/index.ts b/packages/examples/packages/preinstalled/src/components/index.ts index b3e97d2d8f..265d85397b 100644 --- a/packages/examples/packages/preinstalled/src/components/index.ts +++ b/packages/examples/packages/preinstalled/src/components/index.ts @@ -1,2 +1,3 @@ -export * from './dialog'; -export * from './result'; +export * from './Dialog'; +export * from './Result'; +export * from './Settings'; diff --git a/packages/examples/packages/preinstalled/src/index.test.tsx b/packages/examples/packages/preinstalled/src/index.test.tsx index 4fe235d886..12ddeb69fb 100644 --- a/packages/examples/packages/preinstalled/src/index.test.tsx +++ b/packages/examples/packages/preinstalled/src/index.test.tsx @@ -1,7 +1,7 @@ import { expect } from '@jest/globals'; import { installSnap } from '@metamask/snaps-jest'; -import { Dialog, Result } from './components'; +import { Dialog, Result, Settings } from './components'; describe('onRpcRequest', () => { it('throws an error if the requested method does not exist', async () => { @@ -61,4 +61,42 @@ describe('onRpcRequest', () => { expect(result).toRespondWith('foo bar'); }); }); + + describe('getSettings', () => { + it('returns the settings state', async () => { + const { request, onSettingsPage } = await installSnap(); + + const settingPageResponse = await onSettingsPage(); + + const screen = settingPageResponse.getInterface(); + + await screen.clickElement('setting1'); + + await screen.selectFromRadioGroup('setting2', 'option1'); + + await screen.selectInDropdown('setting3', 'option2'); + + expect( + await request({ + method: 'getSettings', + }), + ).toRespondWith({ + setting1: true, + setting2: 'option1', + setting3: 'option2', + }); + }); + }); +}); + +describe('onSettingsPage', () => { + it('returns custom UI', async () => { + const { onSettingsPage } = await installSnap(); + + const response = await onSettingsPage(); + + const screen = response.getInterface(); + + expect(screen).toRender(); + }); }); diff --git a/packages/examples/packages/preinstalled/src/index.tsx b/packages/examples/packages/preinstalled/src/index.tsx index ad7ee359fb..3132be492c 100644 --- a/packages/examples/packages/preinstalled/src/index.tsx +++ b/packages/examples/packages/preinstalled/src/index.tsx @@ -1,23 +1,32 @@ -import { MethodNotFoundError } from '@metamask/snaps-sdk'; +import { MethodNotFoundError, UserInputEventType } from '@metamask/snaps-sdk'; import type { OnRpcRequestHandler, + OnSettingsPageHandler, OnUserInputHandler, } from '@metamask/snaps-sdk'; -import { Dialog, Result } from './components'; +import { Dialog, Result, Settings } from './components'; + +type SnapState = { + setting1?: boolean; + setting2?: 'option1' | 'option2'; + setting3?: 'option1' | 'option2'; +}; /** * Handle incoming JSON-RPC requests from the dapp, sent through the * `wallet_invokeSnap` method. This handler handles a single method: * * - `showDialog` - Opens a dialog. + * - `getSettings`: Get the settings state from the snap state. * * @param params - The request parameters. * @param params.request - The JSON-RPC request object. * @returns The JSON-RPC response. * @see https://docs.metamask.io/snaps/reference/exports/#onrpcrequest * @see https://docs.metamask.io/snaps/reference/rpc-api/#wallet_invokesnap - * @see https://docs.metamask.io/snaps/reference/rpc-api/#snap_notify + * @see https://docs.metamask.io/snaps/reference/snaps-api/#snap_dialog + * @see https://docs.metamask.io/snaps/reference/snaps-api/#snap_managestate */ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { switch (request.method) { @@ -29,18 +38,62 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { }, }); + case 'getSettings': + return await snap.request({ + method: 'snap_manageState', + params: { + operation: 'get', + encrypted: false, + }, + }); + default: // eslint-disable-next-line @typescript-eslint/no-throw-literal throw new MethodNotFoundError({ method: request.method }); } }; +/** + * Handle incoming settings page requests from the MetaMask clients. + * + * @returns A static panel rendered with custom UI. + * @see https://docs.metamask.io/snaps/reference/exports/#onsettingspage + */ +export const onSettingsPage: OnSettingsPageHandler = async () => { + const state: SnapState | null = await snap.request({ + method: 'snap_manageState', + params: { + operation: 'get', + encrypted: false, + }, + }); + + return { + content: ( + + ), + }; +}; + +/** + * Handle incoming user events coming from the MetaMask clients open interfaces. + * + * @param params - The event parameters. + * @param params.event - The event object containing the event type, name and value. + * @param params.id - The unique identifier of the open interface. + * @param params.context - The context object containing the current state of the open interface. + * @see https://docs.metamask.io/snaps/reference/exports/#onuserinput + */ export const onUserInput: OnUserInputHandler = async ({ event, id, context, }) => { - if (event.type === 'ButtonClickEvent') { + if (event.type === UserInputEventType.ButtonClickEvent) { if (event.name === 'cancel') { await snap.request({ method: 'snap_resolveInterface', @@ -62,7 +115,7 @@ export const onUserInput: OnUserInputHandler = async ({ } } - if (event.type === 'FormSubmitEvent') { + if (event.type === UserInputEventType.FormSubmitEvent) { if (event.name === 'form') { const value = String(event.value['custom-input']); await snap.request({ @@ -75,4 +128,31 @@ export const onUserInput: OnUserInputHandler = async ({ }); } } + + if ( + event.type === UserInputEventType.InputChangeEvent && + (event.name === 'setting1' || + event.name === 'setting2' || + event.name === 'setting3') + ) { + const state = await snap.request({ + method: 'snap_manageState', + params: { + operation: 'get', + encrypted: false, + }, + }); + + await snap.request({ + method: 'snap_manageState', + params: { + operation: 'update', + encrypted: false, + newState: { + ...state, + [event.name]: event.value, + }, + }, + }); + } }; diff --git a/packages/examples/packages/settings-page/.depcheckrc.json b/packages/examples/packages/settings-page/.depcheckrc.json deleted file mode 100644 index c437c59cd2..0000000000 --- a/packages/examples/packages/settings-page/.depcheckrc.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "ignore-patterns": ["dist", "coverage"], - "ignores": [ - "@lavamoat/allow-scripts", - "@lavamoat/preinstall-always-fail", - "@metamask/auto-changelog", - "@metamask/eslint-*", - "@types/*", - "@typescript-eslint/*", - "eslint-config-*", - "eslint-plugin-*", - "jest-silent-reporter", - "prettier-plugin-packagejson", - "ts-node", - "typedoc", - "typescript" - ] -} diff --git a/packages/examples/packages/settings-page/.eslintrc.js b/packages/examples/packages/settings-page/.eslintrc.js deleted file mode 100644 index a47fd0b65d..0000000000 --- a/packages/examples/packages/settings-page/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - extends: ['../../.eslintrc.js'], - - parserOptions: { - tsconfigRootDir: __dirname, - }, -}; diff --git a/packages/examples/packages/settings-page/CHANGELOG.md b/packages/examples/packages/settings-page/CHANGELOG.md deleted file mode 100644 index 720e00537e..0000000000 --- a/packages/examples/packages/settings-page/CHANGELOG.md +++ /dev/null @@ -1,10 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -[Unreleased]: https://github.com/MetaMask/snaps/ diff --git a/packages/examples/packages/settings-page/LICENSE.APACHE2 b/packages/examples/packages/settings-page/LICENSE.APACHE2 deleted file mode 100644 index bf37d0e612..0000000000 --- a/packages/examples/packages/settings-page/LICENSE.APACHE2 +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2023 ConsenSys Software Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/examples/packages/settings-page/LICENSE.MIT0 b/packages/examples/packages/settings-page/LICENSE.MIT0 deleted file mode 100644 index 3a4d3cb295..0000000000 --- a/packages/examples/packages/settings-page/LICENSE.MIT0 +++ /dev/null @@ -1,16 +0,0 @@ -MIT No Attribution - -Copyright 2024 Consensys Software Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this -software and associated documentation files (the "Software"), to deal in the Software -without restriction, including without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/examples/packages/settings-page/README.md b/packages/examples/packages/settings-page/README.md deleted file mode 100644 index faa5015009..0000000000 --- a/packages/examples/packages/settings-page/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# `@metamask/settings-page-example-snap` - -This snap demonstrates how to use `endowment:page-settings` permission to show -a settings page that leverages custom UI components. - -This endowment is initially restricted to preinstalled snaps only. - -## Snap manifest - -The manifest of this snap includes the `endowment:page-settings` permission: - -```json -{ - "initialPermissions": { - "endowment:page-settings": {} - } -} -``` - -This permission does not require any additional configuration. - -## Snap usage - -This snap exposes an `onSettingsPage` handler, which returns the UI to be shown. diff --git a/packages/examples/packages/settings-page/jest.config.js b/packages/examples/packages/settings-page/jest.config.js deleted file mode 100644 index f473a91b83..0000000000 --- a/packages/examples/packages/settings-page/jest.config.js +++ /dev/null @@ -1,36 +0,0 @@ -const deepmerge = require('deepmerge'); - -const baseConfig = require('../../../../jest.config.base'); - -module.exports = deepmerge(baseConfig, { - preset: '@metamask/snaps-jest', - - // Since `@metamask/snaps-jest` runs in the browser, we can't collect - // coverage information. - collectCoverage: false, - - // This is required for the tests to run inside the `MetaMask/snaps` - // repository. You don't need this in your own project. - moduleNameMapper: { - '^@metamask/(.+)/production/jsx-runtime': [ - '/../../../$1/src/jsx/production/jsx-runtime', - '/../../../../node_modules/@metamask/$1/jsx/production/jsx-runtime', - '/node_modules/@metamask/$1/jsx/production/jsx-runtime', - ], - '^@metamask/(.+)/jsx': [ - '/../../../$1/src/jsx', - '/../../../../node_modules/@metamask/$1/jsx', - '/node_modules/@metamask/$1/jsx', - ], - '^@metamask/(.+)/node$': [ - '/../../../$1/src/node', - '/../../../../node_modules/@metamask/$1/node', - '/node_modules/@metamask/$1/node', - ], - '^@metamask/(.+)$': [ - '/../../../$1/src', - '/../../../../node_modules/@metamask/$1', - '/node_modules/@metamask/$1', - ], - }, -}); diff --git a/packages/examples/packages/settings-page/package.json b/packages/examples/packages/settings-page/package.json deleted file mode 100644 index 900c03737f..0000000000 --- a/packages/examples/packages/settings-page/package.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "name": "@metamask/settings-page-example-snap", - "version": "0.0.0", - "description": "MetaMask example snap demonstrating the use of settings pages", - "keywords": [ - "MetaMask", - "Snaps", - "Ethereum" - ], - "homepage": "https://github.com/MetaMask/snaps/tree/main/packages/examples/packages/settings-page#readme", - "bugs": { - "url": "https://github.com/MetaMask/snaps/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/MetaMask/snaps.git" - }, - "license": "(MIT-0 OR Apache-2.0)", - "sideEffects": false, - "main": "./dist/bundle.js", - "files": [ - "dist", - "snap.manifest.json" - ], - "scripts": { - "build": "mm-snap build", - "build:clean": "yarn clean && yarn build", - "changelog:update": "../../../../scripts/update-changelog.sh @metamask/settings-page-example-snap", - "changelog:validate": "../../../../scripts/validate-changelog.sh @metamask/settings-page-example-snap", - "clean": "rimraf \"dist\"", - "lint": "yarn lint:eslint && yarn lint:misc --check && yarn changelog:validate && yarn lint:dependencies", - "lint:ci": "yarn lint", - "lint:dependencies": "depcheck", - "lint:eslint": "eslint . --cache --ext js,ts,jsx,tsx", - "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", - "lint:misc": "prettier --no-error-on-unmatched-pattern --loglevel warn \"**/*.json\" \"**/*.md\" \"**/*.html\" \"!CHANGELOG.md\" \"!snap.manifest.json\" --ignore-path ../../../../.gitignore", - "publish:preview": "yarn npm publish --tag preview", - "since-latest-release": "../../../../scripts/since-latest-release.sh", - "start": "mm-snap watch", - "test": "jest --reporters=jest-silent-reporter", - "test:clean": "jest --clearCache", - "test:verbose": "jest --verbose", - "test:watch": "jest --watch" - }, - "dependencies": { - "@metamask/snaps-sdk": "workspace:^" - }, - "devDependencies": { - "@jest/globals": "^29.5.0", - "@lavamoat/allow-scripts": "^3.0.4", - "@metamask/auto-changelog": "^3.4.4", - "@metamask/eslint-config": "^12.1.0", - "@metamask/eslint-config-jest": "^12.1.0", - "@metamask/eslint-config-nodejs": "^12.1.0", - "@metamask/eslint-config-typescript": "^12.1.0", - "@metamask/snaps-cli": "workspace:^", - "@metamask/snaps-jest": "workspace:^", - "@swc/core": "1.3.78", - "@swc/jest": "^0.2.26", - "@typescript-eslint/eslint-plugin": "^5.42.1", - "@typescript-eslint/parser": "^6.21.0", - "deepmerge": "^4.2.2", - "depcheck": "^1.4.7", - "eslint": "^8.27.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jest": "^27.1.5", - "eslint-plugin-jsdoc": "^41.1.2", - "eslint-plugin-n": "^15.7.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^6.1.1", - "jest": "^29.0.2", - "jest-silent-reporter": "^0.6.0", - "prettier": "^2.8.8", - "prettier-plugin-packagejson": "^2.5.2", - "ts-node": "^10.9.1", - "typescript": "~5.3.3" - }, - "engines": { - "node": "^18.16 || >=20" - }, - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org/" - } -} diff --git a/packages/examples/packages/settings-page/snap.config.ts b/packages/examples/packages/settings-page/snap.config.ts deleted file mode 100644 index 577ddbdd7b..0000000000 --- a/packages/examples/packages/settings-page/snap.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { SnapConfig } from '@metamask/snaps-cli'; -import { resolve } from 'path'; - -const config: SnapConfig = { - input: resolve(__dirname, 'src/index.tsx'), - server: { - port: 8071, - }, - typescript: { - enabled: true, - }, - stats: { - buffer: false, - }, -}; - -export default config; diff --git a/packages/examples/packages/settings-page/snap.manifest.json b/packages/examples/packages/settings-page/snap.manifest.json deleted file mode 100644 index 5c984e9d96..0000000000 --- a/packages/examples/packages/settings-page/snap.manifest.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": "0.0.0", - "description": "MetaMask example snap demonstrating the use of settings pages.", - "proposedName": "Settings Page Example Snap", - "repository": { - "type": "git", - "url": "https://github.com/MetaMask/snaps.git" - }, - "source": { - "shasum": "zDRgwSzsvtVtFr3F0SzUJCSCBNfix0C4CgTY98F+uDk=", - "location": { - "npm": { - "filePath": "dist/bundle.js", - "packageName": "@metamask/settings-page-example-snap", - "registry": "https://registry.npmjs.org" - } - } - }, - "initialPermissions": { - "endowment:rpc": { - "dapps": true - }, - "endowment:page-settings": {}, - "snap_manageState": {} - }, - "platformVersion": "6.13.0", - "manifestVersion": "0.1" -} diff --git a/packages/examples/packages/settings-page/src/index.test.tsx b/packages/examples/packages/settings-page/src/index.test.tsx deleted file mode 100644 index 3653e895d8..0000000000 --- a/packages/examples/packages/settings-page/src/index.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, it } from '@jest/globals'; -import { installSnap } from '@metamask/snaps-jest'; - -import { SettingsPage } from './components/SettingsPage'; - -describe('onSettingsPage', () => { - it('returns custom UI', async () => { - const { onSettingsPage } = await installSnap(); - - const response = await onSettingsPage(); - - const screen = response.getInterface(); - - expect(screen).toRender(); - }); -}); - -describe('onRpcRequest', () => { - describe('getSettings', () => { - it('returns the settings state', async () => { - const { request, onSettingsPage } = await installSnap(); - - const settingPageResponse = await onSettingsPage(); - - const screen = settingPageResponse.getInterface(); - - await screen.clickElement('setting1'); - - await screen.selectFromRadioGroup('setting2', 'option1'); - - await screen.selectInDropdown('setting3', 'option2'); - - expect( - await request({ - method: 'getSettings', - }), - ).toRespondWith({ - setting1: true, - setting2: 'option1', - setting3: 'option2', - }); - }); - }); -}); diff --git a/packages/examples/packages/settings-page/src/index.tsx b/packages/examples/packages/settings-page/src/index.tsx deleted file mode 100644 index 2fc0f3cfa8..0000000000 --- a/packages/examples/packages/settings-page/src/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; -import { - MethodNotFoundError, - UserInputEventType, - type OnSettingsPageHandler, - type OnUserInputHandler, -} from '@metamask/snaps-sdk'; - -import { SettingsPage } from './components/SettingsPage'; - -type SnapState = { - setting1?: boolean; - setting2?: 'option1' | 'option2'; - setting3?: 'option1' | 'option2'; -}; - -/** - * Handle incoming JSON-RPC requests from the dapp, sent through the - * `wallet_invokeSnap` method. This handler handles one method: - * - * - `getSettings`: Get the settings state from the snap state. - * - * @param params - The request parameters. - * @param params.request - The JSON-RPC request object. - * @returns The JSON-RPC response. - * @see https://docs.metamask.io/snaps/reference/exports/#onrpcrequest - * @see https://docs.metamask.io/snaps/reference/rpc-api/#wallet_invokesnap - * @see https://docs.metamask.io/snaps/reference/snaps-api/#snap_managestate - */ -export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { - switch (request.method) { - case 'getSettings': - return await snap.request({ - method: 'snap_manageState', - params: { - operation: 'get', - encrypted: false, - }, - }); - default: - // eslint-disable-next-line @typescript-eslint/no-throw-literal - throw new MethodNotFoundError({ method: request.method }); - } -}; - -/** - * Handle incoming settings page requests from the MetaMask clients. - * - * @returns A static panel rendered with custom UI. - * @see https://docs.metamask.io/snaps/reference/exports/#onsettingspage - */ -export const onSettingsPage: OnSettingsPageHandler = async () => { - const state: SnapState | null = await snap.request({ - method: 'snap_manageState', - params: { - operation: 'get', - encrypted: false, - }, - }); - - return { - content: ( - - ), - }; -}; - -/** - * Handle incoming user events coming from the MetaMask clients open interfaces. - * - * @param params - The event parameters. - * @param params.event - The event object containing the event type, name and value. - * @see https://docs.metamask.io/snaps/reference/exports/#onuserinput - */ -export const onUserInput: OnUserInputHandler = async ({ event }) => { - if (event.type === UserInputEventType.InputChangeEvent) { - const state = await snap.request({ - method: 'snap_manageState', - params: { - operation: 'get', - encrypted: false, - }, - }); - - await snap.request({ - method: 'snap_manageState', - params: { - operation: 'update', - encrypted: false, - newState: { - ...state, - [event.name]: event.value, - }, - }, - }); - } -}; diff --git a/packages/examples/packages/settings-page/tsconfig.json b/packages/examples/packages/settings-page/tsconfig.json deleted file mode 100644 index 17a40a6a74..0000000000 --- a/packages/examples/packages/settings-page/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "composite": false, - "baseUrl": "./" - }, - "include": ["src", "snap.config.ts"] -} diff --git a/packages/test-snaps/package.json b/packages/test-snaps/package.json index 47b175528a..1077a1ba86 100644 --- a/packages/test-snaps/package.json +++ b/packages/test-snaps/package.json @@ -66,7 +66,6 @@ "@metamask/notification-example-snap": "workspace:^", "@metamask/preinstalled-example-snap": "workspace:^", "@metamask/send-flow-example-snap": "workspace:^", - "@metamask/settings-page-example-snap": "workspace:^", "@metamask/signature-insights-example-snap": "workspace:^", "@metamask/snaps-utils": "workspace:^", "@metamask/utils": "^10.0.0", diff --git a/packages/test-snaps/src/features/snaps/index.ts b/packages/test-snaps/src/features/snaps/index.ts index 91ba2991f4..c80978a40d 100644 --- a/packages/test-snaps/src/features/snaps/index.ts +++ b/packages/test-snaps/src/features/snaps/index.ts @@ -27,4 +27,3 @@ export * from './signature-insights'; export * from './updates'; export * from './wasm'; export * from './send-flow'; -export * from './settings-page'; diff --git a/packages/test-snaps/src/features/snaps/preinstalled/Preinstalled.tsx b/packages/test-snaps/src/features/snaps/preinstalled/Preinstalled.tsx index 14b49d74c6..735f722f81 100644 --- a/packages/test-snaps/src/features/snaps/preinstalled/Preinstalled.tsx +++ b/packages/test-snaps/src/features/snaps/preinstalled/Preinstalled.tsx @@ -1,6 +1,6 @@ import { logError } from '@metamask/snaps-utils'; import type { FunctionComponent } from 'react'; -import { Button } from 'react-bootstrap'; +import { Button, ButtonGroup } from 'react-bootstrap'; import { useInvokeMutation } from '../../../api'; import { Result, Snap } from '../../../components'; @@ -9,13 +9,20 @@ import { PREINSTALLED_SNAP_ID, PREINSTALLED_VERSION } from './constants'; export const Preinstalled: FunctionComponent = () => { const [invokeSnap, { isLoading, data, error }] = useInvokeMutation(); - const handleSubmit = () => { + const handleSubmitDialog = () => { invokeSnap({ snapId: PREINSTALLED_SNAP_ID, method: 'showDialog', }).catch(logError); }; + const handleSubmitSettings = () => { + invokeSnap({ + snapId: PREINSTALLED_SNAP_ID, + method: 'getSettings', + }).catch(logError); + }; + return ( { version={PREINSTALLED_VERSION} testId="preinstalled-snap" > - + + + + {JSON.stringify(data, null, 2)} diff --git a/packages/test-snaps/src/features/snaps/settings-page/SettingsPage.tsx b/packages/test-snaps/src/features/snaps/settings-page/SettingsPage.tsx deleted file mode 100644 index 11c3090bb3..0000000000 --- a/packages/test-snaps/src/features/snaps/settings-page/SettingsPage.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { logError } from '@metamask/snaps-utils'; -import type { FunctionComponent } from 'react'; -import { Button } from 'react-bootstrap'; - -import { useInvokeMutation } from '../../../api'; -import { Result, Snap } from '../../../components'; -import { getSnapId } from '../../../utils'; -import { - SETTINGS_PAGE_SNAP_ID, - SETTINGS_PAGE_SNAP_PORT, - SETTINGS_PAGE_VERSION, -} from './constants'; - -export const SettingsPage: FunctionComponent = () => { - const [invokeSnap, { isLoading, data }] = useInvokeMutation(); - const snapId = getSnapId(SETTINGS_PAGE_SNAP_ID, SETTINGS_PAGE_SNAP_PORT); - - const handleSubmit = () => { - invokeSnap({ - snapId, - method: 'getSettings', - }).catch(logError); - }; - return ( - - - - - {JSON.stringify(data, null, 2)} - - - ); -}; diff --git a/packages/test-snaps/src/features/snaps/settings-page/constants.ts b/packages/test-snaps/src/features/snaps/settings-page/constants.ts deleted file mode 100644 index 0b2b4ef5da..0000000000 --- a/packages/test-snaps/src/features/snaps/settings-page/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -import packageJson from '@metamask/settings-page-example-snap/package.json'; - -export const SETTINGS_PAGE_SNAP_ID = 'npm:@metamask/settings-page-example-snap'; -export const SETTINGS_PAGE_SNAP_PORT = 8071; -export const SETTINGS_PAGE_VERSION = packageJson.version; diff --git a/packages/test-snaps/src/features/snaps/settings-page/index.ts b/packages/test-snaps/src/features/snaps/settings-page/index.ts deleted file mode 100644 index f533f5abe0..0000000000 --- a/packages/test-snaps/src/features/snaps/settings-page/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './SettingsPage'; diff --git a/yarn.lock b/yarn.lock index e956661fc8..5e73b80771 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5517,43 +5517,6 @@ __metadata: languageName: unknown linkType: soft -"@metamask/settings-page-example-snap@workspace:^, @metamask/settings-page-example-snap@workspace:packages/examples/packages/settings-page": - version: 0.0.0-use.local - resolution: "@metamask/settings-page-example-snap@workspace:packages/examples/packages/settings-page" - dependencies: - "@jest/globals": "npm:^29.5.0" - "@lavamoat/allow-scripts": "npm:^3.0.4" - "@metamask/auto-changelog": "npm:^3.4.4" - "@metamask/eslint-config": "npm:^12.1.0" - "@metamask/eslint-config-jest": "npm:^12.1.0" - "@metamask/eslint-config-nodejs": "npm:^12.1.0" - "@metamask/eslint-config-typescript": "npm:^12.1.0" - "@metamask/snaps-cli": "workspace:^" - "@metamask/snaps-jest": "workspace:^" - "@metamask/snaps-sdk": "workspace:^" - "@swc/core": "npm:1.3.78" - "@swc/jest": "npm:^0.2.26" - "@typescript-eslint/eslint-plugin": "npm:^5.42.1" - "@typescript-eslint/parser": "npm:^6.21.0" - deepmerge: "npm:^4.2.2" - depcheck: "npm:^1.4.7" - eslint: "npm:^8.27.0" - eslint-config-prettier: "npm:^8.5.0" - eslint-plugin-import: "npm:^2.26.0" - eslint-plugin-jest: "npm:^27.1.5" - eslint-plugin-jsdoc: "npm:^41.1.2" - eslint-plugin-n: "npm:^15.7.0" - eslint-plugin-prettier: "npm:^4.2.1" - eslint-plugin-promise: "npm:^6.1.1" - jest: "npm:^29.0.2" - jest-silent-reporter: "npm:^0.6.0" - prettier: "npm:^2.8.8" - prettier-plugin-packagejson: "npm:^2.5.2" - ts-node: "npm:^10.9.1" - typescript: "npm:~5.3.3" - languageName: unknown - linkType: soft - "@metamask/signature-insights-example-snap@workspace:^, @metamask/signature-insights-example-snap@workspace:packages/examples/packages/signature-insights": version: 0.0.0-use.local resolution: "@metamask/signature-insights-example-snap@workspace:packages/examples/packages/signature-insights" @@ -6444,7 +6407,6 @@ __metadata: "@metamask/preinstalled-example-snap": "workspace:^" "@metamask/providers": "npm:^18.1.1" "@metamask/send-flow-example-snap": "workspace:^" - "@metamask/settings-page-example-snap": "workspace:^" "@metamask/signature-insights-example-snap": "workspace:^" "@metamask/snaps-utils": "workspace:^" "@metamask/utils": "npm:^10.0.0"