MetaMask News

Revoke Snap: Revoking Leftover Approvals On ERC20 Token Contracts

An ETHWarsaw Hackathon Snap that allows users to revoke leftover contract approvals.

by MetaMaskNovember 7, 2022
feature

Snaps is the roadmap to making MetaMask the most extensible wallet in the world. As a developer, you can bring your features and APIs to MetaMask in totally new ways. Web3 developers are the core of this growth and this series aims to showcase the novel Snaps being built today.

Revoke Snap with Swix Jedi and Adrian

We chat with Web3 developer Swix Jedi and Adrian who built the Revoke Snap at the ETHWarsaw Hackathon.

Snap Repo: https://github.com/SwixJedi/revokesnap

Why did you build it?

In order to ensure security of token usage by external actors, ERC20 token standards implement an allowance, where the owner of the token needs to approve an actor (i.e. DEX smart contract) to be able to utilize its tokens up to a chosen limit.

In order to enhance the somewhat clunky DeFi user experience, many of the DeFi protocols use a max unit 256 value for approvals in order for the user not to have to approve tokens and spend their money on unnecessary gas fees each time they want to interact with their contracts.

Especially for active DeFi users that can easily create a situation when after using said protocols there are some leftover approvals, which is a potential security threat.

While there are existing solutions to help users tackle the leftover allowance, only some of them offer approvals directly from the user interface. The process is time consuming as each approval needs to be addressed individually and manually confirmed. While this approach is secure it requires a lot of continuous, manual user interaction.

This is what pointed us towards an unaddressed niche where we could leverage the Snaps capabilities, to make things better.

Can you walk us through the technical implementation?

We decided to leverage the following benefits of Snaps:

  • Access to all accounts in Metamask
  • Ability to perform complex operations using a single approval from a user

To propose a solution for this problem, which in the future should make it possible to:

  • Present only one user prompt to get access to all current and future accounts (with notable exception of hardware wallets)
  • Revoke all approvals from all current accounts in batch, with no need for separate confirmations and accounts switching
  • Actively monitor all user accounts (including those on hardware wallets) for new approvals and display notification to the user when detected.

On installing the Revoke Snap through the web UI, MetaMask will prompt for needed permissions.

All permissions are specified in a snap.manifest.json file:

"initialPermissions": {
    "snap_confirm": {},
    "snap_getBip44Entropy_60": {},
    "snap_notify": {},
    "endowment:long-running": {}
  },

Two permissions worth noting here are:

1. Control your “Ethereum” accounts and assets. This means that to function, our snap requires users parent key (secret key) from MetaMask in order to be able to generate accounts and sign transactions on behalf of the user.

2. Run indefinitely While Revoke Snap is not an implementation that is meant to run in the background continuously, settling transactions on blockchain takes time and Snaps do have a built in kill-switch after 60s, which can not be enough to finalize the transactions. This permission allows RevokeSnap to go around that security measure.

After receiving the necessary permissions RevokeSnap will generate a number of accounts out of the user's private key:


// Get chosen amount of wallets (signers) from provider (in this case Metamask)
export async function getSigners(provider: ethers.providers.Provider, amount: number): Promise<Wallet[]> {
    let wallets: Wallet[] = [];

    for (let i=0; i<amount; i++) {
        wallets.push(await getSigner(provider, i));
    }

    return wallets;
}

// Exctract addresses out of wallets
export function getAddresses(wallets: Wallet[]): string[] {
    let addresses: string[] = [];

    for (let i=0; i<wallets.length; i++) {
        addresses.push(wallets[i].address);
    }

    return addresses;
}

// Fetch parent key from Metamask and generate a wallet from it, base on the input index
export async function getSigner(provider: ethers.providers.Provider, index: number): Promise<Wallet> {
    // Metamask uses default HD derivation path
    // https://metamask.zendesk.com/hc/en-us/articles/360060331752-Importing-a-seed-phrase-from-another-wallet-software-derivation-path
    const ethereumNode = (await wallet.request({
      method: 'snap_getBip44Entropy_60',
    })) as unknown as BIP44CoinTypeNode;
    const deriveEthereumAccount = await getBIP44AddressKeyDeriver(ethereumNode);
    // A bug:
    // The current public version of @metamask/key-tree's derive function returns the private key and chain code in a single buffer
    // Ether.js also accepts a 64 byte buffer without errors and returns wrong keys
    // Related issue: https://github.com/ethers-io/ethers.js/issues/2926
    // TODO(ritave): Update to newest key-tree when available and use deriveEthereumAccount(0).privateKey
    const mainAccountKey = (await deriveEthereumAccount(index)).privateKey;
    return new Wallet(mainAccountKey, provider);
  }

Then perform a check for leftover approvals and, once the search is complete, expose a button to revoke all identified approvals.

Screen Shot 2022-11-07 at 3.54.17 PM

On the click of the “Revoke All” button the Snap will issue a series of transactions corresponding to the leftover approval to zero them out one by one. As the Snap already is in possession of the generated wallets private keys this does not require any additional confirmation from the user, beyond the initial one.

What are the next steps if you would implement this?

As during the hackathon there was only a limited amount of time to create the most optimum solutions, we utilized a number of functional implementations which have room for improvement.

For starters, currently the number of accounts generated in the snap is passed as a function parameter and hard-coded as 3. A better option could be for the snap to use an external API (or a dedicated backend) to verify the next transaction nonce on each account for a given chosen chain(s) and only take into account addresses with the nonce number greater than 0.

Moreover for approvals we used a short list of known addresses of tokens and actors to showcase the functionality, but optimally the approval check should be done more globally. This could be done, i.e. by using an RPC provider API (such as Infura) to find all approval transactions and parse them to display on the website.

Can you tell us a little bit about yourself and your team?

I’m Jedi, I work as a Lead Developer at Swix. I have been in crypto since 2017, and since 2019 I fell down the rabbit hole and started working with DeFi protocols, mostly on the Smart Contract side. With Swix I’m currently working on bridging the real estate yields on-chain and bringing new users into the crypto space.

I’m Adrian, I’ve worked as a software developer and architect for small startups and big companies. Blockchain has been my main area of interest for many years, particularly the Ethereum ecosystem.

During the development of the Revoke Snap we split the work so that Jedi focused on working out how Snaps work and setting up necessary basics for the Smart Contact interactions required within our Snap, while Adrian handled the web UI and bundling of the transactions.

When were you first introduced to MetaMask Snaps and what was your experience like?

We were first introduced to Snaps during ETHWarsaw’s presentation, which was quite technical and encompassed more in-depth under the hood workings of the protocol. This triggered us to research the topic some more, and well, here we are.

What makes MetaMask Snaps different from other wallets?

I mean… the idea itself. Snaps breaks out of the standard accepted within the non-custodial browser wallet extensions landscape which is that every transaction, while interacting with web3 applications, needs to be individually approved by the user. Also the functionality of wallets is limited to the functionality the team behind it decides to implement.

What Snaps brings to the table is a platform enabling the bottom-up creation of native wallet integrations for arbitrary blockchain interactions, which carries a lot of resemblance to the mobile app stores.

What opportunities do you see with MetaMask Snaps and the possibilities it unlocks for the Web3 space? What does Snaps mean to you?

One of the biggest value adds for the crypto space coming from Snaps is smoothing of the UX in the crypto and DeFi protocols. Whether that would be enabling bundling transactions, 1-click approval for all transactions made within one dApp, allowing for direct contract interactions while executing credit card-to-crypto purchases or some other even more creative solution, only time will tell.

It is our strong belief that, especially for the new crypto users, abstracting away the complexity and clunkiness of blockchain interactions can be a game changer.

Tell us about what building Snaps with MetaMask is like for you and your team?

A big motivator while building Revoke Snap was that Snaps are still in development and thus the experimentation we do right now can have a tangible impact on where MetaMask, Snaps and, through it to an extent, the crypto space will go. It was a good experience to be able to add our brick to the future of web3.

Any advice you would like to share with developers keen to try MetaMask Snaps?

For any devs looking to try Snaps out — have a go at it. Snaps are pretty straight forward to start with using the available github template repository and the MetaMask team is very helpful as well. The concept itself has a big potential and there isn’t so much that has been done with it already, so it’s easy to provide a value add.

Building with Snaps

To get started with Snaps:

  1. Checkout the developer docs
  2. Install MetaMask Flask
  3. Check out a Snaps guide
  4. Stay connected with us on Twitter, GitHub discussions, and Discord

Keep an eye out for our team at the next hackathon in an area near you! Happy BUIDLing ⚒️

Disclaimer: This Snap was built at a hackathon. It is a proof of concept and prototype.

Receive our Newsletter