262
loading...
This website collects cookies to deliver better user experience
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MerkleProof {
function verify(
bytes32 root,
bytes32 leaf,
bytes32[] memory proof,
uint256[] memory positions
)
public
pure
returns (bool)
{
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (positions[i] == 1) {
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
}
return computedHash == root;
}
}
contract Whitelist is MerkleProof {
bytes32 public immutable merkleRoot;
constructor (bytes32 _merkleRoot) {
merkleRoot = _merkleRoot;
}
function verifyWhitelist(
bytes32[] memory _proof,
uint256[] memory _positions
)
public
view
returns (bool)
{
bytes32 _leaf = keccak256(abi.encodePacked(msg.sender));
return MerkleProof.verify(merkleRoot, _leaf, _proof, _positions);
}
}
merkleRoot
to the constructor of the Whitelist contract when we deploy it. We will generate it using the merklejs
library in the next step.msg.sender
, is done inside the verifyWhitelist
function in the contract. _proof
array will be another hash of that specific leaf. This allows us to prove "Proof-of-inclusion" in our Merkle Tree without revealing or calculating all of the information in the tree._positions
array contains the positions of the corresponding proof (aka node) in the Merkle Tree, so that users can verify the consistency by computing the root value directly.App.js
) generates the Merkle Root. It requires that you have the packages merkletreejs
and keccack256
(hashing function also available in Solidity) installed.import whitelist from "./whitelist.js";
import { MerkleTree } from "merkletreejs";
import keccak256 from "keccak256";
const buf2hex = x => '0x' + x.toString('hex')
const leaves = whitelist.map(x => keccak256(x))
const tree = new MerkleTree(leaves, keccak256)
// This is what we will pass in to our contract when we deploy it
console.log(buf2hex(tree.getRoot()))
whitelist
is simply an array of Ethereum addresses imported from a file in the same directory. In production, you should consider using a json, or something a bit more secure/efficient. You can add your own Ethereum address or a test account address in the array so you can test the functionality when we are done.buf2hex
is a function that converts our buffered array to hexadecimal. keccak256
and pass them to the MerkleTree
constructor to generate the actual tree. tree.getRoot()
and convert it to hexadecimal, while logging the output to the console. (Save this somewhere safe for your deployment.)npx hardhat compile
and deploying to a test network (or localhost) we can now take a look at our actual dapp implementation. For simplicity, we are going to keep all of our Merkle logic in App.js
import logo from "./logo.png";
import "./App.css";
import React, { useContext, useState, useEffect } from "react";
import { Web3Context } from "./web3";
import contract from './artifacts/contracts/Merkle.sol/Whitelist.json'
import { ethers } from 'ethers'
import whitelist from './merkle/whitelist'
const { MerkleTree } = require("merkletreejs");
const keccak256 = require('keccak256')
function App() {
const { account, connectWeb3, logout, provider } = useContext(Web3Context)
const [approved, setApproved] = useState(false);
const whitelistContractAddress = "0x49F59D1b3035055a5DF5F4EbF876b33f204E5aB1" // Rinkeby
const merkle = async () => {
const whitelistContract = new ethers.Contract(whitelistContractAddress, contract.abi, provider.getSigner())
const buf2hex = x => '0x' + x.toString('hex')
const leaves = whitelist.map(x => keccak256(x))
const tree = new MerkleTree(leaves, keccak256);
const leaf = keccak256(account)
const hexProof = tree.getProof(leaf).map(x => buf2hex(x.data))
const positions = tree.getProof(leaf).map(x => x.position === 'right' ? 1 : 0)
let result = await whitelistContract.functions.verifyWhitelist(hexProof, positions);
setApproved(result[0]);
}
useEffect(() => {
if (account != null) {
merkle();
}
}, [account])
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="ethereum_logo" />
<div>
{account == null ? (
<button onClick={connectWeb3}>Connect to MetaMask</button>
) : (
<div>
<p>Account: {account}</p>
{approved ? <p>Congratulations, you are approved!</p> : <p>Sorry, you are not approved</p>}
<button onClick={logout}>Logout</button>
</div>
)}
</div>
<br />
</header>
</div>
);
}
export default App;
<div>
of the webpage, you will see a conditional rendering based on the account
being instantiated (see the web3 directory and Web3Context in the git repo for more details). Since the user hasn't connected their wallet to the website this will return a "Connect to MetaMask" button. Once you have connected to an Ethereum network (make sure you connect to the same network you deployed your contract to), React's useEffect()
function will be called since your account
is no longer null. In turn, we call the merkle()
function within the useEffect()
. generateMerkleTreeRoot.mjs
file. It would be possible to export/import the leaves
and tree
if we reworked the generateMerkleTreeRoot.mjs
, but for the sake of simplicity, we will keep the logic here and recompute these variables.hexProof
and positions
are generated, we pass them to our contract and await for our boolean response. Using React's useState()
, if the smart contract and Merkle Tree return true, we set approved
to true, which renders "Congratulations, you are approved!".require(verifyWhitelist(_proof, _positions))
to prevent people from just going around your website and minting on contract.create-react-app
that installs all the main tools needed to build a dapp without the extra bloat, check it out here. We became friends in DEV_DAO, which is a DAO dedicated to helping web3 developers connect and provide learning resources.