Skip to content

Conversation

@GuillaumeRx
Copy link
Contributor

@GuillaumeRx GuillaumeRx commented Dec 16, 2025

This PR adds the use of StorageService to store the Snap source codes. Due to this storage being async, the SnapController initialization is now async and the preinstalled snaps setup now lives in init()


Note

Adds @metamask/storage-service as a new dependency in packages/snaps-controllers/package.json.

  • Updates dependencies to include @metamask/storage-service@^0.0.1

Written by Cursor Bugbot for commit 197f300. This will update automatically on new commits. Configure here.

@socket-security
Copy link

socket-security bot commented Dec 16, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​metamask/​storage-service@​0.0.1751009989100

View full report

@codecov
Copy link

codecov bot commented Jan 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.35%. Comparing base (afc2c54) to head (197f300).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3777   +/-   ##
=======================================
  Coverage   98.35%   98.35%           
=======================================
  Files         430      430           
  Lines       12340    12362   +22     
  Branches     1919     1920    +1     
=======================================
+ Hits        12137    12159   +22     
  Misses        203      203           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

"@metamask/snaps-rpc-methods": "workspace:^",
"@metamask/snaps-sdk": "workspace:^",
"@metamask/snaps-utils": "workspace:^",
"@metamask/storage-service": "^0.0.1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should really be >=1.0.0 before we use this in production 😵

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know but I was never released as 1.0.0 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Andre considers it stable, we could ask for a bump to v1

@GuillaumeRx GuillaumeRx requested a review from Mrtenz January 21, 2026 10:38
@GuillaumeRx GuillaumeRx marked this pull request as ready for review January 21, 2026 11:16
@GuillaumeRx GuillaumeRx requested a review from a team as a code owner January 21, 2026 11:16
cursor[bot]

This comment was marked as outdated.

* @param sourceCode - The source code for the Snap.
*/
async #setSourceCode(snapId: SnapId, sourceCode: string): Promise<void> {
await this.messenger.call('StorageService:setItem', this.name, snapId, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a problem for right now, if we were storing more than just sourceCode in the storage service, this would overwrite it. Does the StorageService API let us use it more as a KV-store than we are right now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately no, this is as far as it goes as a KV-store. You're right that this will need a rework if we store more than the source code inside but since that's the only thing that we store right now I didn't feel like it was necessary to implement a more complex solution for the moment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly

cursor[bot]

This comment was marked as outdated.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.


// If the snap has a previous source code, set it back to the previous source code.
// If it doesn't, we don't need to set it back to the previous source code because it means we haven't updated the source code.
if (previousSourceCode !== '') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make previousSourceCode optional so we don't need to compare to empty string?

/**
* The source code of the Snap.
*/
sourceCode: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anywhere in the clients we use the sourceCode from state or returned via get()? If yes, that may break from this 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've looked at both clients and couldn't find any usage


export type PersistedSnap = Snap;

export type StoredSnap = Snap & StorageServiceSnapData;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export type StoredSnap = Snap & StorageServiceSnapData;
export type StoredSnap = PersistedSnap & StorageServiceSnapData;

Nit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

welp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fight with @Mrtenz 😂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant that we can remove the StoredSnap type 😅

Suggested change
export type StoredSnap = Snap & StorageServiceSnapData;
export type PersistedSnap = Snap & StorageServiceSnapData;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, not entirely since that is used for typing out the persisted state of the controller itself

Copy link
Member

@Mrtenz Mrtenz Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having both PersistedSnap and StoredSnap seems confusing in my opinion, since they mean very similar things.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't disagree. But unsure what to propose

return getSnapObject({ id: snapId as SnapId });
});

messenger.registerActionHandler(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow why we are changing the default mocks for permissions 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're changing this to avoid throwing a lot of errors due to the fact that now, because we init the controller in the tests, we call callLifecycleHooks. In this path we call hasPermission and getPermissions, if hasPermission returns true but getPermissions doesn't return the correct handler permissions we throw an error and that was happening for each test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes a lot of sense. Can we document that in the PR description?

messenger.registerActionHandler('PermissionController:hasPermission', () => {
return true;
});
messenger.registerActionHandler(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


export const approvalControllerMock = new MockApprovalController();

export const storageAdapter = new InMemoryStorageAdapter();
Copy link
Member

@FrederikBolding FrederikBolding Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not be instantiated with each controller instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very useful as we need to use it when registering the action handlers for the StorageService

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking it makes it easier to have a clean starting point for each test, without needing to clear it out manually

fetchMock.mockImplementation(async () => {
throw new AssertionError({ message: 'Unmocked access to internet.' });
});

// Hydrate the storage service with the default snap data.
await hydrateStorageService();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be dependent on the test? Not all tests need the default Snap to be in the storage service

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -10216,7 +10381,12 @@ describe('SnapController', () => {
origin: 'bar.io',
});

const snapController = getSnapController(
await hydrateStorageService({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand when we need to do this and when we don't. Could getSnapController handle this instead? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we hydrate the defaults in beforeEach so if the test only uses the default values there's no need to call hydrateStorageService. However if the test uses different values in the snaps state we need to set the corresponding source codes in the StorageService

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't each call to getSnapController set it up instead? So we avoid the beforeEach

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants