🪙 ERC20 Multi Meta Transaction
A meta transaction is a method that allows users to interact with the Ethereum network without requiring ETH for gas fees. In this setup, a user signs a transaction with their private key, while a third-party (a relayer) submits this transaction to the network and pays the gas fees. The transaction data is typically structured and signed according to the EIP-712 standard, enhancing security and readability. Furthermore, the ERC20 Permit method allows the token owner to delegate gas payments to another party, supporting the execution of the meta transaction. This process enhances the accessibility and usability of the Ethereum network, especially for users who do not hold ETH. To provide a better understanding, we've included a screenshot of the architecture.
EIP-712 - Typed Structured Data Hashing and Signing
EIP-712 is an Ethereum Improvement Proposal that provides a standard for structuring and displaying data in a human-readable format during Ethereum transactions. It improves the usability of signed data structures, making it easier for users to understand what they're signing.
ERC-2612 - Permit Extension for EIP-20 Signed Approvals
ERC-20 permit, also known as EIP-2612, is a method used in Ethereum blockchain that allows holders of ERC-20 tokens to approve the spending of their tokens via a signature, instead of requiring an Ethereum transaction.
The smart contract Forwarder.sol
extends the EIP712 and entails the following core functions.
verify
: Verifies the signature based on the typed structured data.
function verify(MetaTx calldata _tx, bytes calldata _signature) internal view returns (bool) {
address signer = _hashTypedDataV4(
keccak256(abi.encode(_TYPEHASH, _tx.from, _tx.to, _tx.token, _tx.amount, _tx.nonce))
).recover(_signature);
return signer == _tx.from && nonces[_tx.from] == _tx.nonce;
}
batchTransfer
Executes meta transactions in batches. If one transaction is invalid or has a malformed signature, the process continues as long as the remaining transactions are valid.
function batchTransfer(MetaTxWithSig[] calldata _metaTxWithSig, uint256 gas) external onlyOwner {
uint256 transactionsLength = _metaTxWithSig.length;
for (uint256 i = 0; i < transactionsLength; ++i) {
bytes calldata signature = _metaTxWithSig[i].signature;
MetaTx calldata metaTx = _metaTxWithSig[i].metaTx;
if (verify(metaTx, signature) && whitelistedTokens[IERC20(metaTx.token)] && metaTx.to != address(0)) {
nonces[metaTx.from] = nonces[metaTx.from] + 1;
metaTx.token.transferFrom(metaTx.from, metaTx.to, metaTx.amount);
}
require(gasleft() > gas / 63, "Not enough gas");
}
}
Here's the sample implementation of ERC20 Permit
contract Token1 is ERC20, ERC20Permit {
constructor() ERC20("Token1", "T1") ERC20Permit("Token1") {
_mint(msg.sender, 1000000 * 10 ** 18);
}
}
For this project to function, Redis must be installed, serving as our database to store transaction data.
After you clone the repository, change your directory to the project's directory and run the following commands:
cp frontend/.env.development frontend/.env
cp relayer/.env.development relayer/.env
yarn install
redis-server
4. Start Hardhat local node server. Open a new tab in your terminal, change directory to the contracts
folder and simply run:
npx hardhat node
yarn contracts:deploy:localhost
It should display something like this in your terminal.
Forwarder deployed to: 0x...123
Token1 whitelisted and deployed to 0x...123
Token2 whitelisted and deployed to 0x...123
Token3 whitelisted and deployed to 0x...123
In the relayer workspace, copy and paste the deployed forwarder
contract address, along with the private key of Hardhat's deployer, typically designated as account index 0.
FORWARDER_LOCALHOST_ADDRESS=
PRIV_KEY=
In the frontend
workspace, copy and paste as well the forwarder
contract address.
NEXT_PUBLIC_FORWARDER_LOCALHOST_ADDRESS
Same in the frontend
workspace, copy and paste the tokens addresses in the blockchain.ts
file under the utils
folder.
token: {
tokens: [
{
address: {
localhost: '0x...',
sepolia: '',
goerli: ''
}
},
{
address: {
localhost: '0x..',
sepolia: '',
goerli: ''
}
},
{
address: {
localhost: '0x..',
sepolia: '',
goerli: ''
}
}
],
}
yarn relayer:dev
yarn frontend:dev
9. In your browser, navigate to localhost:3000
, and that's it you successfully run the project. Have fun! 😄
We utilize Hardhat given its robust capabilities for testing smart contracts, encompassing Solidity debugging and clear error messages.
To run the test suite. Simply run:
yarn contracts:test
Here's the sample report from the contract's test suite
·--------------------------------|----------------------------|-------------|-----------------------------·
| Solc version: 0.8.17 · Optimizer enabled: false · Runs: 200 · Block limit: 30000000 gas │
·································|····························|·············|······························
| Methods │
··············|··················|··············|·············|·············|···············|··············
| Contract · Method · Min · Max · Avg · # calls · usd (avg) │
··············|··················|··············|·············|·············|···············|··············
| Forwarder · batchTransfer · - · - · 547434 · 1 · - │
··············|··················|··············|·············|·············|···············|··············
| Forwarder · whitelistToken · - · - · 46764 · 3 · - │
··············|··················|··············|·············|·············|···············|··············
| Token1 · permit · 77091 · 77103 · 77099 · 3 · - │
··············|··················|··············|·············|·············|···············|··············
| Token1 · transfer · 52143 · 52155 · 52151 · 3 · - │
··············|··················|··············|·············|·············|···············|··············
| Token2 · permit · 77067 · 77103 · 77087 · 3 · - │
··············|··················|··············|·············|·············|···············|··············
| Token2 · transfer · 52143 · 52155 · 52151 · 3 · - │
··············|··················|··············|·············|·············|···············|··············
| Token3 · permit · 77091 · 77103 · 77095 · 3 · - │
··············|··················|··············|·············|·············|···············|··············
| Token3 · transfer · 52143 · 52155 · 52151 · 3 · - │
··············|··················|··············|·············|·············|···············|··············
| Deployments · · % of limit · │
·································|··············|·············|·············|···············|··············
| Forwarder · - · - · 1924066 · 6.4 % · - │
·································|··············|·············|·············|···············|··············
| Token1 · - · - · 2269191 · 7.6 % · - │
·································|··············|·············|·············|···············|··············
| Token2 · - · - · 2269191 · 7.6 % · - │
·································|··············|·············|·············|···············|··············
| Token3 · - · - · 2269191 · 7.6 % · - │
·--------------------------------|--------------|-------------|-------------|---------------|-------------·
This project is licensed under GNU General Public License v3.
If this project interests you, follow me on Twitter