The merkle root and proof is only necessary in traid-based order. For other order type, just leave out this.
Merkle Root Calculation Algorithm
Suppose a user filters out a set of tokenIds according to its traits, and the token id array is [12,10,7,8,9,11,13]. Then it needs to sort the token id array and build a binary tree:
The merkleRoot is hash1234(hash12,hash34) and here is value its 0x72da2598329de27c7d20ea24372ca2a4732aaac2d9386467373baa7874b870f6.
MerkleProof
Suppose the target leaf is 7, then the merkle proof is [8, hash2(9,10), hash34(hash3,hash4)]. Suppose the target leaf is 10, then the merkle proof is [9, hash1(7,8), hash34(hash3,hash4)].
Generate Merkle Proof And Root
This is the example JS code to calculate merkle root and merkle proof.
const keccak256 = require('keccak256');
function stringToBytes32(symbol) {
let result = symbol;
for (let i = 0; i < 64 - symbol.length; i++) {
result = "0" + result;
}
return '0x'+result;
}
function calculateProofLength(totalLeaf) {
let merkleProofLength = 0;
let divResult = totalLeaf;
let hasMod = false;
for(;divResult!==0;) {
let tempDivResult = Math.floor(divResult/2);
if (tempDivResult*2<divResult) {
hasMod = true;
}
divResult=tempDivResult;
merkleProofLength++;
}
if (!hasMod) {
merkleProofLength--;
}
return merkleProofLength;
}
function calculateProof(leafA, leafB) {
if ( typeof leafB === 'undefined' ) {
leafB = leafA
}
const leafABuffer = Buffer.from(web3.utils.hexToBytes(leafA));
const leafBBuffer = Buffer.from(web3.utils.hexToBytes(leafB));
let hash;
if (leafA>leafB) {
hash = "0x"+keccak256(Buffer.concat([leafBBuffer, leafABuffer])).toString('hex');
} else {
hash = "0x"+keccak256(Buffer.concat([leafABuffer, leafBBuffer])).toString('hex');
}
return hash;
}
function generateMerkleProofAndRoot(targetTokenId, tokenIds) {
assert.isTrue(Array.isArray(tokenIds), "expect array");
tokenIds.sort((a, b) => {
return a - b
});
const merkleProofLength = calculateProofLength(tokenIds.length);
let merkleProof = new Array(merkleProofLength);
for(let idx=0; idx<merkleProofLength;idx++) {
merkleProof[idx] = "0x0000000000000000000000000000000000000000000000000000000000000000"
}
let merkleProofInputs = new Array(tokenIds.length);
for(let idx=0; idx<tokenIds.length;idx++) {
merkleProofInputs[idx] = stringToBytes32(tokenIds[idx].toString(16))
}
let tempProofLength = Math.floor((tokenIds.length+1)/2)
let tempProof = new Array(tempProofLength);
let merkleRoot;
let leaf = stringToBytes32(targetTokenId.toString(16));
let proofIdx = 0;
for(;tempProof.length>=1;) {
for(let idx=0;idx<tempProof.length;idx++){
let matched = false;
if (leaf === merkleProofInputs[2*idx]) {
merkleProof[proofIdx] = merkleProofInputs[2*idx+1];
proofIdx++;
matched = true;
} else if (leaf === merkleProofInputs[2*idx+1]) {
merkleProof[proofIdx] = merkleProofInputs[2*idx];
proofIdx++;
matched = true;
}
tempProof[idx] = calculateProof(merkleProofInputs[2*idx], merkleProofInputs[2*idx+1]);
if (matched) {
leaf = tempProof[idx];
}
}
merkleProofInputs = tempProof;
if (tempProof.length===1) {
merkleRoot = tempProof[0];
break;
}
tempProofLength = Math.floor((tempProofLength+1)/2)
tempProof = new Array(tempProofLength);
}
return {merkleRoot, merkleProof};
}
Metadata to IPFS
Write tokenIds to a text file and upload it to IPFS.
Convert IPFS hash to bytes32
Fill merkleRoot to merkleData[0] and ipfsHash to merkleData[1]
This is the example JS code to convert the bese58 encoding ipfs hash to bytes32
import bs58 from 'bs58'
function convertIpfsHashToBytes32(ipfsHash) {
return "0x"+bs58.decode(ipfsHash).slice(2).toString('hex')
}
Suppose tokenIds is [7,8,9,10,11,12,13], then the original ipfs hash is QmZHbCohMvg1Pcf6rbh21DBhkCb7qCkwb37QK8xf6HQP39, the bytes32 ipfs hash is 0xa2a7d9b6454df5a7f4809215f12cc347e9662a4cdcd69d83e8a0f2cf65e1ce4c.