Gasless/Meta Transactions
This page describes how to execute gasless or meta transactions on ERC1155 tokens (NFTs).
What are Gasless/Meta transactions?
Gasless or meta transactions are a way to handle transaction costs more simply for users who are not familiar with the technical details of the cryptocurrency world. In these transactions, someone else covers the transaction fees, making it more convenient for users. This helps create a smoother experience, especially for those who are new to the crypto world.
Gasless or Meta transactions allow a wallet to do contract executions on behalf of another wallet and (most importantly) thus pay that transaction's gas fee.
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 let wallet-B pay for the gas fee.
The gasless/meta transactions are only supported for the following chains:
- Polygon (MATIC)
- Avalanche
- Binance Smart Chain
- Arbitrum
- BASE
Terminologies Explained
- User Wallet: The user wallet is the wallet associated with the end-user or the person who holds the NFT.
- Payer Wallet: The wallet that pays for the transaction fees associated with executing a smart contract or a transaction on the blockchain.
from address
: The public wallet address of the user wallet.to address
: The public wallet address where the NFT will be transferred to.tokenId
: The ID of the NFT that is to be transferred.userAddress
: The public wallet address of the user wallet.
Prerequisites
- 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 agetNonce
(read) function
Code Example
View the code example for gasless/meta transaction.
Basic flow
- An EIP712 JSON document is generated specifying the contract, method, and parameters that are allowed to be executed on the user wallet’s behalf
- The user wallet signs this JSON document using our Widget or Wallet-API and supplies the signature to the owner of the payer wallet (gas fee payer)
- Using the payer wallet, the
executeMetaTransaction
function on the contract needs to be called supplying the signature of the EIP712 document - The method specified in step 1 will be executed using the parameters also supplied in step 1 (The payer wallet pays for the GAS fees of the transaction)
An example of how this could look like for an NFT transfer:
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 an EIP712 document, calling safeTransferFrom
on the 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": "0x0000000000000000000000000000000000000000000000000000000000013882"
},
"primaryType": "MetaTransaction",
"message": {
"nonce": 0,
"from": "0xeB947ED047020F3C2982d35Ac2a8EbE8A7330282",
"functionSignature": "0xf242432a000000000000000000000000eb947ed047020f3c2982d35ac2a8ebe8a73302820000000000000000000000008fe26c6ff544bee01f41e6f87e6d0ead0ad274050000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"
}
}
1.1 types
types
types
is about defining the structure and data types of the document. This part you should take as is.
1.2 domain
domain
domain
is about describing the contract you want to interact with. Here are some customizations that need to be done:
Property | Description |
---|---|
name | The name of the (NFT) contract you want to interact with |
version | This should always be 1 |
verifyingContract | The contract address you want to interact with e.g. the NFT contract address |
salt | The hex value of the chainId , left padded with 0 until its length equals 64 chars, then prepended with 0x. In this case 80002 = Polygon Amoy Testnet, an overview can be found at https://chainlist.org/So the salt value for Amoy will be: 0x0000000000000000000000000000000000000000000000000000000000080002 |
1.3 primaryType
primaryType
primaryType
needs to be MetaTransaction
1.4 message
message
message
is about defining which contract call to execute and what parameters should be used.
Property | Description |
---|---|
nonce | This 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 (_user wallet_) |
from | The address of the signer (_user wallet_) |
functionSignature | The 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) |
2. Building the functionSignature
functionSignature
The functionSignature
defines what the signer allows to be executed by a third party. To explain how it is built we’ll work with an example:
0xf242432a000000000000000000000000Bc4C3E0b388b65E64350E7321317a694f23eE6b40000000000000000000000002983793B56ff06FE7fDA56e69563b1D55991c9ce0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000
This is the functionSignature
for safeTransferFrom(address,address,uint256,uint256,bytes)
+ input data and is build as follows:
functionSignature Part | Description | Example |
---|---|---|
Method ID | This is the method ID of the function you want to call (ABI encoded function signature). |
|
input data for each of the inputs of the function you want to call | These always need to be 64 chars long and to achieve this, they are pre-padded with 0 until 64 chars |
|
Input data breakdown
In the table below, 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
0xBc4C3E0b388b65E64350E7321317a694f23eE6b4
to
0x2983793B56ff06FE7fDA56e69563b1D55991c9ce
tokenId
6
amount
1
This translates to the following input data breakdown:
Input | Input safeTransferFrom | Rule | functionSignature encoded |
---|---|---|---|
1 | from address from = 0xBc4C3E0b388b65E64350E7321317a694f23eE6b4 | 0x stripped, zero left-padded until 64 chars | 000000000000000000000000Bc4C3E0b388b65E64350E7321317a694f23eE6b4 |
2 | to address to = 0x2983793B56ff06FE7fDA56e69563b1D55991c9ce | 0x stripped, zero left-padded until 64 chars | 0000000000000000000000002983793B56ff06FE7fDA56e69563b1D55991c9ce |
3 | token id that needs to be transferredtokenId = 6 | HEXdecimal encoded, zero lef-padded until 64 chars | 0000000000000000000000000000000000000000000000000000000000000006 |
4 | amount of tokens to be transferredamount = 1 | HEXdecimal encoded, zero lef-padded until 64 chars | 0000000000000000000000000000000000000000000000000000000000000001 |
5 | calldata bytes | in this case a fixed stream (128 chars, notice the a in the middle) | 00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000 |
Putting all of the above together we get the following functionSignature
(methodID + input data):
0xf242432a000000000000000000000000Bc4C3E0b388b65E64350E7321317a694f23eE6b40000000000000000000000002983793B56ff06FE7fDA56e69563b1D55991c9ce0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000
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
3. Signing the EIP712 JSON document
- This has to be done by the user 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.
Example Request: reference
POST /api/signatures
Parameter | Param Type | Value | Description | Example Value |
---|---|---|---|---|
Signing-Method | Header | id:value | id : This is the ID of the signing methodvalue : This is the value of the signing method | 756ae7a7-3713-43ee-9936-0dff50306488:123456 |
{
"signatureRequest": {
"secretType": "BASE",
"walletId": "97ce941c-52f8-4789-abcd-afa704f70351",
"type": "EIP712",
"data": {
"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": "0x675fd9b8a2821196c932aa6906a5cd62f281ac1b",
"salt": "0x0000000000000000000000000000000000000000000000000000000000014a34"
},
"primaryType": "MetaTransaction",
"message": {
"nonce": 0,
"from": "0xBc4C3E0b388b65E64350E7321317a694f23eE6b4",
"functionSignature": "0xf242432a000000000000000000000000Bc4C3E0b388b65E64350E7321317a694f23eE6b40000000000000000000000002983793B56ff06FE7fDA56e69563b1D55991c9ce0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"
}
}
}
}
Response Body
The
r
,s
, andv
values are to be provided to the payer wallet who will execute the transaction and pay the gas fee.
{
"success": true,
"result": {
"type": "HEX_SIGNATURE",
"r": "0xb951e6d5923579aa3ceb8039d7cd6c7250f76cf7ec67d37b417216e6eaf389e5",
"s": "0x2ac10a0e9cc6f6545c89bc597bf28202f6681fad7dcda2dfe36385b75b683c38",
"v": "0x1c",
"signature": "0xb951e6d5923579aa3ceb8039d7cd6c7250f76cf7ec67d37b417216e6eaf389e52ac10a0e9cc6f6545c89bc597bf28202f6681fad7dcda2dfe36385b75b683c381c"
}
}
4. Executing the transaction
This has to be done by the payer wallet (who will be paying for the gas fee)!
After the user wallet has signed the EIP712 document, it needs to provide the signature to the payer 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.
Widget Function:
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 (user wallet)functionSignature
= thefunctionSignature
that was also included in the EIP712 documentsigR
= ther
value of the signature from step 3sigS
= thes
value of the signature from step 3sigV
= thev
value of the signature from step 3
Note that the
executeMetaTransaction
function needs to be called on the specific smart contract, so theto
in the transaction-execution should reflect the address of the relevant smart contract.
Example Request: reference
POST /api/transactions/execute
Parameter | Param Type | Value | Description | Example Value |
---|---|---|---|---|
Signing-Method | Header | id:value | id : This is the ID of the signing methodvalue : This is the value of the signing method | 756ae7a7-3713-43ee-9936-0dff50306488:123456 |
{
"transactionRequest":
{
"type": "CONTRACT_EXECUTION",
"functionName": "executeMetaTransaction", // the function name you want to call
"value": 0,
"inputs":
[
{
"type": "address", // the from wallet address
"value": "0xBc4C3E0b388b65E64350E7321317a694f23eE6b4"
},
{
"type": "bytes", // the functionSignature
"value": "0xf242432a000000000000000000000000Bc4C3E0b388b65E64350E7321317a694f23eE6b40000000000000000000000002983793B56ff06FE7fDA56e69563b1D55991c9ce0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"
},
{
"type": "bytes32", // the r value of the signature
"value": "0xb951e6d5923579aa3ceb8039d7cd6c7250f76cf7ec67d37b417216e6eaf389e5"
},
{
"type": "bytes32", // the s value of the signature
"value": "0x2ac10a0e9cc6f6545c89bc597bf28202f6681fad7dcda2dfe36385b75b683c38"
},
{
"type": "uint8", // the v value of the signature
"value": "0x1c"
}
],
"walletId": "ae4fb862-2b21-460b-a657-1b15f0fe678e", // the wallet ID of the PAYER WALLET (who will pay the gas fee)
"to": "0x675fd9b8a2821196c932aa6906a5cd62f281ac1b", // the contract address for which we want to do a safeTransferFrom
"secretType": "BASE" // blockchain of the tx
}
}
Response Body
{
"success": true,
"result": {
"id": "5b81f997-5e83-453a-a748-80c62b218129",
"transactionHash": "0xac5625242ce7099279ac1fc9629f06c5b0dcec210ae7e8bf996a07fd2378d915"
}
}
Result: Successful Meta Transaction
Updated 4 months ago