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/132fc6094f29d9f6da2ad2922896ac51 to your computer and use it in GitHub Desktop.

Select an option

Save patcito/132fc6094f29d9f6da2ad2922896ac51 to your computer and use it in GitHub Desktop.
Frontend Dev Guide: Querying Invest Proofs for Users

Investor Wave - Frontend Developer Guide

This guide explains how to query merkle proofs from the API for the invest flow.

Overview

The investor wave system generates merkle proofs for whitelisted investors. Frontend needs to:

  1. Check if a user is whitelisted
  2. Get their proof and allocation amount
  3. Pass these to the contract's invest() function

API Endpoints

GET /whitelist/invest-proof

Returns the merkle proof for a specific wallet/token combination.

Request:

GET https://api.flyingtulip.com/whitelist/invest-proof?wallet=0x...&token=0x...

Parameters:

  • wallet (required): User's wallet address
  • token (required): Token address they want to invest with
  • chainId (optional): Defaults to 1 (Ethereum)

Response (user is whitelisted):

{
  "proof": [
    "0x1234567890abcdef...",
    "0xabcdef1234567890...",
    "0x..."
  ],
  "proofAmount": "1000000000000000000000",
  "merkleRoot": "0x...",
  "found": true
}

Response (user NOT whitelisted):

{
  "proof": [],
  "proofAmount": "0",
  "merkleRoot": "0x...",
  "found": false
}

Fields:

  • proof: Array of bytes32 hashes for merkle proof
  • proofAmount: Max amount user can invest (in wei, as string)
  • merkleRoot: Current merkle root (for verification)
  • found: Whether user is in the whitelist

GET /whitelist/merkle-root

Returns the current merkle root (useful for display/verification).

GET https://api.flyingtulip.com/whitelist/merkle-root

TypeScript Example

interface InvestProofResponse {
  proof: string[];
  proofAmount: string;
  merkleRoot: string;
  found: boolean;
}

async function getInvestProof(
  wallet: string, 
  token: string
): Promise<InvestProofResponse> {
  const params = new URLSearchParams({ wallet, token });
  const response = await fetch(
    `https://api.flyingtulip.com/whitelist/invest-proof?${params}`
  );
  return response.json();
}

// Usage
const { proof, proofAmount, found } = await getInvestProof(
  userAddress,
  usdcAddress
);

if (!found) {
  // Show "not whitelisted" message
  return;
}

// User is whitelisted for proofAmount
console.log(`User can invest up to ${ethers.formatUnits(proofAmount, 6)} USDC`);

React Hook Example

import { useQuery } from '@tanstack/react-query';
import { useAccount } from 'wagmi';

const INVEST_TOKEN = '0x...'; // USDC or other token

export function useInvestProof() {
  const { address } = useAccount();
  
  return useQuery({
    queryKey: ['investProof', address],
    queryFn: async () => {
      if (!address) return null;
      
      const params = new URLSearchParams({
        wallet: address,
        token: INVEST_TOKEN,
      });
      
      const res = await fetch(
        `https://api.flyingtulip.com/whitelist/invest-proof?${params}`
      );
      return res.json();
    },
    enabled: !!address,
  });
}

// In component
function InvestButton() {
  const { data: proofData, isLoading } = useInvestProof();
  
  if (isLoading) return <Spinner />;
  
  if (!proofData?.found) {
    return <div>You are not whitelisted for this raise</div>;
  }
  
  return (
    <div>
      <p>Your allocation: {formatAmount(proofData.proofAmount)}</p>
      <button onClick={() => invest(proofData)}>Invest</button>
    </div>
  );
}

Calling the Contract

import { parseUnits } from 'viem';

async function invest(
  proofData: InvestProofResponse, 
  investAmount: string
) {
  const { proof, proofAmount } = proofData;
  
  // investAmount is what user wants to invest
  // proofAmount is their max allocation from whitelist
  const amountWei = parseUnits(investAmount, 6); // 6 decimals for USDC
  
  // Call contract
  await writeContract({
    address: RAISE_CONTRACT,
    abi: raiseAbi,
    functionName: 'invest',
    args: [
      TOKEN_ADDRESS,           // token
      amountWei,               // amount to invest
      BigInt(proofAmount),     // proofAmount from API
      proof,                   // merkle proof array
    ],
  });
}

Error Handling

async function getProofSafe(wallet: string, token: string) {
  try {
    const data = await getInvestProof(wallet, token);
    
    if (!data.found) {
      return { 
        isWhitelisted: false, 
        error: 'Address not in whitelist' 
      };
    }
    
    return {
      isWhitelisted: true,
      proof: data.proof,
      maxAmount: data.proofAmount,
    };
  } catch (error) {
    return { 
      isWhitelisted: false, 
      error: 'Failed to fetch whitelist status' 
    };
  }
}

Notes

  • proofAmount is returned as a string to handle large numbers (wei)
  • Proof array can be empty if user is not whitelisted
  • The API defaults to Ethereum - no need to pass chainId for mainnet
  • Cache proof data - it only changes when a new wave is deployed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment