A Guide to Gasless ERC20 Token Transfer

Posted By : Ashish

Nov 20, 2023

Find out the step-by-step guide for gasless ERC20 token transfers in Ethereum development services to streamline transactions and foster accessibility in blockchain assets. 

 

Gasless ERC20 Token Transfers: Embracing Efficiency and Accessibility

 

In the evolving world of blockchain technology, the implementation of gasless ERC20 token transfers stands out as a significant innovation. This approach utilizes meta-transactions to enhance the efficiency and accessibility of token transfers on the Ethereum network.

 

What are ERC20 Tokens?

 

ERC20 tokens are digital assets built on the Ethereum blockchain. They adhere to a specific set of standards, making them interoperable with various applications and services within the Ethereum ecosystem.

 

Suggested Read | ERC-20 Token Standard | Development Essentials

 

The Role of Meta Transactions

 

Meta transactions represent a pivotal shift in how blockchain transactions are processed. By allowing a third party to pay the transaction fees ('gas'), users can transfer ERC20 tokens without incurring these costs directly. This method significantly lowers the barrier to entry and makes transactions more user-friendly.

 

Exploring the Solidity Code

 

IERC20Permit Interface

 

The IERC20Permit interface extends the standard ERC20 functionalities, introducing the permit function. This function is crucial for authorizing third-party transactions without the token holder incurring gas fees.

 

GaslessTokenTransfer Contract

 

The GaslessTokenTransfer contract leverages the permit method to enable gasless transactions. The send function within this contract allows the transfer of tokens from a sender to a receiver, with the transaction fee being handled externally.

 

ERC20Permit Contract

 

The ERC20Permit contract is a concrete implementation of the ERC20 standard, incorporating the EIP-2612 standard. This includes the permit function, allowing for more flexible and efficient token transfers.

 

Example Code:

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IERC20Permit {
   function totalSupply() external view returns (uint256);

   function balanceOf(address account) external view returns (uint256);

   function transfer(address recipient, uint256 amount) external returns (bool);

   function allowance(address owner, address spender) external view returns (uint256);

   function approve(address spender, uint256 amount) external returns (bool);

   function transferFrom(
       address sender,
       address recipient,
       uint256 amount
   ) external returns (bool);

   function permit(
       address owner,
       address spender,
       uint256 value,
       uint256 deadline,
       uint8 v,
       bytes32 r,
       bytes32 s
   ) external;

   event Transfer(address indexed from, address indexed to, uint256 value);
   event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract GaslessTokenTransfer {
   function send(
       address token,
       address sender,
       address receiver,
       uint256 amount,
       uint256 fee,
       uint256 deadline,
       // Permit signature
       uint8 v,
       bytes32 r,
       bytes32 s
   ) external {
       // Permit
       IERC20Permit(token).permit(
           sender,
           address(this),
           amount + fee,
           deadline,
           v,
           r,
           s
       );
       // Send amount to receiver
       IERC20Permit(token).transferFrom(sender, receiver, amount);
       // Take fee - send fee to msg.sender
       IERC20Permit(token).transferFrom(sender, msg.sender, fee);
   }
}


/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
   /*//////////////////////////////////////////////////////////////
                                EVENTS
   //////////////////////////////////////////////////////////////*/

   event Transfer(address indexed from, address indexed to, uint256 amount);

   event Approval(address indexed owner, address indexed spender, uint256 amount);

   /*//////////////////////////////////////////////////////////////
                           METADATA STORAGE
   //////////////////////////////////////////////////////////////*/

   string public name;

   string public symbol;

   uint8 public immutable decimals;

   /*//////////////////////////////////////////////////////////////
                             ERC20 STORAGE
   //////////////////////////////////////////////////////////////*/

   uint256 public totalSupply;

   mapping(address => uint256) public balanceOf;

   mapping(address => mapping(address => uint256)) public allowance;

   /*//////////////////////////////////////////////////////////////
                           EIP-2612 STORAGE
   //////////////////////////////////////////////////////////////*/

   uint256 internal immutable INITIAL_CHAIN_ID;

   bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

   mapping(address => uint256) public nonces;

   /*//////////////////////////////////////////////////////////////
                              CONSTRUCTOR
   //////////////////////////////////////////////////////////////*/

   constructor(string memory _name, string memory _symbol, uint8 _decimals) {
       name = _name;
       symbol = _symbol;
       decimals = _decimals;

       INITIAL_CHAIN_ID = block.chainid;
       INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
   }

   /*//////////////////////////////////////////////////////////////
                              ERC20 LOGIC
   //////////////////////////////////////////////////////////////*/

   function approve(address spender, uint256 amount) public virtual returns (bool) {
       allowance[msg.sender][spender] = amount;

       emit Approval(msg.sender, spender, amount);

       return true;
   }

   function transfer(address to, uint256 amount) public virtual returns (bool) {
       balanceOf[msg.sender] -= amount;

       // Cannot overflow because the sum of all user
       // balances can't exceed the max uint256 value.
       unchecked {
           balanceOf[to] += amount;
       }

       emit Transfer(msg.sender, to, amount);

       return true;
   }

   function transferFrom(
       address from,
       address to,
       uint256 amount
   ) public virtual returns (bool) {
       uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

       if (allowed != type(uint256).max)
           allowance[from][msg.sender] = allowed - amount;

       balanceOf[from] -= amount;

       // Cannot overflow because the sum of all user
       // balances can't exceed the max uint256 value.
       unchecked {
           balanceOf[to] += amount;
       }

       emit Transfer(from, to, amount);

       return true;
   }

   /*//////////////////////////////////////////////////////////////
                            EIP-2612 LOGIC
   //////////////////////////////////////////////////////////////*/

   function permit(
       address owner,
       address spender,
       uint256 value,
       uint256 deadline,
       uint8 v,
       bytes32 r,
       bytes32 s
   ) public virtual {
       require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

       // Unchecked because the only math done is incrementing
       // the owner's nonce which cannot realistically overflow.
       unchecked {
           address recoveredAddress = ecrecover(
               keccak256(
                   abi.encodePacked(
                       "\x19\x01",
                       DOMAIN_SEPARATOR(),
                       keccak256(
                           abi.encode(
                               keccak256(
                                   "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                               ),
                               owner,
                               spender,
                               value,
                               nonces[owner]++,
                               deadline
                           )
                       )
                   )
               ),
               v,
               r,
               s
           );

           require(
               recoveredAddress != address(0) && recoveredAddress == owner,
               "INVALID_SIGNER"
           );

           allowance[recoveredAddress][spender] = value;
       }

       emit Approval(owner, spender, value);
   }

   function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
       return
           block.chainid == INITIAL_CHAIN_ID
               ? INITIAL_DOMAIN_SEPARATOR
               : computeDomainSeparator();
   }

   function computeDomainSeparator() internal view virtual returns (bytes32) {
       return
           keccak256(
               abi.encode(
                   keccak256(
                       "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                   ),
                   keccak256(bytes(name)),
                   keccak256("1"),
                   block.chainid,
                   address(this)
               )
           );
   }

   /*//////////////////////////////////////////////////////////////
                       INTERNAL MINT/BURN LOGIC
   //////////////////////////////////////////////////////////////*/

   function _mint(address to, uint256 amount) internal virtual {
       totalSupply += amount;

       // Cannot overflow because the sum of all user
       // balances can't exceed the max uint256 value.
       unchecked {
           balanceOf[to] += amount;
       }

       emit Transfer(address(0), to, amount);
   }

   function _burn(address from, uint256 amount) internal virtual {
       balanceOf[from] -= amount;

       // Cannot underflow because a user's balance
       // will never be larger than the total supply.
       unchecked {
           totalSupply -= amount;
       }

       emit Transfer(from, address(0), amount);
   }
}

contract ERC20Permit is ERC20 {
   constructor(
       string memory _name,
       string memory _symbol,
       uint8 _decimals
   ) ERC20(_name, _symbol, _decimals) {}

   function mint(address to, uint256 amount) public {
       _mint(to, amount);
   }
}

 

Advantages of Gasless Token Transfers

 

  • Cost Efficiency: Users can transfer tokens without the burden of gas fees.
  • User Accessibility: Lowers the entry barrier for new users unfamiliar with the complexities of gas fees.
  • Enhanced Transaction Flow: Streamlines the process, making it more appealing for regular use.

 

Check It Out | ERC-20 Token Standard | Things You Must Know

 

Conclusion

 

The integration of gasless ERC20 token transfers marks a significant stride in blockchain technology, offering a more accessible and efficient means of handling digital assets. As blockchain matures, innovations like these play a critical role in shaping its future, making it more inclusive and user-friendly. 

 

Interested in ERC20 token development? Connect with our Ethereum developers today to get started. 

Leave a

Comment

Name is required

Invalid Name

Comment is required

Recaptcha is required.

blog-detail

September 8, 2024 at 02:22 am

Your comment is awaiting moderation.

By using this site, you allow our use of cookies. For more information on the cookies we use and how to delete or block them, please read our cookie notice.

Chat with Us
Telegram Button
Youtube Button
Contact Us

Oodles | Blockchain Development Company

Name is required

Please enter a valid Name

Please enter a valid Phone Number

Please remove URL from text