What are meta transactions, the EIP-712 standard, and how to sign a message with Metamask?

Meta transactions in Ethereum blockchain is an approach that removes the complexity for our users to deal with gas fees. The gas fee is a transaction fee paid to validators for proof of stake (POS) or miners for proof of work (POW) blockchains. With this approach, users sign a transaction, which is sent to a smart contract. It keeps all the security aspects upon which the Ethereum blockchain was created. This article will examine the EIP-712 standard and how to sign transactions with Metamask.

What is EIP-712 standard?

EIP-712 standard describes how data is structured, hashed, and signed. Signing a transaction has been around in crypto wallets like Metamask, but this approach aims to display that in a much more human-readable way that users can understand and review before signing.

Structure a typed data

The EIP-712 states how the typed data should be structured in a JSON document. Let's go over the main parts of it.

At first, we need to describe what the types would look like. It always starts describing the EIP712Domain type and follows with types that will be presented when signing.

Let's say we would like to add a new employee. That means we must introduce an Employee type with an inner Address type.

types: {
    EIP712Domain: [
      {
        name: "name",
        type: "string"
      },
      {
        name: "version",
        type: "string"
      },
      {
        name: "verifyingContract",
        type: "address"
      },
      {
        name: "salt",
        type: "bytes32"
      }
    ],
    Employee: [
      { name: 'id', type: "uint256" },
      { name: 'name', type: 'string' },
      { name: 'address', type: 'Address' }
    ],
    Address: [
      { name: 'address', type: "string" },
      { name: 'country', type: "string" },
      { name: 'phoneNumber', type: "string" }
    ]
  }

After that, we should pass on information about the domain specifics.

Let's go over the fields:

  • name is the smart contract name that this message will be sent to;
  • version will always be 1;
  • verifyingContract is the smart contract address whose name is in name field;
  • salt is the chain id in hex format that you can find the chain id from chainlist.org.
domain: {
  name: "AddEmployee",
  version: "1",
  verifyingContract: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
  salt: "0x0000000000000000000000000000000000000000000000000000000000005"
  }

Now we need to pass data and tell what will be the primary type.

primaryType: "Employee",
message: {
  id: 1111,
  name: "John",
  address: {
    address: 'Infinity Loop 1',
    country: 'USA',
    phoneNumber: '+123456789'
  }
}

We combined all these parts to construct the JSON data in a typed structured data object.

How to sign a message with Metamask?

Now that we have compiled the JSON data object, we want to sign it with Metamask. We won't go into details about how to use JavaScript libraries like Ethers.js or web3.js, but we will do it with the Metamask console that is available in Chrome-based browsers.

At first, we should enable the Metamask Ethereum console. To do that, we must open the developer console in any Chrome-based browser and type ethereum.enable(). Metamask will ask to connect to an account. Please allow it to do so.

Connect to Metamask

We can verify if everything is ok by opening the promise in the console and seeing that it is fulfilled.

Connect to Metamask

Now, we should copy the wallet address to sign the message. Then save it to a variable in the developer console for easier access.

const account = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
Set account in developer console

After that, we will use the JSON data object we constructed according to the EIP-712 standard and save it in a variable.

const msgData = JSON.stringify({
  types: {
    EIP712Domain: [
      {
        name: "name",
        type: "string"
      },
      {
        name: "version",
        type: "string"
      },
      {
        name: "verifyingContract",
        type: "address"
      },
      {
        name: "salt",
        type: "bytes32"
      }
    ],
    Employee: [
      { name: 'id', type: "uint256" },
      { name: 'name', type: 'string' },
      { name: 'address', type: 'Address' }
    ],
    Address: [
      { name: 'address', type: "string" },
      { name: 'country', type: "string" },
      { name: 'phoneNumber', type: "string" }
    ]
  },
  domain: {
    name: "AddEmployee",
    version: "1",
    verifyingContract: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
    salt:
      "0x00000000000000000000000000000000000000000000000000000000005"
  },
  primaryType: "Employee",
  message: {
    id: 1111,
    name: "John",
    address: {
      address: 'Infinity Loop 1',
      country: 'USA',
      phoneNumber: '+123456789'
    }
  }
});
EIP-712 typed JSON data in developer console

Now, we can finally sign the message by executing the eth_signTypedData_v4 method.

ethereum.request({method: "eth_signTypedData_v4", params: [account, msgData]})

Then Metamask will show the message data. We can once again verify if it is correct and sign it.

Signing message with Metamask

After we hit the sign button, we return the message hash that we can use in the smart contract.

Message hash

This data can be used on behalf of the signer to execute a smart contract on the blockchain, which we will look into in the next post.

TL;DR

The EIP-712 standard opens doors to sign a transaction and allows someone else to use this transaction. It helps to implement gasless transactions to avoid our users paying the gas fee and figuring out how to get native tokens like Ethereum, Matic, and others. This standard describes how to format the message in human-readable form in a crypto wallet like Metamask when a user signs it. After it is signed, we can use this signed transaction in a smart contract, but more on that in one of the following blog posts.

Links