What are Meta Transactions

This page describes the meta transactions that are used in the Market.

What are meta transaction?

Meta transaction allow a wallet to do contract executions on behalf of another wallet and (most importantly) thus pay for the gas fee of that transaction.

So for example if wallet-A owns an NFT, the owner of wallet-A could allow wallet-B to transfer that NFT to wherever necessary and letting wallet-B pay for the gas fee.

Prerequisite

  • Meta transactions can only be used on contracts and thus cannot be used with native tokens (e.g. Ether, Matic, โ€ฆ)
  • The (token) contract needs an executeMetaTransaction (write) and a getNonce (read) function

Basic flow

  1. An EIP712 JSON document is generated specifying the contract, method and parameters that are allowed to be executed on the source walletโ€™s behalf
  2. The source wallet signs this JSON document using our widget and supplies the signature to the owner of the executor wallet (gas fee payer)
  3. Using the executor wallet, the executeMetaTransaction function on the contract needs to be called supplying the signature of the EIP712 document
  4. The method specified in step 1 will be executed using the parameters also supplied in step 1 (The executer wallet pays for the GAS fees of the transaction)

An example of how this could look like for an NFT transfer:

Execute Meta Transactions

Execute Meta Transactions

The specifics

1. Building the EIP712 JSON document

The EIP712 standard defines a way to structure a document that needs a signature in such a way that wallets can parse and show it in a readable way to their end users that need to sign.

Below you can find an example of such a EIP712 document, calling safeTransferFrom on contract 0x0096100f27d5ed9a3455b54af3934df07b58b506

{
  "types": {
    "EIP712Domain": [
      {
        "name": "name",
        "type": "string"
      },
      {
        "name": "version",
        "type": "string"
      },
      {
        "name": "verifyingContract",
        "type": "address"
      },
      {
        "name": "salt",
        "type": "bytes32"
      }
    ],
    "MetaTransaction": [
      {
        "name": "nonce",
        "type": "uint256"
      },
      {
        "name": "from",
        "type": "address"
      },
      {
        "name": "functionSignature",
        "type": "bytes"
      }
    ]
  },
  "domain": {
    "name": "Digimon",
    "version": "1",
    "verifyingContract": "0x0096100f27d5ed9a3455b54af3934df07b58b506",
    "salt": "0x0000000000000000000000000000000000000000000000000000000000013881"
  },
  "primaryType": "MetaTransaction",
  "message": {
    "nonce": 0,
    "from": "0xeB947ED047020F3C2982d35Ac2a8EbE8A7330282",
    "functionSignature": "0xf242432a000000000000000000000000eb947ed047020f3c2982d35ac2a8ebe8a73302820000000000000000000000008fe26c6ff544bee01f41e6f87e6d0ead0ad274050000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"
  }
}

types

types is about defining the structure and data types of the document. This part you should basically take as is.

domain

domain is about describing the contract you want to interact with. Here some customisation needs to be done:

PropertyDescription
nameThe name of the (NFT) contract you want to interact with
versionThis should always be 1
chainIdThe Id of the blockchain network
verifyingContractThe contract address you want to interact with e.g. the NFT contract address
saltA unique 32-byte value hardcoded into both the contract and the dApp. Meant as a last resort to distinguish the dApp from others

primaryType

primaryType needs to be MetaTransaction

message

message is about defining which contract call to execute and what parameters should be used.

PropertyDescription
nonceThis is a contract + wallet specific sequence number that needs to be fetched from the contract. This can be done by calling the getNonce(address user) function and supplying it with the wallet address that needs to sign the message (wallet A)
fromThe address of the signer
functionSignatureThe encoded function + parameters that need to be executed. This depends on which contract call you want to do. Below you will find a breakdown on how to build this signature for calling safeTransferFrom(address,address,uint256,uint256,bytes)

Building the functionSignature

The functionSignature defines what the signer allows to be executed by a third party. To explain how it is build weโ€™ll work with an example:

0xf242432a000000000000000000000000eb947ed047020f3c2982d35ac2a8ebe8a73302820000000000000000000000008fe26c6ff544bee01f41e6f87e6d0ead0ad274050000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000

This is the functionSignature for safeTransferFrom(address,address,uint256,uint256,bytes) + input data and is build as follows:

functionSignature PartDescriptionExample
Method ID

This is the method ID of the function you want to call (ABI encoded function signature).

You can get this in several ways, but to make it easy weโ€™ve created a jsFiddle that can help you with that:https://jsfiddle.net/nipee/sanjmtu7/.
In the fiddle you can input your function signature e.g. safeTransferFrom(address,address,uint256,uint256,bytes) and it will return you the method ID.

0xf242432a
= ABI encoded signature for the function safeTransferFrom(address,address,uint256,uint256,bytes)

input data for each of the inputs of the function you want to callThese always need to be 64 chars long and to achieve this, they are pre-padded with 0 until 64 chars

The safeTransferFrom we like to call contains the following arguments:
address from, address to, uint256 id, uint256 amount, bytes calldata

