Gasless tx/ permit & transferFrom - ERC20

This guide describes how to execute a gasless transaction using permit and transferFrom functions (EIP-2612) with ERC20 tokens.

Intro

Gasless transactions with "permit and signed approvals" for ERC-20 tokens offer a seamless way to sponsor token transfers using a payer account where the holder of the tokens does not have to spend gas fees. This feature empowers users to authorize an ERC20 token transfer via a signed message, allowing someone else to cover the gas costs. It simplifies the process by eliminating the need for a gas balance in your wallet, making transactions more accessible and user-friendly.

Gasless Transaction for ERC20 using `permit` & `transferFrom` function

Gasless Transaction for ERC20 using permit & transferFrom function

Scope

🚧

This only works for ERC20 token contracts supporting permit and transferFrom function. Read about EIP-2612.

Code Example

📘

View the code example for the implementation of ERC20 gasless transaction.

Prerequisites

This example describes a gasless transaction on the Avalanche Fuji Testnet which carries out an ERC20 transfer. Make sure you have the following params ready:

ParamsDescription/ Value
ERC20 token holder wallet address (owner/User Wallet)0xb636F53AFc9Fe7fa7246E1c220DF2436936b4315
Wallet address that will pay for the gas fee (spender/Payer Wallet)0x36CD949d0ccb506C52cB40972391C1E5AA1D5c78
Name of ERC20 token contractInfinity
ERC20 token contract address0x022a39f06413e249b3739c573bdb28b98eac051b
Wallet address where the tokens are to be transferred0x6E59e504d1b64e8F344a7af3A3384c9Ae9E02371
chainId80002 for Matic Amoy testnet (find the chainId from the chain list)
nonceFetch the nonce using the nonces read function on token contract. Function: nonces(address owner). External view returns (uint).
deadlineThe time until the signed message is valid in Unix time format

Terminologies Explained

  • User Wallet/ owner: The user wallet is the wallet associated with the end-user or the person who holds the ERC20 tokens.
  • Payer Wallet/spender: 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(Wallet X): The public wallet address to which the ERC20 tokens will be transferred.

Prepare EIP712 JSON document

The first step is preparing the EIP 712 JSON document that the user wallet will sign.

{
    "types": { // copy the types section as it is
        "EIP712Domain": [
            {
                "name": "name",
                "type": "string"
            },
            {
                "name": "version",
                "type": "string"
            },
            {
                "name": "chainId",
                "type": "uint256"
            },
            {
                "name": "verifyingContract",
                "type": "address"
            }
        ],
        "Permit": [
            {
                "name": "owner",
                "type": "address"
            },
            {
                "name": "spender",
                "type": "address"
            },
            {
                "name": "value",
                "type": "uint256"
            },
            {
                "name": "nonce",
                "type": "uint256"
            },
            {
                "name": "deadline",
                "type": "uint256"
            }
        ]
    },
    "primaryType": "Permit", // this will be Permit
    "domain": {
        "name": "Infinity", // name of the ERC20 token contract
        "version": "1", // hardcoded to 1
        "chainId": "80002", // the chainId
        "verifyingContract": "0x022a39f06413e249b3739c573bdb28b98eac051b" // the ERC20 token contract address
    },
    "message": {
        "owner": "0xb636F53AFc9Fe7fa7246E1c220DF2436936b4315", // from wallet address (address that holds the ERC20 tokens)
        "spender": "0x36CD949d0ccb506C52cB40972391C1E5AA1D5c78", // wallet address of PAYER WALLET (who will pay the gas fee)
        "value": "2000000000000000000", // value being transferred, (value needs to be in WEI)
        "nonce": "1", // nonce you got from the nonces read function on token contract
        "deadline": "1726390448" // time until the signed message will be valid
    }
}

Sign the EIP712 JSON document

🚧

  • This has to be done by the user wallet (that holds the ERC20 tokens)!
  • Signing is done off chain, so no gas fees have to be paid.

Request: reference

POST /api/signatures
ParameterParam TypeValueDescriptionExample Value
Signing-MethodHeaderid:valueid: This is the ID of the signing method
value: This is the value of the signing method
756ae7a7-3713-43ee-9936-0dff50306488:123456
{
    "signatureRequest": {
        "secretType": "MATIC", // blockhain of the tx
        "walletId": "c701b419-b0bc-4391-8f91-f99bbcf67ab5", // the wallet ID that will sign the message (user wallet)
        "type": "EIP712",
        "data": {
            "types": {
                "EIP712Domain": [
                    {
                        "name": "name",
                        "type": "string"
                    },
                    {
                        "name": "version",
                        "type": "string"
                    },
                    {
                        "name": "chainId",
                        "type": "uint256"
                    },
                    {
                        "name": "verifyingContract",
                        "type": "address"
                    }
                ],
                "Permit": [
                    {
                        "name": "owner",
                        "type": "address"
                    },
                    {
                        "name": "spender",
                        "type": "address"
                    },
                    {
                        "name": "value",
                        "type": "uint256"
                    },
                    {
                        "name": "nonce",
                        "type": "uint256"
                    },
                    {
                        "name": "deadline",
                        "type": "uint256"
                    }
                ]
            },
            "primaryType": "Permit",
            "domain": {
                "name": "Infinity",
                "version": "1",
                "chainId": "80002",
                "verifyingContract": "0x022a39f06413e249b3739c573bdb28b98eac051b"
            },
            "message": {
                "owner": "0xb636F53AFc9Fe7fa7246E1c220DF2436936b4315",
                "spender": "0x36CD949d0ccb506C52cB40972391C1E5AA1D5c78",
                "value": "2000000000000000000",
                "nonce": "1",
                "deadline": "1726390448"
            }
        }
    }
}

Response body

📘

The r, s, and v 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": "0x225a86d284b3b02fd76e49cdefb157b329a67a1fb2dcb50310de14b854251001",
        "s": "0x5f198ef763d42c27d39b65e3ba032aa39015fa7c88864ad9652b212a2237fc14",
        "v": "0x1c",
        "signature": "0x225a86d284b3b02fd76e49cdefb157b329a67a1fb2dcb50310de14b8542510015f198ef763d42c27d39b65e3ba032aa39015fa7c88864ad9652b212a2237fc141c"
    }
}

Executing the transactions

After that user wallet has signed the EIP712 message, they provide the r, s, and v values to the payer wallet. Next, the payer wallet needs to do two contract function calls.

🚧

The two transactions have to be executed by the payer wallet (who will be paying for the gas fee)!

1. Call permit function

The payer wallet will call the permit function to set the allowance. Allowance is permitting someone else to move your tokens.

Request: reference

POST /api/transactions/execute
ParameterParam TypeValueDescriptionExample Value
Signing-MethodHeaderid:valueid: This is the ID of the signing method
value: This is the value of the signing method
756ae7a7-3713-43ee-9936-0dff50306488:123456
{
    "transactionRequest": {
        "type": "CONTRACT_EXECUTION",
        "functionName": "permit", // the function name
        "value": 0.0, // this will be 0.0
        "inputs": [
            {
                "type": "address", // the from address (User Wallet)
                "value": "0xb636F53AFc9Fe7fa7246E1c220DF2436936b4315"
            },
            {
                "type": "address", // the spender address (Payer Wallet)
                "value": "0x36CD949d0ccb506C52cB40972391C1E5AA1D5c78"
            },
            {
                "type": "uint256", // the amount being transferred
                "value": "2000000000000000000"
            },
            {
                "type": "uint256", // deadline from the EIP172.message request
                "value": "1726390448"
            },
            {
                "type": "uint8", // v from the sign eip712 message response
                "value": "0x1c"
            },
            {
                "type": "bytes32", // r from the sign eip712 message response
                "value": "0x225a86d284b3b02fd76e49cdefb157b329a67a1fb2dcb50310de14b854251001"
            },
            {
                "type": "bytes32", // s from the sign eip712 message response
                "value": "0x5f198ef763d42c27d39b65e3ba032aa39015fa7c88864ad9652b212a2237fc14"
            }
        ],
        "walletId": "6249865f-d413-4ebc-bfa8-6cb4c89cc930", // wallet ID of Payer Wallet
        "to": "0x022a39f06413e249b3739c573bdb28b98eac051b", // the token contract address
        "secretType": "MATIC" // blockchain of the tx
    }
}

Response body

Now the payer wallet has successfully set an allowance of 2000000000000000000 (assuming the erc20 token uses 18 decimals, equivalent to 2 tokens), and they can move them anywhere by paying the gas fee.

{
    "success": true,
    "result": {
        "id": "1481f1a0-997a-47e4-8f96-523d36348b2d",
        "transactionHash": "0x12ad50765c59cae07f122346bcfeba2946ec49b7a4c6c770c720ea38f13a9abd"
    }
}

2. Call transferFrom function

The final step is calling the transferFrom function by the payer wallet. In this function call the from address will be the user wallet. The to address is where the tokens will be transferred and the payer wallet will pay the gas fee.

📘

The payer wallet can call the transferFrom function and transfer the same amount of tokens that were set in the allowance.

Request: reference

POST /api/transactions/execute
ParameterParam TypeValueDescriptionExample Value
Signing-MethodHeaderid:valueid: This is the ID of the signing method
value: This is the value of the signing method
756ae7a7-3713-43ee-9936-0dff50306488:123456
{
    "transactionRequest": {
        "type": "CONTRACT_EXECUTION",
        "functionName": "transferFrom", // the function name
        "value": 0.0, // this will be 0.0
        "inputs": [
            {
                "type": "address", // the from address (User Wallet)
                "value": "0xb636F53AFc9Fe7fa7246E1c220DF2436936b4315"
            },
            {
                "type": "address", // the to address (where the tokens will be sent to)
                "value": "0x6E59e504d1b64e8F344a7af3A3384c9Ae9E02371"
            },
            {
                "type": "uint256", // the amount being transferred
                "value": "2000000000000000000"
            }
        ],
        "walletId": "6249865f-d413-4ebc-bfa8-6cb4c89cc930", // wallet ID of the Payer Wallet
        "to": "0x022a39f06413e249b3739c573bdb28b98eac051b", // the token contract address
        "secretType": "MATIC" // blockchain of the tx
    }
}

Response body

{
    "success": true,
    "result": {
        "id": "7464177f-237c-4393-b7ec-ef01b58560f5",
        "transactionHash": "0x1d1e28d80420029d35ba4de7c407d6493c8a7c0b8a807558d52bd72930918d1f"
    }
}

Result: Successful Gasless Transaction for ERC20 token

Successful Gasless Transaction for ERC20

Successful Gasless Transaction for ERC20