Three Bugs That Nearly Killed Our Web3 Hackathon Project

CORS, commit-reveal race conditions, and Map size exceeded — surviving a 10-day hackathon on X Layer with Uniswap V4 hooks.

· DEV

We built Fanovo — a Uniswap V4 Hook protocol for the World Cup. Two hackathons at once, X Layer, 48 country tokens, 144 player tokens, bonding curves, commit-reveal packs. 10 days total. Sounded like an adventure. Spoiler: it was.

29 OpenCode sessions later, about half were about bugs. These three hurt the most.


Bug #1: Why Was the RPC Cursed?

Everything worked in MetaMask. Contracts deployed. Transactions went through. But the frontend loaded zero data. Wagmi just stayed silent. No errors. No logs.

Turns out X Layer RPC did not send CORS headers for browser requests. MetaMask, being a browser extension, does not care about CORS. But wagmi uses fetch, and the browser quietly blocked every response.

First attempt: Next.js rewrites pointing to the same RPC. Did not work. Rewrites did not forward POST bodies correctly, and the RPC returned 400.

// Before: Next.js rewrites
const nextConfig = {
  async rewrites() {
    return [{
      source: "/api/rpc",
      destination: "https://rpc.xlayer.tech/",
    }];
  },
};

We moved to a dedicated API route. Added User-Agent, CORS headers, and retry logic for 429 rate limits. After that, everything flew.

// After: dedicated API route
export async function POST(request: NextRequest) {
  const body = await request.text();
  const res = await fetch(RPC_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "User-Agent": "fanovo-frontend/1.0",
    },
    body,
  });

  const data = await res.text();
  return new NextResponse(data, {
    status: res.status,
    headers: {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": "*",
    },
  });
}
Pitfall: If your Web3 frontend works in MetaMask but loads no RPC data — check CORS. Not a contract bug, not a code bug. A dumb HTTP header.

Bug #2: Why Would the Commit-Reveal Not Open a Second Pack?

Pack opening used commit-reveal: user sends a hash, waits for confirmation, sends the original value. A fair scheme against frontrunning.

First pack opens fine. Second one reverts. The contract still remembered the previous commit — it thought the user was already opening a pack. The slot never got cleared after reveal.

// Problem: commit slot not cleared after reveal
function reveal(commitHash, value) {
  require(commits[msg.sender] == commitHash);
  // delete commits[msg.sender]; // this line was missing
  _openPack(msg.sender, value);
}

Fix: one line — delete commits[msg.sender] after successful reveal. Plus emit a PackOpened event with the user address so the frontend can listen without reloading.

Bug #3: Why Did Map Maximum Size Get Exceeded?

The site worked. For about 10 minutes. Then Next.js crashed with "RangeError: Map maximum size exceeded". Not the browser — the server-side render just died.

useBlockNumber from wagmi watches every block by default. On X Layer a block comes every 2 seconds. In 10 minutes the V8 Map had 300+ entries and hit the hard limit.

// Before: watches every block
const { data: blockNumber } = useBlockNumber({ watch: true });

// After: polls every 15 seconds
const { data: blockNumber } = useBlockNumber({
  watch: true,
  query: { refetchInterval: 15_000 },
});

We also built a server-side indexer for burnt stats — the client could not aggregate thousands of events. Moved computation to the server, served pre-calculated numbers. BURNT LAST 24H stopped being forever zero.

Pro Tip: useBlockNumber({ watch: true }) without refetchInterval is a guaranteed memory leak. On fast chains like X Layer (2s blocks) it kills Next.js in 10 minutes.

What Was the Takeaway?

We shipped the project. Two hackathons, two contracts, Next.js frontend, all running on X Layer mainnet. The funny thing — none of these bugs were in the contract logic. CORS, a memory leak, a missing delete. This is what actually eats your time.

Web3 development is 20% smart contracts and 80% fighting the tools around them.