I'm using a copy of Invisible Friends NFT contract (https://etherscan.io/address/0x59468516a8259058bad1ca5f8f4bff190d30e066#code) and I'm currently in a process of creating a minting page. Everything is going well except validating merkle proofs for whitelist minting.
First, I created an array of 2 ETH addresses, put together merkle tree, calculate root hash and update it on the contract:
let leafNodes;
let merkleTree;
let merkleProof;
let merkleRoot;
const whitelist = ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"];
leafNodes = whitelist.map(addr => keccak256(addr.concat("5")));
merkleTree = new MerkleTree(leafNodes, keccak256, { sortPairs: true });
merkleRoot = merkleTree.getRoot();
await contract.setMerkleProof(merkleRoot);
Now, when I create merkle proof and try to verify it on my front-end, it works:
merkleProof = merkleTree.getHexProof(keccak256(owner.address.concat("5")));
console.log(merkleTree.verify(merkleProof, keccak256(owner.address.concat("5")), merkleRoot)) => returns "true"
However, when I try to mint using the generated proof, it fails:
await contract.mintListed(1, merkleProof, 5, { value: ethers.utils.parseEther("0.25") });
=> "Error: VM Exception while processing transaction: reverted with reason string 'Invalid proof'"
Contract side which is responsible for merkle proof verification:
function _verify(
bytes32[] calldata merkleProof,
address sender,
uint256 maxAmount
) private view returns (bool) {
bytes32 leaf = keccak256(
abi.encodePacked(sender, maxAmount.toString())
);
return MerkleProof.verify(merkleProof, merkleRoot, leaf);
}
This returns false, therefore failing the mint. I can't figure out why it's doing this, since I'm doing the same thing on my front-end and it's working well.
And mintListed function itself:
function mintListed(
uint256 amount,
bytes32[] calldata merkleProof,
uint256 maxAmount
) public payable nonReentrant {
address sender = _msgSender();
require(isActive, "Sale is closed");
require(
amount <= maxAmount - _alreadyMinted[sender],
"Insufficient mints left"
);
require(_verify(merkleProof, sender, maxAmount), "Invalid proof");
require(msg.value == price * amount, "Incorrect payable amount");
_alreadyMinted[sender] += amount;
_internalMint(sender, amount);
}
When I remove the "maxAmount" from merkle tree and using only keccak256 hashed addresses, it works, but I do need it.
I tried different ways to concatenate strings on JS side, but nothing works.
I managed to fix the issue.
So instead of
keccak256(addr.concat("5")));
I used
keccak256(ethers.utils.solidityPack(["address", "string"], [addr, "5"]))
Now it works fine!