Skip to content

Instantly share code, notes, and snippets.

@KONFeature
Last active December 14, 2023 12:34
Show Gist options
  • Save KONFeature/410e039981497f3080644c3a7e455c36 to your computer and use it in GitHub Desktop.
Save KONFeature/410e039981497f3080644c3a7e455c36 to your computer and use it in GitHub Desktop.
Small gist used to reproduce voting MerkleTree generation & proof generation for a Jokerace contest. If you check out this gist, and find it useful, don't hesitate to vote for the Frak proposal aha
import {
encodePacked,
getAddress,
isAddress,
keccak256,
parseUnits,
} from "viem";
import { MerkleTree } from "merkletreejs";
import fs from "fs";
import csv from "csv-parser";
export interface Recipient {
address: string;
numVotes: string;
}
// Frak proposal on the `village buidl on polygon#1` of Jokerace:
// https://jokerace.xyz/contest/polygon/0x317Fc116E3c524316da9e783eB88099f0397ab2E/submission/76584427412783342536093397040981532505068033608056882071147444248795741602076
async function main() {
// Read the CSV and get all the address (first column) as address
const addresses: Set<string> = new Set<string>();
const votesData: Record<string, number> = {};
const rawRecipients: Recipient[] = [];
let duplicateCount: number = 0;
console.log("Reading CSV");
fs.createReadStream("voters.csv")
.pipe(csv())
.on("data", (data) => {
// Yeah strange encoding, but it works (I have update the header of the CSV to `addr`)
const address = data["addr"];
const numberOfVotes = parseFloat(data["numVotes"]);
if (!address && !isAddress(address)) throw new Error("Invalid address");
rawRecipients.push({
address: getAddress(address),
numVotes: parseUnits(numberOfVotes.toString(), 18).toString(),
});
if (addresses.has(address)) {
// Duplicate, exit
duplicateCount++;
console.log("Duplicate address", address);
return;
}
addresses.add(address);
if (numberOfVotes !== 0) {
votesData[address] = numberOfVotes;
}
})
.on("end", () => {
console.log("CSV file successfully processed");
console.log(addresses.size, "addresses");
console.log(duplicateCount, "duplicates");
console.log(Object.keys(votesData).length, "addressToNumVotes");
// Generate the recipients stuff
const recipients = setupVoteRecipients(18, votesData);
console.log(recipients.length, "recipients");
console.log(rawRecipients.length, "raw recipients");
const merkleTree = createMerkleTree(rawRecipients);
console.log("Merkle tree generated");
console.log("Root: ", merkleTree.getHexRoot());
// Submission merkle root
// 0xc1b8ebc70ba8f951acf475ab234abe60990b6389a75d67d033e78b069860a760
// Voting merkle root
// 0x36a5c5b58b73bf54ad852a1dc89839a942669cc8a14efd6dd62495392b325e1e
// Generated voting merkle root:
// 0x1f50a1b41f34951e354340178680616f615b268a734bcb9074edd0028bb672eb
// Proof
let proof = generateProof(
merkleTree,
"0xaE4e57b886541829BA70eFC84340653c41e2908C",
"1",
);
console.log("Proof: ", proof);
});
}
await main();
function generateLeaf(address: string, numVotes: string) {
// Exactly the same as etherjs but more performant
// It doesn't impact the merklee tree generation (same hash with both method)
const packed = encodePacked(
["address", "uint256"],
[getAddress(address), BigInt(numVotes)],
);
return keccak256(packed);
}
/**
* Generate Merkle Tree from recipients
* @param {Recipient[]} recipients array of recipients
* @returns {MerkleTree} merkle tree
*/
const createMerkleTree = (recipients: Recipient[]): MerkleTree => {
return new MerkleTree(
recipients.map(({ address, numVotes }) => generateLeaf(address, numVotes)),
keccak256,
{ sortPairs: true },
);
};
const setupVoteRecipients = (
decimals: number,
votesData: Record<string, number>,
): Recipient[] => {
const recipients: Recipient[] = [];
// For each entry
for (const [address, votes] of Object.entries(votesData)) {
recipients.push({
address: getAddress(address),
numVotes: parseUnits(votes.toString(), decimals).toString(),
});
}
return recipients;
};
export const generateProof = (
merkleTree: MerkleTree,
address: string,
numVotes: string,
): string[] => {
const leaf = generateLeaf(address, parseUnits(numVotes, 18).toString());
return merkleTree.getHexProof(leaf);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment