Last active
September 23, 2023 07:29
-
-
Save BlockmanCodes/447a4609fe67ce718afc827c646b759b to your computer and use it in GitHub Desktop.
Airdrop app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
] | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require("@nomiclabs/hardhat-waffle"); | |
/** @type import('hardhat/config').HardhatUserConfig */ | |
module.exports = { | |
solidity: { | |
version: "0.8.9" | |
}, | |
paths: { | |
artifacts: "./client/src/artifacts" | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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