Created
February 17, 2023 15:41
-
-
Save mediavrog/75bc67ca7564d2d2fb790fe087a8c9ba to your computer and use it in GitHub Desktop.
This file contains 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 { task, types } from "hardhat/config"; | |
import fs from "fs"; | |
import { MerkleTree } from "merkletreejs"; | |
import keccak256 from "keccak256"; | |
// ethers library | |
import "@nomiclabs/hardhat-ethers"; | |
interface PresaleEntry { | |
address: string, | |
amount?: number | |
} | |
interface PresaleEntryWithProof extends PresaleEntry { | |
proof: string[] | |
} | |
interface MerkleEntries { | |
[key: string]: PresaleEntryWithProof | |
} | |
export default task("merkle", "Create the merkle root for a list of addresses") | |
.addParam("file", "The CSV file containing Ethereum addresses separated by a newline (\\n)") | |
.addOptionalParam("includeAmounts", "Whether to encode amounts in the merkle tree", true, types.boolean) | |
.addOptionalParam("safeEncode", "Whether to encode a `,` in between address and amount", false, types.boolean) | |
.addOptionalParam("title", "Title for this merkle configuration", "", types.string) | |
.setAction(async (taskArgs, { ethers }) => { | |
const includeAmounts = !!taskArgs.includeAmounts; | |
const safeEncode = !!taskArgs.safeEncode; | |
const title = taskArgs.title; | |
const data = fs.readFileSync(taskArgs.file, 'utf8'); | |
console.log(`\nContent of reserved list:\n`); | |
console.log(data); | |
const entries = data | |
.split("\n") | |
.filter((entry) => !!entry) | |
.map((entryLine) => entryLine.split(",")) | |
.map((entry) => ({ | |
address: ethers.utils.getAddress(entry[0]), // normalize to checksum address | |
...(includeAmounts && entry[1] ? { amount: parseInt(entry[1], 10) } : {}) | |
} as PresaleEntry)) | |
// create merkle root | |
const leaves = entries | |
.map((entry) => { | |
const types = entry.amount | |
? (safeEncode ? ['address', 'string', 'uint256'] : ['address', 'uint256']) | |
: ['address'] | |
const values = entry.amount | |
? (safeEncode ? [entry.address, ',', entry.amount] : [entry.address, entry.amount]) | |
: [entry.address] | |
return Buffer.from(ethers.utils.solidityKeccak256(types, values).slice(2), 'hex') | |
}) | |
// console.log(leaves) | |
const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true }); | |
// console.log(merkleTree) | |
console.log(`\n${leaves.length} addresses in reserved list. Merkle root is:`); | |
console.log(merkleTree.getHexRoot()); | |
const entriesWithProof = entries.reduce((agg, entry, index) => { | |
agg[entry.address] = { | |
...entry, | |
proof: merkleTree.getHexProof(leaves[index]), | |
} | |
return agg | |
}, {} as MerkleEntries) | |
// create file to use in frontend | |
const websiteListContent = `{ | |
"root": "${merkleTree.getHexRoot()}", | |
"lastUpdatedAt": ${new Date().getTime()}, | |
"title": "${title}", | |
"encodingSafe": ${safeEncode}, | |
"entries": ${JSON.stringify(entriesWithProof, null, 2)} | |
}`; | |
const newFile = taskArgs.file.substr(0, taskArgs.file.lastIndexOf(".")) + ".merkle.json"; | |
fs.writeFileSync(newFile, websiteListContent); | |
console.log(`Reserved list file for website saved to ${newFile}`); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment