Skip to content

Instantly share code, notes, and snippets.

@patcito
Last active January 17, 2026 12:53
Show Gist options
  • Select an option

  • Save patcito/1a09ab9fb40d93d7b960a0c7b573a2a0 to your computer and use it in GitHub Desktop.

Select an option

Save patcito/1a09ab9fb40d93d7b960a0c7b573a2a0 to your computer and use it in GitHub Desktop.
Solidity Dev Guide: Querying Merkle Root for Contract Updates

Investor Wave - Solidity Developer Guide

This guide explains how to query the current merkle root from the API to update your smart contract.

Overview

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.

API Endpoint

GET /whitelist/merkle-root

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 contract
  • waveNumber: Which version of the whitelist this is
  • entryCount: Number of investors in this wave
  • hasWave: Whether a wave exists for this chain

Updating Your Contract

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);
}

Using cast (Foundry)

# 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_KEY

Using ethers.js

const 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();

Workflow

  1. New investor CSV is committed to assets/whitelist/investors.csv
  2. PR merged → deploy runs → import_wave creates new wave with new merkle root
  3. Query /whitelist/merkle-root to get the new root
  4. Call setMerkleRoot() on your contract with the new value
  5. Users can now call invest() with proofs from /whitelist/invest-proof

Verifying a Proof On-Chain

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
}

Notes

  • Merkle root is a bytes32 value (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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment