Skip to content

Conversation

@jeffsmale90
Copy link
Contributor

@jeffsmale90 jeffsmale90 commented Sep 11, 2025

Explanation

  • Why change?
    When signing a permission, MetaMask must present the permission in a way that is easily comprehensible to the user. Based on the design shared here, the permission must be decoded from the eth_signTypedData_v4 request.

This PR adds that functionality to the GatorPermissionsController.

  • What’s the solution?

Introduce decodePermissionFromPermissionContextForOrigin to GatorPermissionsController and register a new action handler. It:

  • Validates the caller origin matches permissionsProviderSnapId (throws OriginNotAllowedError otherwise).

  • Resolves enforcer contract addresses per chain via @metamask/delegation-deployments. Throws if contracts are missing for the chain.

  • Identifies the permission type from caveats via identifyPermissionByEnforcers, extracts expiry and permission-specific data via getPermissionDataAndExpiry, and builds a DecodedPermission via reconstructDecodedPermission.

  • Non-obvious pieces:

    • Added @metamask/delegation-core for terms helpers like createTimestampTerms and createNativeTokenStreamingTerms, and ROOT_AUTHORITY.
    • Added @metamask/delegation-deployments to source enforcer addresses and CHAIN_ID.
    • Moved the decoding logic into a subfolder, to make it easier to export internal functionality from utils.ts to facilitate low level unit testing, without polluting the interface that is exposed to the GatorPermissionsController with these functions.

References

Architecture design https://www.notion.so/metamask-consensys/SignTypedData-with-Metadata-Specification-22bf86d67d688023be67e2ee06e3a56a#22bf86d67d688023be67e2ee06e3a56a

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed, highlighting breaking changes as necessary
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

@jeffsmale90 jeffsmale90 force-pushed the feat/decodePermission branch 4 times, most recently from e5e5d20 to 56b2f73 Compare September 12, 2025 01:08
@jeffsmale90 jeffsmale90 force-pushed the feat/decodePermission branch 4 times, most recently from 15794d8 to 4bc812a Compare September 15, 2025 04:24
@jeffsmale90 jeffsmale90 changed the title Add method decodePermissionFromPermissionContextForOrigin to GatorPer… Add method to decode permission to GatorPermissionsController Sep 15, 2025
@jeffsmale90 jeffsmale90 marked this pull request as ready for review September 15, 2025 08:44
@jeffsmale90 jeffsmale90 requested review from a team as code owners September 15, 2025 08:44
…missionsController

- rejects any request from an origin other than the gator permission snap
- attempts to identify the permission type, and decode the data
- rejects any request where a unique permission is unable to be decoded

Also renames GatorPermissionsController.test.ts
cursor[bot]

This comment was marked as outdated.

@socket-security
Copy link

socket-security bot commented Sep 15, 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/​delegation-core@​0.2.0751009992100
Added@​metamask/​delegation-deployments@​0.12.0961007785100

View full report

Copy link
Member

@V00D00-child V00D00-child left a comment

Choose a reason for hiding this comment

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

I'm not seeing a strong justification for exposing decodePermissionFromPermissionContextForOrigin() via the GatorPermissionsController.

As mentioned in the inline comment, we should consider decoupling it entirely and exposing it as a utility function directly from @metamask/gator-permissions-controller.

- fix incorrect type of periodDuration in native-token-periodic-permission was Hex, now number
- removed unnecessary type assertions
- add necessary type assertion to test
cursor[bot]

This comment was marked as outdated.

@jeffsmale90 jeffsmale90 enabled auto-merge (squash) September 16, 2025 18:28
@jeffsmale90 jeffsmale90 merged commit 1e07701 into main Sep 16, 2025
239 checks passed
@jeffsmale90 jeffsmale90 deleted the feat/decodePermission branch September 16, 2025 18:44
jeffsmale90 added a commit that referenced this pull request Sep 23, 2025
## Summary

Add execution-permission decoding to `SignatureController` for
`eth_signTypedData_v4` delegation requests. If a delegation request is
unable to be decoded into a coherent Execution Permission, reject
external (not from `origin: metamask`) attempts to sign delegations for
internal accounts. Adds decoded permission in signature request state
for rendering downstream.

## Why
Readable Permissions requires the ability to sign delegations, only when
the delegation can be decoded to a permission that is then shown to the
user.

[The architecture is described in this
document](https://www.notion.so/metamask-consensys/SignTypedData-with-Metadata-Specification-22bf86d67d688023be67e2ee06e3a56a).
Decoding has been added to `GatorPermissionsController` in #6556

Note: the request origin is always `@metamask/gator-permission-snap`
(any other origin is rejected). Because of this, `metadata` is added to
the 712 payload, defining the `justification` and `origin` which is the
_original_ origin of the request (the dapp).
github-merge-queue bot pushed a commit to MetaMask/metamask-extension that referenced this pull request Sep 25, 2025
…ded permission (#36054)

## **Description**


This feature is only enabled in Flask.

When a dapp requests an EIP-7715 Readable Permission, the user must sign
with `eth_signTypedData_v4`, but it's important that the user can
understand what is being signed. This PR surfaced the decoded permission
information in a new Sign Permission view.

When a `eth_signTypedData_v4` request is received by
`@metamask/signature-controller` that appears to be a delegation, it
will pass this request to `@metamask/gator-permissions-controller` to
decode the request into the originating permission. If this is
successful, `@metamask/signature-controller` attaches the decoded
permission to the request `metadata`.

This PR adds a new view showing the details of the permission that the
user is signing. This does not implement the final design, and focusses
only on introducing the functionality to extension. Further iteration
will be made to improve the UX. As such, no internationalization has
been included in this change.

A couple important points to note:
- if `GATOR_PERMISSIONS_ENABLED` is not true, the request will be
rejected here (this should never happen, because the method
`wallet_requestExecutionPermissions` will not allow requests if the
feature is not enabled
- if the origin is not the configured Gator Permissions Snap, the
permission will not be decoded by the `GatorPermissionsController`
- if the request is for a delegation signature, and no permission is
decoded, the SignatureController will reject the request
- This feature is only being released into Flask at this stage - future
PRs will refine the UX before this is released to end-users

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

## **Changelog**

CHANGELOG entry: presents a Permission confirmation view when a decoded
permission exists on signTypedData metadata. Flask only.

## **Related issues**

These PRs are related to this feature:
- Introduce GatorPermissionsController to mobile
MetaMask/metamask-mobile#20006
- Decode permissions in SignatureController
MetaMask/core#6619
- Method 'decodePermissionFromPermissionContextForOrigin' is now
synchronous MetaMask/core#6656
- Add `decodePermissionFromPermissionContextForOrigin` to
`GatorPermissionsController` MetaMask/core#6556

## **Manual testing steps**

1. Run the 7715 gator snaps locally
2. Build MetaMask flask with `GATOR_PERMISSIONS_ENABLED=true` and
`GATOR_PERMISSIONS_PROVIDER_SNAP_ID=local:http://localhost:8082`
2. Open the 7715 gator snap page
3. Install snaps if necessary, by clicking "Install kernel snap", then
"Install provider snap"
4. Select the appropriate permission type and parameters
5. Click "Grant Permission"
6. Adjust the requested permission as necessary
7. Click "Grant"

Expect to see the sign permission screen:

## **Screenshots/Recordings**

<img width="396" height="852" alt="image"
src="https://github.com/user-attachments/assets/b45e5a7d-ccaa-4456-be65-4b990bc2ea2d"
/>

## **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.
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.

3 participants