DEV Community

Cover image for What the Flap Protocol Docs Don't Tell You: 5 Verified Findings From Building a Launchpad on BNB Chain
Adriel Oloko
Adriel Oloko

Posted on

What the Flap Protocol Docs Don't Tell You: 5 Verified Findings From Building a Launchpad on BNB Chain

What the Flap Protocol Docs Don't Tell You

I built a non-custodial token launch and trading console for the Flap protocol on BNB Chain. Launch tokens through Flap's Portal contract from your own UI: metadata upload to IPFS, CREATE2 vanity-salted deployment, bonding-curve quoting, and slippage-protected trades.

The interesting part was never the UI. It was everything the docs got wrong. This post is the field report: five findings verified against live BNB mainnet and testnet state, with the code that worked around them. The full source is public at github.com/adriel-oloko/flap-launchpad.

How I verify (no wallet required)

Before writing any UI, I built probe scripts that read live chain state with plain RPC calls, no wallet, no gas:

  • ABI probes against the Portal contract
  • eth_getLogs event scans for TokenCreated
  • Live quoteExactInput calls against the newest token
  • Transaction replays of recent launches
  • Gas measurement of real launch transactions

That discipline is why the findings below exist. Every claim in this post was reproduced on-chain, not taken from a README.

Finding 1: The docs version table is stale

Flap's developer docs list Portal versions v5.8.5 (mainnet) and v5.14.16 (testnet). The live portals report something else entirely via version():

Network Docs say Live Portal reports
BNB mainnet v5.8.5 v5.16.1
BNB testnet v5.14.16 v5.15.3

The version skew is not cosmetic. The documented read path, getTokenV8Safe, reverts on the testnet portal with selector 0xde6137d1. A brand new integrator following the docs would hit a hard revert on testnet and assume their setup was wrong.

The fix is a silent fallback: try V8, fall back to V6, and normalize both shapes into one internal model:

export async function getTokenInfo(client: PublicClient, token: Address): Promise<TokenInfo | null> {
  try {
    const state = await client.readContract({
      address: FLAP_CONFIG.portalAddress,
      abi: PORTAL_ABI,
      functionName: 'getTokenV8Safe',
      args: [token],
    });
    return normalizeV8Safe(state as unknown as TokenStateV8Safe);
  } catch {
    try {
      const state = await client.readContract({
        address: FLAP_CONFIG.portalAddress,
        abi: PORTAL_ABI,
        functionName: 'getTokenV6',
        args: [token],
      });
      return normalizeV6(state as unknown as TokenStateV6);
    } catch {
      return null;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

One shape difference to handle: V6 only exposes a single symmetric tax rate, V8 exposes separate buy and sell rates. The normalizer maps taxRate to both sides so downstream code never cares which version answered.

Finding 2: Standard launches are disabled on mainnet

The protocol's standard launch path (V2_PERMIT) reverts with FeatureDisabled (selector 0xac5f6092) on both live portals. newTokenV7 is also unusable, reverting with NewTokenV7RuleViolation.

Only the TAXED_V3 path works today. That is a big deal for a token launcher: every launch goes through the taxed implementation, so every token gets per-side tax, and your UI cannot pretend otherwise. My console builds newTokenV6 params exclusively for the taxed path and enforces the constraint before signing.

Finding 3: The dex threshold gate

Even within TAXED_V3, newTokenV6 only accepts DexThreshType.FOUR_FIFTHS (value 1). Any other threshold reverts InvalidDexThresholdType (selector 0x77146b42).

The lesson for integrators: enums with exact Solidity values are part of the ABI contract. I mirrored them in TypeScript with the raw values, so the UI cannot construct an invalid call:

export const DexThreshType = {
  ONE_HALF: 0,
  FOUR_FIFTHS: 1, // the only value the live portals accept
  ONE_AND_A_HALF: 2,
} as const;
Enter fullscreen mode Exit fullscreen mode

Finding 4: CREATE2 vanity salts

Token addresses are CREATE2-predictable via EIP-1167 minimal-proxy init code. Flap's docs show the standard 3d602d80600a3d3981f3... proxy prefix with the implementation address embedded, and the launcher searches locally for a salt whose predicted address ends in a vanity suffix (8888 standard, 7777 tax):

const EIP1167_PREFIX = '0x3d602d80600a3d3981f3363d3d373d3d3d363d73';
const EIP1167_SUFFIX = '5af43d82803e903d91602b57fd5bf3';

export function predictTokenAddress(salt: Hex, tokenImpl: Address, portal: Address): Address {
  const bytecode = `${EIP1167_PREFIX}${tokenImpl.slice(2).toLowerCase()}${EIP1167_SUFFIX}` as Hex;
  return getContractAddress({
    from: portal,
    salt: toBytes(salt),
    bytecode,
    opcode: 'CREATE2',
  });
}

export function findVanityTokenSalt(suffix: string, tokenImpl: Address, portal: Address): VanitySalt {
  const seed = generatePrivateKey();
  let salt = keccak256(toHex(seed));
  let iterations = 0;
  while (!predictTokenAddress(salt, tokenImpl, portal).endsWith(suffix)) {
    salt = keccak256(salt);
    iterations++;
  }
  return { salt, address: predictTokenAddress(salt, tokenImpl, portal), iterations };
}
Enter fullscreen mode Exit fullscreen mode

A 4-character hex suffix averages ~65k keccak iterations. That runs sub-second locally, which means the vanity search happens client-side before any transaction, no server needed.

Finding 5: RPC log limits and the gas clamp

Two operational traps that only show up under real load:

Log limits. The testnet is spammy, and wide eth_getLogs ranges exceed provider caps. Event scans run in 1000-block chunks over a 400k-block lookback, and the app pins RPCs that support log-heavy reads.

Gas clamp. Public BNB gateways reject raw transactions above 16,777,216 gas, and some return wildly inflated estimates: I observed ~34.8M for launches that really use ~2M. Every estimate is clamped to maxTxGas (15M), which keeps broadcasts inside what the gateways accept.

Bonus: gasless sells via permit

Sells on the bonding curve can skip the approve transaction entirely. swapExactInput accepts ERC-2612 permitData (an EIP-712 typed signature), so the sell is one transaction: sign the permit off-chain, include it in the swap call. Tokens without permit support fall back to the approve path. One less transaction, one less point of failure.

What I learned

  1. Docs drift on live protocols. Version tables, function availability, and enum ranges all change while documentation lags. Treat docs as a starting point and the chain as the source of truth.
  2. Revert selectors are a debugging gift. 0xde6137d1, 0xac5f6092, 0x77146b42 each point to a specific gate in the contract. 4byte.directory and the ABI itself decode them faster than reading docs.
  3. Build verification into the workflow. The no-wallet probe scripts (in scripts/ of the repo) made every finding reproducible in seconds. That is the difference between "I think this works" and "I verified this works."

The complete, reproducible tooling is in the repo: github.com/adriel-oloko/flap-launchpad, including verify-contracts.mjs, measure-gas.mjs, and the deeper protocol probes. If you are building on Flap, Uniswap v4 launchpads, or Robinhood Chain, I am open to contract work and full-time roles. DMs open.

Top comments (0)