Skip to content

Instantly share code, notes, and snippets.

@BlockmanCodes
Last active September 23, 2023 07:29
Show Gist options
  • Save BlockmanCodes/447a4609fe67ce718afc827c646b759b to your computer and use it in GitHub Desktop.
Save BlockmanCodes/447a4609fe67ce718afc827c646b759b to your computer and use it in GitHub Desktop.
Airdrop app
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.2.1",
"buffer": "^6.0.3",
"ethers": "^5.7.1",
"keccak256": "^1.0.6",
"merkletreejs": "^0.2.32",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
import { useEffect, useState } from 'react';
import { ethers } from 'ethers';
import KECCAK256 from 'keccak256';
import MerkleTree from 'merkletreejs';
import { Buffer } from "buffer/";
import './App.css';
import artifact from './artifacts/contracts/MerkleDistributor.sol/MerkleDistributor.json'
const CONTRACT_ADDRESS = '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512'
window.Buffer = window.Buffer || Buffer;
function App() {
const [provider, setProvider] = useState(undefined);
const [signer, setSigner] = useState(undefined);
const [contract, setContract] = useState(undefined);
const [signerAddress, setSignerAddress] = useState(undefined);
const [tree, setTree] = useState(undefined)
const [proof, setProof] = useState([])
useEffect(() => {
const onLoad = async () => {
const provider = await new ethers.providers.Web3Provider(window.ethereum)
setProvider(provider)
const contract = await new ethers.Contract(
CONTRACT_ADDRESS,
artifact.abi,
provider
)
setContract(contract)
const tree = await getTree()
setTree(tree)
}
onLoad()
}, [])
const isConnected = () => (signer !== undefined)
const connect = () => {
getSigner(provider)
.then(signer => {
setSigner(signer)
})
}
const getTree = async () => {
const indexedAddresses = require('./walletAddresses.json');
const addresses = []
Object.keys(indexedAddresses).forEach(function(idx) {
addresses.push(indexedAddresses[idx])
})
const leaves = addresses.map(x => KECCAK256(x))
const tree = new MerkleTree(leaves, KECCAK256, { sortPairs: true })
return tree
}
const getSigner = async provider => {
const signer = provider.getSigner();
await signer.getAddress()
.then((address) => {
setSignerAddress(address)
const proof = tree.getHexProof(KECCAK256(address))
setProof(proof)
})
return signer;
}
const claimAirdrop = async () => {
await contract.connect(signer).claim(proof)
}
return (
<div className="App">
<header className="App-header">
{isConnected() ? (
<div>
<p>
Welcome {signerAddress?.substring(0,10)}...
</p>
<div className="list-group">
<div className="list-group-item">
<button
className="btn btn-success"
onClick={() => claimAirdrop()}>
Claim
</button>
</div>
</div>
</div>
) : (
<div>
<p>
You are not connected
</p>
<button onClick={connect} className="btn btn-primary">Connect Metamask</button>
</div>
)}
</header>
</div>
);
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import 'bootstrap/dist/css/bootstrap.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
require("@nomiclabs/hardhat-waffle");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
version: "0.8.9"
},
paths: {
artifacts: "./client/src/artifacts"
}
};
{
"name": "merkle-distributor",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@nomiclabs/hardhat-ethers": "^2.1.1",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@openzeppelin/contracts": "^4.8.0-rc.1",
"chai": "^4.3.6",
"ethereum-waffle": "^3.4.4",
"ethers": "^5.7.1",
"hardhat": "^2.11.2",
"keccak256": "^1.0.6",
"merkletreejs": "^0.2.32"
}
}
const { MerkleTree } = require('merkletreejs')
const KECCAK256 = require('keccak256');
const { BigNumber } = require('ethers');
const fs = require('fs').promises;
async function main() {
[signer1, signer2, signer3, signer4, signer5, signer6, signer7, signer8] = await ethers.getSigners();
walletAddresses = [signer1, signer2, signer3, signer4, signer5, signer6, signer7, signer8].map((s) => s.address)
leaves = walletAddresses.map(x => KECCAK256(x))
tree = new MerkleTree(leaves, KECCAK256, { sortPairs: true })
TofuCoin = await ethers.getContractFactory('TofuCoin', signer1);
token = await TofuCoin.deploy();
MerkleDistributor = await ethers.getContractFactory('MerkleDistributor', signer1);
distributor = await MerkleDistributor.deploy(
token.address,
tree.getHexRoot(),
BigNumber.from('1000000000000000000')
);
await token.connect(signer1).mint(
distributor.address,
BigNumber.from('9000000000000000000')
)
console.log("TofuCoin:", token.address);
console.log("MerkleDistributor:", distributor.address);
console.log("signer1:", signer1.address);
const indexedAddresses = {}
walletAddresses.map((x, idx) => indexedAddresses[idx] = x)
const serializedAddresses = JSON.stringify(indexedAddresses);
await fs.writeFile("client/src/walletAddresses.json", serializedAddresses);
}
// npx hardhat run --network localhost scripts/deploy.js
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
const { MerkleTree } = require('merkletreejs')
const KECCAK256 = require('keccak256');
const { expect } = require("chai");
describe('MerkleDistributor', () => {
beforeEach(async () => {
[signer1, signer2, signer3, signer4, signer5, signer6, signer7, signer8] = await ethers.getSigners();
walletAddresses = [signer1, signer2, signer3, signer4, signer5, signer6, signer7, signer8].map((s) => s.address)
leaves = walletAddresses.map(x => KECCAK256(x))
tree = new MerkleTree(leaves, KECCAK256, { sortPairs: true })
TofuCoin = await ethers.getContractFactory('TofuCoin', signer1);
token = await TofuCoin.deploy();
MerkleDistributor = await ethers.getContractFactory('MerkleDistributor', signer1);
distributor = await MerkleDistributor.deploy(token.address, tree.getHexRoot(), 500);
await token.connect(signer1).mint(
distributor.address,
'4000'
)
});
describe('8 account tree', () => {
it('successful and unsuccessful claim', async () => {
expect(await token.balanceOf(signer1.address)).to.be.equal(0)
const proof = tree.getHexProof(KECCAK256(signer1.address))
await distributor.connect(signer1).claim(proof)
expect(await token.balanceOf(signer1.address)).to.be.equal(500)
expect(
distributor.connect(signer1).claim(proof)
).to.be.revertedWith(
'MerkleDistributor: Drop already claimed.'
)
expect(await token.balanceOf(signer1.address)).to.be.equal(500)
})
it('unsuccessful claim', async () => {
const generatedAddress = '0x4dE8dabfdc4D5A508F6FeA28C6f1B288bbdDc26e'
const proof2 = tree.getHexProof(KECCAK256(generatedAddress))
expect(
distributor.connect(signer1).claim(proof2)
).to.be.revertedWith(
'MerkleDistributor: Invalid proof.'
)
})
it('emits a successful event', async () => {
const proof = tree.getHexProof(KECCAK256(signer1.address))
await expect(distributor.connect(signer1).claim(proof))
.to.emit(distributor, 'Claimed')
.withArgs(signer1.address, 500)
})
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment