Skip to content

Commit

Permalink
chore: Bump Snaps packages (#30677)
Browse files Browse the repository at this point in the history
## **Description**

<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->

This bumps the Snaps packages to the latest version. The changes
include:

- Support for [SIP-30](https://metamask.github.io/SIPs/SIPS/sip-30).
- Improved error messaging.
- Log unhandled errors thrown by Snaps.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/30677?quickstart=1)

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
  • Loading branch information
Mrtenz authored Mar 5, 2025
1 parent c6c8710 commit 116a2bc
Show file tree
Hide file tree
Showing 17 changed files with 435 additions and 403 deletions.
1 change: 1 addition & 0 deletions app/scripts/controllers/permissions/specifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export const unrestrictedMethods = Object.freeze([
'snap_clearState',
'snap_getFile',
'snap_getState',
'snap_listEntropySources',
'snap_createInterface',
'snap_updateInterface',
'snap_getInterfaceState',
Expand Down
55 changes: 54 additions & 1 deletion app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2639,7 +2639,37 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger,
'SnapController:clearSnapState',
),
getMnemonic: this.getPrimaryKeyringMnemonic.bind(this),
getMnemonic: async (source) => {
if (!source) {
return this.getPrimaryKeyringMnemonic();
}

try {
const { type, mnemonic } = await this.controllerMessenger.call(
'KeyringController:withKeyring',
{
id: source,
},
async (keyring) => ({
type: keyring.type,
mnemonic: keyring.mnemonic,
}),
);

if (type !== KeyringTypes.hd || !mnemonic) {
// The keyring isn't guaranteed to have a mnemonic (e.g.,
// hardware wallets, which can't be used as entropy sources),
// so we throw an error if it doesn't.
throw new Error(
`Entropy source with ID "${source}" not found.`,
);
}

return mnemonic;
} catch {
throw new Error(`Entropy source with ID "${source}" not found.`);
}
},
getUnlockPromise: this.appStateController.getUnlockPromise.bind(
this.appStateController,
),
Expand Down Expand Up @@ -6366,6 +6396,29 @@ export default class MetamaskController extends EventEmitter {
currency: fiatCurrency,
};
},
getEntropySources: () => {
/**
* @type {KeyringController['state']}
*/
const state = this.controllerMessenger.call(
'KeyringController:getState',
);

return state.keyrings
.map((keyring, index) => {
if (keyring.type === KeyringTypes.hd) {
return {
id: state.keyringsMetadata[index].id,
name: state.keyringsMetadata[index].name,
type: 'mnemonic',
primary: index === 0,
};
}

return null;
})
.filter(Boolean);
},
hasPermission: this.permissionController.hasPermission.bind(
this.permissionController,
origin,
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@
"[email protected]": "^7.5.4",
"[email protected]": "^7.5.4",
"lavamoat-core@npm:^16.2.2": "patch:lavamoat-core@npm%3A16.2.2#~/.yarn/patches/lavamoat-core-npm-16.2.2-e361ff1f8a.patch",
"@metamask/snaps-sdk": "^6.18.0",
"@metamask/snaps-sdk": "^6.19.0",
"@swc/[email protected]": "^0.1.6",
"@babel/core": "patch:@babel/core@npm%3A7.25.9#~/.yarn/patches/@babel-core-npm-7.25.9-4ae3bff7f3.patch",
"@babel/runtime": "patch:@babel/runtime@npm%3A7.25.9#~/.yarn/patches/@babel-runtime-npm-7.25.9-fe8c62510a.patch",
Expand Down Expand Up @@ -316,11 +316,11 @@
"@metamask/selected-network-controller": "^19.0.0",
"@metamask/signature-controller": "^23.1.0",
"@metamask/smart-transactions-controller": "^16.0.1",
"@metamask/snaps-controllers": "^10.0.0",
"@metamask/snaps-controllers": "^10.0.1",
"@metamask/snaps-execution-environments": "^7.0.0",
"@metamask/snaps-rpc-methods": "^11.12.0",
"@metamask/snaps-sdk": "^6.18.0",
"@metamask/snaps-utils": "^9.0.0",
"@metamask/snaps-rpc-methods": "^11.13.1",
"@metamask/snaps-sdk": "^6.19.0",
"@metamask/snaps-utils": "^9.0.1",
"@metamask/solana-wallet-snap": "^1.10.0",
"@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A45.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-45.0.0-010fef9da6.patch",
"@metamask/user-operation-controller": "^24.0.1",
Expand Down
16 changes: 16 additions & 0 deletions test/e2e/fixture-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,22 @@ class FixtureBuilder {
});
}

/**
* Add a keyring controller with a vault that contains multiple SRP keyrings.
*
* @returns {FixtureBuilder}
*/
withKeyringControllerMultiSRP() {
return this.withKeyringController({
keyringsMetadata: [
{ id: '01JNGTRZ3QCEEQ7GYYFXBSQSBK', name: 'SRP 1' },
{ id: '01JNGTTNRVYNQVN5FN8YTFAMJ4', name: 'SRP 2' },
],
vault:
'{"data":"EfMp/e5oTwIWzxXXUKJDBwnG9ooALjzWDh0Stb2anQ5Q763pW9H0KJ6LJ8J+AnMkfWqP93JWEl0i7meCgDHQz/2rkE15ZAsY3IInEgLYPFqDbEr4zI/wfvHNg5GJGA4v14X/C+ts6asQeJUdMlUgC3m2mVdpis7ALACtDCIWWRuTkdq0jtkafXQra1ExMulPV31ZOMnDsvgHUuoXlL7+om8yNYqKuwMqVO/09X+WdbRG7EJqjNzjfV8BrNvOAJnhmdQpWGXKyIk0IA4o1QFJO8fmrXZMRmf64a3TWYAmEp6jAG3Rz9X4nnL52BpLjMBcik0L8k5y8ZuUZH5+Wme1D86HaHwnJtZvsRULuPFYbb/UCsfW9PwcyYZ4bmDOn/xYiOEFTk0Ku8PE6vkFqcIrrkCJoWaw6BnuriRovsoduzuGcAaJNvQ2bXvV2yyCdQSP7i2WnlXGgRDO7beCamoa80YIf+PAGFC00UxuyBWda4eZ+ipixGmGdp4EpBzt0GgY9GMknA6ivyb2UUNOyz4DMVL/CjjiU9ezf/Go22RzSLcXLUYF4KlpLEP6sFJCG3Uo2WzUWQFiy6Gs/VrYitLeB52MTNRfEviXXmlN+rSQc02zg8xPG19WCnjA3e7fkYzP0aL/DOtWuNqIZwP3s7EziiIGlb/cxIxc0VwyI/Ew0XG2/xbxt8WKsY2DkXdgMsviHcz3u+1STZaxiORVOeBsKnj8w6nEtzbqPxTRpqva7q4nOICk6Xbc//YHezqg8kJWvPmLeZAuYY4VyMiBthCG+qvHqtnhIQ8iobUdhqJ5+QlukPLmFMXfKqPF1fFEPk1vtpCNwhURkG7z2xDzGIzjXmSQUjtlFP6vqmFaqJAgpbuw1mVfoDudQ3cZCH71W5gOyn3TeMIyVse39W/+dK3YLXqmt+Sx05GsXco9QeSlBzAGhMYFB1GKxaiZIEACGg==","iv":"JvKKq/Rs8pbTTamsoPgcGQ==","keyMetadata":{"algorithm":"PBKDF2","params":{"iterations":600000}},"salt":"Su9hh66sokHRahkEZLUGowOI1pPbNCT90ymFqLFYza0="}',
});
}

withMetaMetricsController(data) {
merge(this.fixture.data.MetaMetricsController, data);
return this;
Expand Down
205 changes: 104 additions & 101 deletions test/e2e/page-objects/pages/test-snaps.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,18 @@
import { Driver } from '../../webdriver/driver';
import { TEST_SNAPS_WEBSITE_URL } from '../../snaps/enums';
import { largeDelayMs, WINDOW_TITLES } from '../../helpers';
import SnapInstall from './dialog/snap-install';
import SnapInstallWarning from './dialog/snap-install-warning';

export class TestSnaps {
driver: Driver;

private readonly installedSnapsHeader = '[data-testid="InstalledSnaps"]';
public readonly snapInstall;

private readonly connectDialogsSnapButton =
'[data-testid="dialogs"] [data-testid="connect-button"]';
private readonly installedSnapsHeader = '[data-testid="InstalledSnaps"]';

private readonly dialogsSnapConfirmationButton = '#sendConfirmationButton';

private readonly dialogConnectButton = {
text: 'Connect',
tag: 'button',
css: '[data-testid="page-container-footer-next"]',
};

private readonly dialogConfirmButton = {
text: 'Confirm',
tag: 'button',
css: '[data-testid="page-container-footer-next"]',
};

private readonly dialogOkButton = {
text: 'OK',
tag: 'button',
css: '[data-testid="page-container-footer-next"]',
};

private readonly connectHomePage = '#connecthomepage';

private readonly connectBip32 = '#connectbip32';

private readonly connectBip44 = '#connectbip44';

private readonly reconnectButton = {
css: '#connectbip32',
text: 'Reconnect to BIP-32 Snap',
};

private readonly reconnectBip44Button = {
css: '#connectbip44',
text: 'Reconnect to BIP-44 Snap',
};

private readonly getPublicKeyButton = {
css: '#bip32GetPublic',
text: 'Get Public Key',
Expand Down Expand Up @@ -76,43 +43,104 @@ export class TestSnaps {

constructor(driver: Driver) {
this.driver = driver;
this.snapInstall = new SnapInstall(driver);
}

async openPage() {
await this.driver.openNewPage(TEST_SNAPS_WEBSITE_URL);
await this.driver.waitForSelector(this.installedSnapsHeader);
}

async clickConnectDialogsSnapButton() {
/**
* Install a Snap with the given `connectButton` selector. This assumes a
* button exists on the `test-snaps` page that will open a dialog to install
* the Snap.
*
* @param connectButton - The selector for the button that will open the
* dialog to install the Snap.
* @param withWarning - Whether the installation will have a warning dialog,
* e.g., in the case of entropy Snaps requiring special permissions.
*/
async installSnap(connectButton: string, withWarning = false) {
await this.driver.scrollToElement(
this.driver.findClickableElement(this.connectDialogsSnapButton),
this.driver.findClickableElement(connectButton),
);

await this.driver.delay(largeDelayMs);
await this.driver.clickElement(this.connectDialogsSnapButton);
}
await this.driver.waitForSelector(connectButton);
await this.driver.clickElement(connectButton);

async clickDialogsSnapConfirmationButton() {
await this.driver.clickElement(this.dialogsSnapConfirmationButton);
}
await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
await this.snapInstall.check_pageIsLoaded();
await this.snapInstall.clickNextButton();

async clickConnectBip32() {
console.log('Wait, scroll and click connect button');
await this.driver.scrollToElement(
this.driver.findClickableElement(this.connectBip32),
);
await this.driver.delay(largeDelayMs);
await this.driver.waitForSelector(this.connectBip32);
await this.driver.clickElement(this.connectBip32);
// click confirm
await this.snapInstall.clickConfirmButton();

if (withWarning) {
const snapInstallWarning = new SnapInstallWarning(this.driver);
await snapInstallWarning.clickCheckboxPermission();
await snapInstallWarning.clickConfirmButton();
}

await this.snapInstall.clickNextButton();
await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);

await this.driver.waitForSelector({
css: connectButton,
text: 'Reconnect',
});
}

/**
* Click a button with the given selector.
*
* @param selector - The selector for the button to click.
* @returns A promise that resolves after the button is clicked.
*/
async clickButton(selector: string) {
console.log('Wait and click button');
await this.driver.waitForSelector(selector);
await this.driver.clickElement(selector);
}

/**
* Paste a message into a field with the given selector.
*
* @param selector - The selector for the field to paste the message into.
* @param message - The message to paste into the field.
* @returns A promise that resolves after the message is pasted into the
* field.
*/
async pasteIntoField(selector: string, message: string) {
await this.driver.pasteIntoField(selector, message);
}

/**
* Click the approve button in the dialog window.
*
* @returns A promise that resolves after the approve button is clicked.
*/
async approveDialog() {
// Switch to approve window.
await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);

// Wait for and click on approve and wait for window to close.
await this.driver.waitForSelector({
text: 'Approve',
tag: 'button',
});
await this.driver.clickElementAndWaitForWindowToClose({
text: 'Approve',
tag: 'button',
});

// Switch back to `test-snaps` page.
await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
}

async clickConnectBip44() {
console.log('Wait, scroll and click connect button');
await this.driver.scrollToElement(
this.driver.findClickableElement(this.connectBip44),
);
await this.driver.delay(largeDelayMs);
await this.driver.waitForSelector(this.connectBip44);
await this.driver.clickElement(this.connectBip44);
async clickDialogsSnapConfirmationButton() {
await this.driver.clickElement(this.dialogsSnapConfirmationButton);
}

async clickGetPublicKeyButton() {
Expand All @@ -133,39 +161,6 @@ export class TestSnaps {
await this.driver.clickElement(this.getCompressedKeyButton);
}

async completeSnapInstallConfirmation() {
await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);

await this.driver.waitForSelector(this.dialogConnectButton);

await this.driver.clickElement(this.dialogConnectButton);

await this.driver.waitForSelector(this.dialogConfirmButton);

await this.driver.clickElement(this.dialogConfirmButton);

await this.driver.waitForSelector(this.dialogOkButton);

await this.driver.clickElement(this.dialogOkButton);

await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps);
}

async clickConnectHomePage() {
// find and scroll to the homepage snap
const connectHomePageButton = await this.driver.findElement(
this.connectHomePage,
);
await this.driver.scrollToElement(connectHomePageButton);

// added delay for firefox
await this.driver.delayFirefox(1000);

// wait for and click connect
await this.driver.waitForSelector(this.connectHomePage);
await this.driver.clickElement(this.connectHomePage);
}

async fillMessageSecp256k1(message: string) {
console.log('Wait and fill message in secp256k1');
await this.driver.fill(this.inputMessageSecp256k1, message);
Expand Down Expand Up @@ -203,13 +198,21 @@ export class TestSnaps {
await this.driver.scrollToElement(sendEd25519);
}

async waitForReconnectButton() {
console.log('Wait for reconnect button');
await this.driver.waitForSelector(this.reconnectButton);
}

async waitForReconnectBip44Button() {
console.log('Wait for reconnect button');
await this.driver.waitForSelector(this.reconnectBip44Button);
/**
* Select an entropy source from the dropdown with the given ID.
*
* @param id - The ID of the dropdown.
* @param name - The name of the entropy source to select.
*/
async selectEntropySource(id: string, name: string) {
console.log('Select entropy source');
const selector = await this.driver.findElement(`#${id}-entropy-selector`);
await this.driver.scrollToElement(selector);
await selector.click();

await this.driver.clickElement({
text: name,
css: `#${id}-entropy-selector option`,
});
}
}
2 changes: 1 addition & 1 deletion test/e2e/snaps/enums.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = {
TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.19.0',
TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.20.1',
};
Loading

0 comments on commit 116a2bc

Please sign in to comment.