Smart Account Session Snap: Gaming Dapp Auto Approvals

An ETHBogota Hackathon Snap

by MetaMaskNovember 14, 2022
feature

MetaMask 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 MetaMask Snaps being built today.

Smart Account Sessions

Snap Repo: https://github.com/livingrockrises/snap-auto-approvals

Why did you build it?

To create a seamless user experience for gaming dapps providing them a way for auto approvals in secure manner.

Can you walk us through the technical implementation?

The App

Our Snap is composed from two parts. First is a website that users interact with. The second one is the actual Snap. Those two elements work in a way similar to client-server architecture, where the Snap is a server and the website is the client.

Website has following user-flow:

  1. Connect your EOA and install the Smart account session Snap.
  2. Enable Smart Account on top of MetaMask address. MetaMask EOA will be controller of this Smart account.
  3. Enable Session Module on your Smart Account. Modules enable additional access-control logic for your Smart Safe account. Essentially, every Smart account is controlled by two means. By the MetaMask account owner using their signer keys and by optional modules that have their own custom access logic.
  4. Create a session.
  5. This will create a temporary session keys on your smart account that are authorized to transaction on your wallet via module. Session can have parameters like startTime, endTime and Permissions for custom actions on Dapp contracts.
  6. Send auto approved transactions using above session key without MetaMask pop-up for gas or signatures.

The Metamask API

MetaMask provides API calls, which can be used by MetaMask Snaps if permitted by user.

In this case we have levarged below:

snap_manageState - which allow as two store encrypted data inside MetaMask. For instance we use it to store created session key pair.

export type SessionKeyStorage = {
  owner: string;
  sessionKey: string;
  pk: string;
};

export const storeSessionInfo = async (data: SessionKeyStorage) => {
  await wallet.request({
    method: 'snap_manageState',
    params: ['update', data],
  });
};

export const clearSessionInfo = async () => {
  const result = await wallet.request({
    method: 'snap_manageState',
    params: ['clear'],
  });
  return result;
};

export const getSessionInfo = async () => {
  const result = await wallet.request({
    method: 'snap_manageState',
    params: ['get'],
  });
  return result;
};

snap_confirm - which allow as two store encrypted data inside MetaMask. For instance we use it to store created accounts.


export const promptUser = async (
  prompt: string,
  description: string,
  content: string,
): Promise<boolean> => {
  const response: any = await wallet.request({
    method: 'snap_confirm',
    params: [
      {
        prompt,
        description,
        textAreaContent: content,
      },
    ],
  });
  console.log('Prompt user response', response);
  if (response) {
    return response;
  }
  return false;
};

promptUser(getMessage(origin),
            'Do you want to use Smart Account',
            `Your Smart Account Address is ${_smartAccount.address}`,
          ).then((approval) => {
            if (approval) {
              console.log('Sending smart acccount response ', _smartAccount);
              resolve({
                address: _smartAccount.address,
                owner: _smartAccount.owner,
              });
            } else {
              reject(new Error('EOA user'));
            }
          });

Snap will handle incoming JSON-RPC requests, sent through wallet_invokeSnap from the site.

For above app journey, we have created custom request methods like connect, enable_session_module create_session and interact. One example is below and the code is here.


export const useSmartAccount = async () => {
  console.log('invoke Snap...');
  const smartAccountResponse: any = await window.ethereum.request({
    method: 'wallet_invokeSnap',
    params: [
      defaultSnapOrigin,
      {
        method: 'connect',
        params: [],
      },
    ],
  });
  return smartAccountResponse;
};

Session Key Module

We wrote a Gnosis safe style module to extend functionality of smart contract wallet using access control with session keys.

Code can be found here for module and wallet contracts.

struct Session {
        address smartAccount;
        address sessionKey;
        uint256 startTimestamp;
        uint256 nonce;
        uint256 endTimestamp;
        bool enable;
        PermissionStorage permission;
    }

struct PermissionStorage {
        address[] whitelistDestinations;
        mapping(address => bool) whitelistDestinationMap;
        mapping(address => bytes4[]) whitelistMethods;
        mapping(address => mapping(bytes4 => bool)) whitelistMethodsMap;
        mapping(address => TokenApproval) tokenApprovals;
    }

When session is created by invoking snap and in turn invoking smart account, the below method is called on Session key module from Smart Account via a relayer.

