This guide explains how to query the current merkle root from the API to update your smart contract.
The investor wave system maintains a merkle tree of whitelisted investors. When a new investor CSV is deployed, a new merkle root is generated. You need to update your contract with this new root to enable the new whitelist.
Returns the current merkle root for the raise contract.
Request:
GET https://api.flyingtulip.com/whitelist/merkle-root
chainId is optional (defaults to Ethereum/1):
GET https://api.flyingtulip.com/whitelist/merkle-root?chainId=1
Response:
{
"merkleRoot": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"chainId": 1,
"waveNumber": 3,
"entryCount": 150,
"hasWave": true
}Fields:
merkleRoot: The bytes32 root to set in your contractwaveNumber: Which version of the whitelist this isentryCount: Number of investors in this wavehasWave: Whether a wave exists for this chain
When a new wave is deployed, update your contract's merkle root:
// Your contract should have a function like:
function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner {
merkleRoot = _merkleRoot;
emit MerkleRootUpdated(_merkleRoot);
}# Query the merkle root
MERKLE_ROOT=$(curl -s https://api.flyingtulip.com/whitelist/merkle-root | jq -r '.merkleRoot')
# Update contract
cast send $CONTRACT_ADDRESS "setMerkleRoot(bytes32)" $MERKLE_ROOT --private-key $PRIVATE_KEYconst response = await fetch('https://api.flyingtulip.com/whitelist/merkle-root');
const { merkleRoot, waveNumber } = await response.json();
console.log(`Updating to wave ${waveNumber} with root: ${merkleRoot}`);
const tx = await contract.setMerkleRoot(merkleRoot);
await tx.wait();- New investor CSV is committed to
assets/whitelist/investors.csv - PR merged → deploy runs → import_wave creates new wave with new merkle root
- Query
/whitelist/merkle-rootto get the new root - Call
setMerkleRoot()on your contract with the new value - Users can now call
invest()with proofs from/whitelist/invest-proof
Your contract should verify proofs like this:
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
function invest(
address token,
uint256 amount,
uint256 proofAmount,
bytes32[] calldata proof
) external {
// Compute leaf: keccak256(abi.encodePacked(msg.sender, token, proofAmount))
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, token, proofAmount));
// Verify proof
require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof");
// User is whitelisted for up to proofAmount
require(amount <= proofAmount, "Amount exceeds allocation");
// ... rest of invest logic
}- Merkle root is a
bytes32value (66 chars with 0x prefix) - The API defaults to Ethereum (chainId=1) - no need to specify for mainnet raise
- Wave number increments each time a new CSV is deployed
- Old waves are archived but proofs only work with the current merkle root