MetaMask / MetaMask/snap-7715-permissions

[Feature]: Add `erc20-token-swap`, an outcome-defined permission type for protocol exits and bounded swaps

Open
#423 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
11
Forks
9
Avg merge
3d 13h
Merged PRs (30d)
2

Description

Technical Details
Description

Add an ERC-7715 permission type that is defined by its outcome rather than by the shape of the call: the delegate may call an allow-listed contract function that converts at most X of ERC-20 token A into at least Y of ERC-20 token B, delivered to the granting account.

Motivating use case: incident response. A user pre-signs a narrow permission, a watcher service redeems it when an exploit is detected on the protocol holding the position, and the funds land back in the user's own account. This is the exit counterpart of token-approval-revocation (revoke.delegate).

None of the seven existing types express this, and there is no function-call permission. Building it as a raw ERC-7710 delegation is blocked for MetaMask-managed accounts (including EOAs upgraded through the smart account flow) by validateDelegation in MetaMask/core signature-controller, which only lets external origins sign delegations for internal accounts when the request carries a decodedPermission, i.e. when it went through ERC-7715 and decodes into a known permission type.

The feat/promised-outcomes branch (#88) sketched this direction: the wallet interprets a promised outcome, generates enforcement caveats, shows the assured outcome to the user, and returns the caveats in the permission context. This proposal lifts that idea into a first-class permission type. It also addresses the concern raised on MetaMask/smart-accounts-kit#218 that showing 4-byte selectors and hex calldata in the confirmation is undesirable: the user reads the outcome, not the call.

Technical Details

Request shape (permission.data):

{
  "justification": "Exit your Pendle LP position to USDC if an exploit is detected.",
  "spend":   { "tokenAddress": "0xPositionToken", "maxAmount": "0x…" },
  "receive": { "tokenAddress": "0xUSDC",          "minAmount": "0x…" },
  "via":     { "targets": ["0xRouter"], "selectors": ["removeLiquiditySingleToken(address,address,uint256,…)"] }
}

Supported rules: expiry, redeemer. payee is rejected (the recipient is always the granting account).

Caveats, all existing Delegation Framework v1.3.0 enforcers, no new contracts:

Enforcer Terms
AllowedTargetsEnforcer via.targets
AllowedMethodsEnforcer via.selectors (4-byte hex or ABI signature, hashed by the snap)
ValueLteEnforcer 0
ERC20BalanceChangeEnforcer increase, receive.tokenAddress, grantor, receive.minAmount
ERC20BalanceChangeEnforcer decrease, spend.tokenAddress, grantor, spend.maxAmount
LimitedCallsEnforcer 1

plus the shared Timestamp, Redeemer and Nonce caveats appended by the grant pipeline.

The balance-change terms are 73 packed bytes (flag, token, recipient, amount), so a wallet can render "delivers at least 36,427.61 USDC to your own account" from the bytes alone, with no protocol-specific decoding and no simulation.

Confirmation UI: "Swap up to" and "Receive at least" (adjustable when isAdjustmentAllowed), "Delivered to" the selected account, and read-only "Allowed contracts" / "Allowed functions" lists. Targets and selectors are never user-editable.

Validated on a HyperEVM fork against a live Pendle market: with no calldata pins, the balance caveats alone reject a redirected payout, a wrong-asset payout and an under-delivering exit, while the honest exit passes, for both a Hybrid account and a 7702-upgraded EOA.

Known limitations (documented in packages/gator-permissions-snap/docs/erc20TokenSwap.md): single-call execution mode only; other assets already approved to via.targets remain reachable through the allowed selectors; minAmount is a theft bound rather than a slippage bound; spend/receive tokens must differ; ERC-20 only in v1.

Companion changes needed for MetaMask to sign it for internal accounts:

  • @metamask/7715-permission-types (MetaMask/smart-accounts-kit): type, createErc20TokenSwapCaveats, makeErc20TokenSwapDecoderConfig, schema entry, and EnforcerContractName += erc20BalanceChangeEnforcer, allowedMethodsEnforcer, limitedCallsEnforcer.
  • @metamask/gator-permissions-controller (MetaMask/core): map those three enforcers in toEnforcerAddressesByName.
Acceptance Criteria
  • erc20-token-swap is accepted by wallet_requestExecutionPermissions, listed by permissionsProvider_getPermissionOffers and getSupportedPermissions with rule types expiry and redeemer.
  • Malformed requests (zero amounts, same spend/receive token, empty/oversized/duplicate targets or selectors, invalid selector strings, a recipient that is not the granting account, a payee rule) are rejected before the confirmation opens.
  • The confirmation shows both amounts with token icons, the delivery account, and the read-only allow-lists; amounts are editable only when isAdjustmentAllowed is true; expiry is always editable.
  • The granted delegation carries exactly the caveats above, with recipient equal to the granting account even after the user switches accounts in the confirmation.
  • Existing permission types are unaffected.
  • Translations: new en.json keys (Crowdin fills the rest).

Scenario: exit a position on exploit detection

  • GIVEN a user holds a Pendle LP position and has approved the Pendle router
  • WHEN the watcher requests erc20-token-swap with spend = the LP token, receive = USDC, via = the router and removeLiquiditySingleToken, and a redeemer rule naming the watcher
  • THEN the user sees "Swap up to … LP", "Receive at least … USDC", "Delivered to" their account, and the router and function names
  • AND on approval the returned context decodes to the six caveats plus expiry, redeemer and nonce
  • AND a redemption that under-delivers, pays out elsewhere, or pays in the wrong asset reverts, while the honest exit succeeds once
References
  • feat/promised-outcomes branch and draft PR #88
  • MetaMask/smart-accounts-kit#218 (function-call permission types and the calldata rendering concern), MetaMask/smart-accounts-kit#263
  • MetaMask/core packages/signature-controller/src/utils/validation.ts (validateDelegation) and packages/gator-permissions-controller/src/decodePermission
  • Delegation Framework ERC20BalanceChangeEnforcer.sol

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with packages/gator-permissions-snap/docs/erc20TokenSwap.md, then trace wallet_requestExecutionPermissions, permissionsProvider_getPermissionOffers, getSupportedPermissions, and decodePermission. Review packages/signature-controller/src/utils/validation.ts and the referenced gator-permissions-controller mapping before assessing the companion smart-accounts-kit changes. Done means the acceptance scenarios pass across validation, confirmation, caveats, redemption, and existing permission types.

Written by the indexing model from the issue text.

Assessment

Tech stack
solidity, typescript
Domain
authorization, blockchain, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.