function createSession(
        address sessionKey,
        PermissionParam[] calldata permissions,
        SessionParam calldata sessionParam
    ) external {
        require(
            !sessionMap[sessionKey].enable,
            "Session for key is already enabled"
        );
        Session storage _session = sessionMap[sessionKey];
        ...

txHash: https://goerli.etherscan.io/tx/0x8b45f4cb0a7527bb7c44f6923eb9bb391faa656e212829ce0fd9c733ca71352b

Once session has been created, a relayer can invoke transaction on session key module with valid session key and signature.


function executeTransaction(
        address _sessionKey,
        address payable _to,
        uint256 _value,
        bytes calldata _data,
        bytes calldata signature
    ) external returns (bool success) {
        Session storage session = sessionMap[_sessionKey];
        require(session.enable, "Session is not active");
        require(
            session.startTimestamp <= block.timestamp,
            "Session has not yet started"
        );
        require(session.endTimestamp >= block.timestamp, "Session has expired");
        ...

txHash: https://goerli.etherscan.io/tx/0xf6d40bf9c74cff266f8bc26dabcf7a44ca1c0e85387f297f43fa5cb89bbea1a0

The SDK

Biconomy SDK is the ultimate Web3 suite for developers to leverage smart accounts & simplify user journeys from complex multi-step into a simple one-click experience.

Each user gets a smart contract wallet, rather than a simple cryptographic key pair, and dApps can customize all sorts of seamless interactions using transaction bundles via this SC wallet for the user.

We have linked the below function with ‘connect’ rpc call by invoking snap.


import SmartAccount from '@biconomy-sdk/smart-account';

async function getsmartAccount(): Promise<SmartAccount> {
  smartAccount = new SmartAccount(wallet, {
    activeNetworkId: Number(wallet.chainId),
    // supportedNetworksIds: supportedChains,
  });

  smartAccount = await smartAccount.init();
  console.log('initialized');
  console.log(smartAccount);
  return smartAccount;
}

What are the next steps if you would implement this?

We are looking forward to keyrings feature and ability to display smart account infromation in place of EOAs in the Metamask UI.

  1. We will be publishing Biconomy SDK compatible with MetaMask Snaps for developers to use.
  2. Adding social recovery module and one-click cross chain batched transactions using Smart Accounts and showing notifications as soon as destination chain transaction is executed.

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

Sachin is the CTO and Co-Founder at Biconomy. Coming with a computer science background, he has 6 yrs of web2 and 5 yrs of web3 experience. He loves to take a structured approach towards problem solving and likes being hands on to quickly implement his ideas and test them.

Chirag is a blockchain engineer at Biconomy. He loves programming and learning all things web3 through research and development.

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

We learned about MetaMask Snaps shortly before the ETHBogota hackathon. We were super impressed by the documentation and it’s capabilities through snap methods.

What makes MetaMask Snaps different from other wallets?

An ability to extend features of MetaMask wallet without depending on the MetaMask core team and enable faster growth. Developers can access existing features of MetaMask like JSON RPC calls interface and storage.

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

Basic development was very intuitive, but we spent lot of time figuring out some of the permission requirements for specific methods. Ability to build custom UI elements for example to display balance of smart contract wallets just like EOAs is tricky at the momemnt.

What does MetaMask Snaps mean to you?

It is a secret weapon which enables us to support ERC4337 Smart contract wallets within MetaMask, without having to build or having dapps to integrate custom widget on their front end.

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

Dapp-specific customised MetaMask Snaps can provide better user experience and enable multichain interactions. Introducing fee abstraction, account abstraction and wallet security capabilities on top of existing Web3 userbase.

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

Checkout github discussions and Discord which can help mitigating lot of blockers in your development. Anyone familiar with javascript and json rpc calls can build. snap-monorepos and example MetaMask Snaps is a good place to speed up your development.

If you’d like to store something, only single object can be stored so you can nest multiple key values within one single object. When you need to return responses from custom invoked snap methods make sure it returns plain json object.

Building with MetaMask Snaps

To get started with MetaMask Snaps:

  1. Checkout the developer docs
  2. Install MetaMask Flask
  3. Check out a MetaMask 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: MetaMask Snaps are generally developed by third parties other the ConsenSys Software. Use of third-party-developed MetaMask Snaps is done at your own discretion and risk and with agreement that you will solely be responsible for any loss or damage that results from such activities. ConsenSys makes no express or implied warranty, whether oral or written, regarding any third-party-developed MetaMask Snaps and disclaims all liability for third-party developed MetaMask Snaps. Use of blockchain-related software carries risks, and you assume them in full when using MetaMask Snaps.

Receive our Newsletter