Together this gives the following (see table below for the breakdown)
000000000000000000000000eb947ed047020f3c2982d35ac2a8ebe8a73302820000000000000000000000008fe26c6ff544bee01f41e6f87e6d0ead0ad274050000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000

Input data breakdown

In the below table, a breakdown is done of the input data section of the functionSignature, specifically for the safeTransferFrom(address,address,uint256,uint256,bytes)

Suppose we want to perform the function with the following parameters.
Send an NFT

  • from 0xeb947ed047020f3c2982d35ac2a8ebe8a7330282
  • to 0x8fe26c6ff544bee01f41e6f87e6d0ead0ad27405
  • tokenId 101
  • amount 1

This translates to the following input data breakdown:

InputInput safeTransferFromRulefunctionSignature encoded
1from address

from = 0xeb947ed047020f3c2982d35ac2a8ebe8a7330282
0x stripped, zero left-padded until 64 chars000000000000000000000000eb947ed047020f3c2982d35ac2a8ebe8a7330282
2to address

to = 0x8fe26c6ff544bee01f41e6f87e6d0ead0ad27405
0x stripped, zero left-padded until 64 chars0000000000000000000000008fe26c6ff544bee01f41e6f87e6d0ead0ad27405
3token id that needs to be transferred

tokenId = 101
HEXdecimal encoded, zero lef-padded until 64 chars0000000000000000000000000000000000000000000000000000000000000065
4amount of tokens to be transferred

amount = 1
HEXdecimal encoded, zero lef-padded until 64 chars0000000000000000000000000000000000000000000000000000000000000001
5calldata bytesin this case a fixed stream (128 chars, notice the a in the middle)00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000

Putting all of the above togehter we get the followig functionSignature (methodID and input data):

0xf242432a000000000000000000000000eb947ed047020f3c2982d35ac2a8ebe8a73302820000000000000000000000008fe26c6ff544bee01f41e6f87e6d0ead0ad274050000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000

๐Ÿ“˜

All blockchain contracts are public. You can lookup the possible functions and their arguments through the relevant blockexplorers. For example, here you can find the contract code for our own Metaring collection (you will find the safeTransferFrom function inside this contract): https://polygonscan.com/address/0xba8c3db050dd99cbe7f980f3d8f48084c6dcc20b/transactions#code

2. Signing the EIP712 JSON document

๐Ÿšง

This has to be done by the source wallet!

๐Ÿ“˜

Signing is done off chain, so no gas fee has to be paid.

Signing the EIP712 JSON document can be done very easily either by using the Venly API or by using the Venly Widget.

3. Executing the transaction

๐Ÿšง

This has to be done by the executor wallet (who will be paying for the gas fee)!

After the source wallet has signed the EIP712 document, it needs to provide the signature to the executor wallet which can then use it as input to call the executeMetaTransaction method on the specific contract. Calling this is again easy using the Venly API or the Venly Widget.

function executeMetaTransaction(
    address userAddress,
    bytes memory functionSignature,
    bytes32 sigR,
    bytes32 sigS,
    uint8 sigV
)

The executeMetaTransaction method looks like this:

Where:

  • userAddress = the address from which the transaction must appear to be originating. I.e. the address of the wallet that signed the EIP712 document
  • functionSignature = the functionSignature that was also included in the EIP712 document
  • sigR = the r value of the signature from step 2
  • sigS = the s value of the signature from step 2
  • sigV = the v value of the signature from step 2

๐Ÿ“˜

Note that the executeMetaTransaction function needs to be called on the specific smart contract, so the to in the transaction-execution should reflect the address of the relevant smart contract.

In our example we have to perform the following call:

POST /api/transactions/execute
ParameterParam TypeValueDescription
Signing-MethodHeaderid:valueid: This is the ID of the signing method
value: This is the value of the signing method
{
    "transactionRequest":
    {
        "type": "CONTRACT_EXECUTION",
        "functionName": "executeMetaTransaction",
        "value": 0.0,
        "inputs":
        [
            {
                "type": "address", // the from address
                "value": "0xeb947ed047020f3c2982d35ac2a8ebe8a7330282"
            },
            {
                "type": "bytes", // the functionSignature
                "value": "0xf242432a000000000000000000000000eb947ed047020f3c2982d35ac2a8ebe8a73302820000000000000000000000008fe26c6ff544bee01f41e6f87e6d0ead0ad274050000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"
            },
            {
                "type": "bytes32",
                "value": "0xc469e9bdc3419aa6e6dac835e94c41f9deae82566fc0e4ecf80fa5c5f3eaa017"
            },
            {
                "type": "bytes32",
                "value": "0x3d01d29561920e8bb610cc6fc04f2e003f7bfb400e906d6605699b9377b425a5"
            },
            {
                "type": "uint8",
                "value": "0x1c"
            }
        ],
        "walletId": "252a4dab-2b98-4745-9c92-2d3fdef9b95e",
        "to": "0x88f9564d3894d66b4406c673b37bb8f91cce452f", // the contract address for which we want to do a safeTransferFrom
        "secretType": "MATIC"
    }
}