1. Place the problem
Suppose we are about to issue 1 ERC-20 coin on Ethereum and need an airdrop for an initial user base, to increase the number of token holders as well as to broaden the promotion for the upcoming token.
Basically, we will need to take the following steps to build the Airdrop feature:
- Of course, you must deploy the ERC-20 contract token first.
- Writing Airdrop contract: Contract saves a list of addresses that register to receive tokens. After the Airdrop registration period ends , users can call the contract to claim token.
- In addition, we can build more bots (such as Telegram bot) to track users with the condition follow page, share articles. v..v to register for Airdrop .
Merkle Airdrop
With the above Airdrop contract design, we have 1 problem. With airdrops , the number of people who register to receive tokens depending on their size can range from hundreds, thousands or even tens of thousands of people.
Ethereum ‘s storage fees as well as transaction fees are currently not cheap at all, imagine an array of contracts containing thousands of registered addresses will be very expensive, not to mention new transactions. address to the array again. In general, the above Airdrop contract design solution is not at all optimal for your wallet.
With Merkle Airdrop , we will not have to worry about having to save a large number of addresses registered in the contract, while still ensuring the verification of the claim address registered before or not? From there, it saves a lot of costs in Airdrop .
2. Theoretical basis
Merkle Tree
The Merkle Tree is simply a data structure in the form of a binary tree, the value of the nodes, the leaves being the hash of the data.
To create a Merkle Tree , from the data we have, use the hash function to compute the corresponding hash value of the data, which will be the leaf node of the tree. Continue to hash adjacent values until there is a unique hash value (Root of the Merkle tree). How is a Merkle Tree computed in the figure below?
Merkle Tree helps in verifying and verifying data integrity while consuming only a small amount of storage space (due to the small size of the hash). In Blockchain, Merkle Tree is very popular to verify transactions (Used in Bitcoin, Ethereum, etc.)
Merkle Proof
Merkle Proof is used to check whether the input data belongs to the Merkle Tree or not without having to reveal all the data that make up the Merkle Tree .
We will look at the example illustrated in the picture above to be able to understand what Merkle Proof is? In this example we need to prove that the data K belongs to the Merkle Tree . We need to calculate the Hash of K and then gradually climb to the root of the Merkle Tree , if the value of the Merkle Tree root is the same as the given Merkle Root value, it proves that K belongs to the Merkle Tree .
Instead of having to use all the data from the AP to recalculate the Merkle Root to see if it is the same as the original Merkle Root ? We just need to get the back nodes of the tree to verify that the K belongs to the Merkle Tree .
- The hash of L can then calculate the hash KL
- HJ ‘s hash can then calculate the IJKL hash
- The MNOP hash can then calculate the IJKLMNOP hash
- Hash of ABCDEFGH
From there we can completely calculate the Merkle Root that only needs to know the 4 node values in the Merkle Tree
3. Merkle Airdrop
The basic flow that we will implement Airdrop is as follows
- For users to register for the airdrop and save the list as follows (We can save it in the server, cloud or IPFS whatever it is). The first value is the registered address and the second is the amount of airdrop tokens for that address.
1 2 3 4 | 0x19171a5da52276b6a034CB859ddA1e905739F8B2 10000000000000000000 0x04d1eC716Fe9AC219D59b9E4f0D64D3B4339642e 10000000000000000000 0x14C06EC9402f7CD52dd0AF02979a350EAF133F76 10000000000000000000 |
- After finishing the airdrop registration time, from the list above, we calculate the Merkle Root and save it on the smart contract.
- Based on the number of airdrop registrants, we will send the corresponding number of ERC-20 tokens to the Merkle Airdrop smart contract to be able to airdrop for users.
- The user will then call the Merkle Airdrop contract to claim the amount of registered tokens. Based on Merkle Proof , the contract will calculate whether this address has registered for the airdrop or not and the number of token claim is satisfied or not? If true, the contract will send the corresponding amount of tokens to the user.
Specific example
Our side has also built an Airdrop page and so far it is still working pretty well.
Registration : Users will register to receive airdrops through BOT on Telegram with some conditions such as join box chat Telegram, follow twitter or retweet. When the user completes the registration steps, the BOT will call the Node.js server and store the user’s information in MongoDB .
Claim : After closing the airdrop registration and allowing users to claim token. User will go to the airdrop page. The server will calculate and return the Merkle Proof based on the user’s address. Then, the user signs a transaction to call the smart contract to claim, if the user has registered before, he will receive the token when completing the transaction.
4. Smart contract
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | <span class="token comment">// MerkleAirdrop.sol</span> pragma solidity <span class="token operator">^</span> <span class="token number">0.6</span> <span class="token number">.0</span> <span class="token punctuation">;</span> pragma experimental ABIEncoderV2 <span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token string">"@openzeppelin/contracts/cryptography/MerkleProof.sol"</span> <span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token string">"@openzeppelin/contracts/token/ERC20/IERC20.sol"</span> <span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token string">"@openzeppelin/contracts/token/ERC20/SafeERC20.sol"</span> <span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token string">"@openzeppelin/contracts/math/SafeMath.sol"</span> <span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token string">"@openzeppelin/contracts/proxy/Initializable.sol"</span> <span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token string">"@openzeppelin/contracts/access/Ownable.sol"</span> <span class="token punctuation">;</span> contract PhoneAirdrop is Ownable <span class="token punctuation">{</span> using SafeERC20 <span class="token keyword">for</span> <span class="token constant">IERC20</span> <span class="token punctuation">;</span> using SafeMath <span class="token keyword">for</span> uint256 <span class="token punctuation">;</span> event <span class="token function">Claimed</span> <span class="token punctuation">(</span> address claimant <span class="token punctuation">,</span> uint256 week <span class="token punctuation">,</span> uint256 balance <span class="token punctuation">)</span> <span class="token punctuation">;</span> event <span class="token function">TrancheAdded</span> <span class="token punctuation">(</span> uint256 tranche <span class="token punctuation">,</span> bytes32 merkleRoot <span class="token punctuation">,</span> uint256 totalAmount <span class="token punctuation">)</span> <span class="token punctuation">;</span> event <span class="token function">TrancheExpired</span> <span class="token punctuation">(</span> uint256 tranche <span class="token punctuation">)</span> <span class="token punctuation">;</span> event <span class="token function">RemovedFunder</span> <span class="token punctuation">(</span> address indexed _address <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token constant">IERC20</span> <span class="token keyword">public</span> token <span class="token punctuation">;</span> <span class="token function">mapping</span> <span class="token punctuation">(</span> <span class="token parameter">uint256</span> <span class="token operator">=></span> bytes32 <span class="token punctuation">)</span> <span class="token keyword">public</span> merkleRoots <span class="token punctuation">;</span> <span class="token function">mapping</span> <span class="token punctuation">(</span> <span class="token parameter">uint256</span> <span class="token operator">=></span> <span class="token function">mapping</span> <span class="token punctuation">(</span> <span class="token parameter">address</span> <span class="token operator">=></span> bool <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> claimed <span class="token punctuation">;</span> uint256 <span class="token keyword">public</span> tranches <span class="token punctuation">;</span> <span class="token function">constructor</span> <span class="token punctuation">(</span> <span class="token constant">IERC20</span> _token <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token punctuation">{</span> token <span class="token operator">=</span> _token <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">seedNewAllocations</span> <span class="token punctuation">(</span> <span class="token parameter">bytes32 _merkleRoot <span class="token punctuation">,</span> uint256 _totalAllocation</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> onlyOwner <span class="token function">returns</span> <span class="token punctuation">(</span> <span class="token parameter">uint256 trancheId</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> token <span class="token punctuation">.</span> <span class="token function">safeTransferFrom</span> <span class="token punctuation">(</span> msg <span class="token punctuation">.</span> sender <span class="token punctuation">,</span> <span class="token function">address</span> <span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> _totalAllocation <span class="token punctuation">)</span> <span class="token punctuation">;</span> trancheId <span class="token operator">=</span> tranches <span class="token punctuation">;</span> merkleRoots <span class="token punctuation">[</span> trancheId <span class="token punctuation">]</span> <span class="token operator">=</span> _merkleRoot <span class="token punctuation">;</span> tranches <span class="token operator">=</span> tranches <span class="token punctuation">.</span> <span class="token function">add</span> <span class="token punctuation">(</span> <span class="token number">1</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> emit <span class="token function">TrancheAdded</span> <span class="token punctuation">(</span> trancheId <span class="token punctuation">,</span> _merkleRoot <span class="token punctuation">,</span> _totalAllocation <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">expireTranche</span> <span class="token punctuation">(</span> <span class="token parameter">uint256 _trancheId</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> onlyOwner <span class="token punctuation">{</span> merkleRoots <span class="token punctuation">[</span> _trancheId <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">bytes32</span> <span class="token punctuation">(</span> <span class="token number">0</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> emit <span class="token function">TrancheExpired</span> <span class="token punctuation">(</span> _trancheId <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">claimWeek</span> <span class="token punctuation">(</span> <span class="token parameter">address _liquidityProvider <span class="token punctuation">,</span> uint256 _tranche <span class="token punctuation">,</span> uint256 _balance <span class="token punctuation">,</span> bytes32 <span class="token punctuation">[</span> <span class="token punctuation">]</span> memory _merkleProof</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token punctuation">{</span> <span class="token function">_claimWeek</span> <span class="token punctuation">(</span> _liquidityProvider <span class="token punctuation">,</span> _tranche <span class="token punctuation">,</span> _balance <span class="token punctuation">,</span> _merkleProof <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">_disburse</span> <span class="token punctuation">(</span> _liquidityProvider <span class="token punctuation">,</span> _balance <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">claimWeeks</span> <span class="token punctuation">(</span> <span class="token parameter">address _liquidityProvider <span class="token punctuation">,</span> uint256 <span class="token punctuation">[</span> <span class="token punctuation">]</span> memory _tranches <span class="token punctuation">,</span> uint256 <span class="token punctuation">[</span> <span class="token punctuation">]</span> memory _balances <span class="token punctuation">,</span> bytes32 <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> memory _merkleProofs</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token punctuation">{</span> uint256 len <span class="token operator">=</span> _tranches <span class="token punctuation">.</span> length <span class="token punctuation">;</span> <span class="token function">require</span> <span class="token punctuation">(</span> len <span class="token operator">==</span> _balances <span class="token punctuation">.</span> length <span class="token operator">&&</span> len <span class="token operator">==</span> _merkleProofs <span class="token punctuation">.</span> length <span class="token punctuation">,</span> <span class="token string">"Mismatching inputs"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> uint256 totalBalance <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span> uint256 i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator"><</span> len <span class="token punctuation">;</span> i <span class="token operator">++</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">_claimWeek</span> <span class="token punctuation">(</span> _liquidityProvider <span class="token punctuation">,</span> _tranches <span class="token punctuation">[</span> i <span class="token punctuation">]</span> <span class="token punctuation">,</span> _balances <span class="token punctuation">[</span> i <span class="token punctuation">]</span> <span class="token punctuation">,</span> _merkleProofs <span class="token punctuation">[</span> i <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> totalBalance <span class="token operator">=</span> totalBalance <span class="token punctuation">.</span> <span class="token function">add</span> <span class="token punctuation">(</span> _balances <span class="token punctuation">[</span> i <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">_disburse</span> <span class="token punctuation">(</span> _liquidityProvider <span class="token punctuation">,</span> totalBalance <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">verifyClaim</span> <span class="token punctuation">(</span> <span class="token parameter">address _liquidityProvider <span class="token punctuation">,</span> uint256 _tranche <span class="token punctuation">,</span> uint256 _balance <span class="token punctuation">,</span> bytes32 <span class="token punctuation">[</span> <span class="token punctuation">]</span> memory _merkleProof</span> <span class="token punctuation">)</span> <span class="token keyword">public</span> view <span class="token function">returns</span> <span class="token punctuation">(</span> <span class="token parameter">bool valid</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">_verifyClaim</span> <span class="token punctuation">(</span> _liquidityProvider <span class="token punctuation">,</span> _tranche <span class="token punctuation">,</span> _balance <span class="token punctuation">,</span> _merkleProof <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">_claimWeek</span> <span class="token punctuation">(</span> <span class="token parameter">address _liquidityProvider <span class="token punctuation">,</span> uint256 _tranche <span class="token punctuation">,</span> uint256 _balance <span class="token punctuation">,</span> bytes32 <span class="token punctuation">[</span> <span class="token punctuation">]</span> memory _merkleProof</span> <span class="token punctuation">)</span> <span class="token keyword">private</span> <span class="token punctuation">{</span> <span class="token function">require</span> <span class="token punctuation">(</span> _tranche <span class="token operator"><</span> tranches <span class="token punctuation">,</span> <span class="token string">"Week cannot be in the future"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token operator">!</span> claimed <span class="token punctuation">[</span> _tranche <span class="token punctuation">]</span> <span class="token punctuation">[</span> _liquidityProvider <span class="token punctuation">]</span> <span class="token punctuation">,</span> <span class="token string">"LP has already claimed"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token function">_verifyClaim</span> <span class="token punctuation">(</span> _liquidityProvider <span class="token punctuation">,</span> _tranche <span class="token punctuation">,</span> _balance <span class="token punctuation">,</span> _merkleProof <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token string">"Incorrect merkle proof"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> claimed <span class="token punctuation">[</span> _tranche <span class="token punctuation">]</span> <span class="token punctuation">[</span> _liquidityProvider <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token punctuation">;</span> emit <span class="token function">Claimed</span> <span class="token punctuation">(</span> _liquidityProvider <span class="token punctuation">,</span> _tranche <span class="token punctuation">,</span> _balance <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">_verifyClaim</span> <span class="token punctuation">(</span> <span class="token parameter">address _liquidityProvider <span class="token punctuation">,</span> uint256 _tranche <span class="token punctuation">,</span> uint256 _balance <span class="token punctuation">,</span> bytes32 <span class="token punctuation">[</span> <span class="token punctuation">]</span> memory _merkleProof</span> <span class="token punctuation">)</span> <span class="token keyword">private</span> view <span class="token function">returns</span> <span class="token punctuation">(</span> <span class="token parameter">bool valid</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> bytes32 leaf <span class="token operator">=</span> <span class="token function">keccak256</span> <span class="token punctuation">(</span> abi <span class="token punctuation">.</span> <span class="token function">encodePacked</span> <span class="token punctuation">(</span> _liquidityProvider <span class="token punctuation">,</span> _balance <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> MerkleProof <span class="token punctuation">.</span> <span class="token function">verify</span> <span class="token punctuation">(</span> _merkleProof <span class="token punctuation">,</span> merkleRoots <span class="token punctuation">[</span> _tranche <span class="token punctuation">]</span> <span class="token punctuation">,</span> leaf <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">_disburse</span> <span class="token punctuation">(</span> <span class="token parameter">address _liquidityProvider <span class="token punctuation">,</span> uint256 _balance</span> <span class="token punctuation">)</span> <span class="token keyword">private</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> _balance <span class="token operator">></span> <span class="token number">0</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> token <span class="token punctuation">.</span> <span class="token function">safeTransfer</span> <span class="token punctuation">(</span> _liquidityProvider <span class="token punctuation">,</span> _balance <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token function">revert</span> <span class="token punctuation">(</span> <span class="token string">"No balance would be transferred - not going to waste your gas"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Let’s take a look at the logic of the Merkle Airdrop contract
- The variable
tranches
stores the id of the Airdrop (we can open many different airdrops) - Mapping
merkleRoots
saves the Merkle Root value of the respective Airdrop. - Mapping
claimed
used to check for specific airdrop during the address that claim or not? - The
seedNewAllocations
function is the init function of the Airdrop, after the airdrop registration is finished, the owner of the contract will call this function to transfer the token to the contract as well as store the Merkle Root value. - The private
_claimWeek
function will check the conditions to see if the address of the user has claimed or not?tranches
id valid or not? - The
_verifyClaim
function will rely on the Merkle Proof that the user submits to calculate if the address is correct registered for the airdrop or not? - Finally, the
_disburse
function is the function that will send tokens from the contract to the user when all conditions have been met.