Skip to content

Instantly share code, notes, and snippets.

@mediavrog
Created February 17, 2023 15:41
Show Gist options
  • Save mediavrog/75bc67ca7564d2d2fb790fe087a8c9ba to your computer and use it in GitHub Desktop.
Save mediavrog/75bc67ca7564d2d2fb790fe087a8c9ba to your computer and use it in GitHub Desktop.
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