Build an Indexer

This page explains how to track every token launched through Pool Launch: which events to index, and which reads to call when you need state that events do not carry.

Pool Launch has one entry contract per chain, the Pool Launch Portal. Every Pool Launch event below is emitted by its proxy address (0x000042108465206185A077Edc3F3fd2b49060000 on BNB Chain and BNB Chain Testnet; see Deployed Contract Addresses β†’ Pool Launch protocol). It is a different contract from the bonding curve Portal: a Pool Launch token never appears in the bonding curve's events, and the other way round.

Unless noted, event fields are not indexed: decode them from the log data.

1. Index from events

Launcher

A launch built on a Launcher starts before the token exists: first the Launcher is created, then users fund it, then it launches the token.

EventRequiredDescription Β· default when missing
FlapNewLauncherCreated(address launcher, address launcherFactory, address creator, string description)Required if you list LaunchersA Launcher was created through an enabled factory. description is the Launcher's own display data as JSON (see Launcher description). Missing β†’ no Launcher (the token was launched directly). "" β†’ no description; you can call description() on the Launcher.
FlapLauncherFactoryMultiplierSet(address factory, uint16 multiplierBps, bool enabled)OptionalA Launcher factory was enabled, disabled or given a new floor multiplier. Missing β†’ the factory is not enabled ((false, 10000)).

Launcher description

This is the spec every Launcher's description() must follow. FlapNewLauncherCreated.description (and description() on the Launcher) is a JSON object, format version 1. v is the only number; every other value is a string. Amounts and ratios are 1e18 fixed-point decimal strings, addresses are lowercase 0x hex, and keys are camelCase. To build one, see Build on Pool Launch β†’ Describe the Launcher.

{"v":1,"name":"MyToken","symbol":"MTK",
 "quoteToken":"0x0000000000000000000000000000000000000000",
 "targetRaise":"1000000000000000000","poolTokenRatio":"500000000000000000",
 "titleZh":"ε‹Ÿθ΅„ε‘ε°„","titleEn":"Contribution round",
 "summaryZh":"…","summaryEn":"BNB is collected until the target is met. …"}
KeyMeaning
vFormat version, 1. Bumped only when a key is renamed, removed or redefined; new keys are added without bumping it.
name, symbolThe token the Launcher will launch.
quoteTokenThe quote token raised; the zero address means native BNB.
targetRaiseThe raise target (1e18 fixed point).
poolTokenRatioShare of the supply that goes into the pool (1e18 = 100%).
titleEn, titleZh, summaryEn, summaryZhOne-line title and a short description of the mechanism. Other languages add a capitalised suffix (titleJa); fall back to the …En value when a language is missing or empty.

When parsing:

  • "" means no description: the Launcher does not implement description(), it reverted, or it returned malformed or oversized data. Creating the Launcher never fails because of it.
  • A non-empty string that is not a JSON object: show it as plain text.
  • Ignore unknown keys, and hide keys whose value is "".
  • Use BigInt, not Number, for targetRaise.
  • It is display data only: never use it for gating, accounting or payments.

Token creation

EventRequiredDescription Β· default when missing
FlapDirectLaunched(address token, uint8 kind, address creator, bytes32 schemaId, bytes data)RequiredThe single launch record: one per Pool Launch token. kind is the launch shape (0 = DIRECT_V2_POOL_TAX, the only kind today). schemaId selects the layout of data: for keccak256("DirectLaunchV2(DirectLaunchV2Data)"), decode data as DirectLaunchV2Data (below). An unknown schemaId means a newer layout: skip it rather than mis-decode. creator is the caller, or the user a Launcher declared through the creator extension.
FlapNewVaultCreated(address token, address vault, address vaultFactory, address creator)Required if you show vaultsEmitted right after FlapDirectLaunched when the launch has a VAULT fee slot: which vault belongs to the token and which factory created it. Missing β†’ the token has no vault.
FlapDistributionExecuted(address token, uint8 target, address strategy, uint256 amount)OptionalThe dev-reserved remainder was handed to a strategy. target: 0 NONE, 1 EXPLICIT (for example a Launcher pulling it for contributors), 2 VAULT (the launch's vault took it). Missing β†’ no remainder was distributed at launch (it stays pending; see pendingRemainder below).
FlapSaltLocked(address locker, bytes32 salt, address tokenAddress, uint8 launchKind, uint256 ts)OptionalA launch salt (and so the future token address) was reserved. Useful to show a token before it launches. Missing β†’ the launch did not reserve its salt in advance.
FlapSaltUsed(address locker, bytes32 salt, address tokenAddress, uint8 launchKind, uint256 ts)OptionalA reserved salt was used by the launch. Missing β†’ the salt was not reserved, or is not used yet.

DirectLaunchV2Data, the data of FlapDirectLaunched:

struct DirectLaunchV2Data {
    uint256 nonce;           // global launch nonce (shared with bonding-curve launches)
    uint256 ts;              // launch block timestamp
    uint8 tokenVersion;      // TOKEN_DIRECT_TAXED (0)
    address quoteToken;      // quote side of the pool, address(0) = native
    uint8 migratorType;      // always V2_MIGRATOR
    uint256 initialPriceWad; // opening price, quote per token, 1e18 fixed point
    string name;
    string symbol;
    string meta;             // IPFS CID of the metadata JSON
    address pool;            // the V2 pool
    uint256 totalSupply;     // 18 decimals
    uint8 antiSniperMode;    // always 0 in this version
    uint16 maxBuyBps;        // always 0 on this path
}

Graduation

EventRequiredDescription Β· default when missing
FlapTokenGraduated(address token)RequiredThe launch met its graduation floor. For DIRECT_V2_POOL_TAX it is emitted only inside the launch transaction. Missing β†’ the token did not graduate and, for this kind, never will (show it as New).

Graduation rules depend on kind. A future kind may graduate later through a progress mechanism, so index graduation by (token, kind) and keep listening after the launch transaction.

Token activity

These events are emitted by each Pool Launch token (a FlapDirectTaxToken), not by the Portal.

EventRequiredDescription Β· default when missing
FlapTokenBalanceChanged(address from, address to, uint256 value, uint256 fromBalance, uint256 toBalance)OptionalSame as ERC-20 Transfer, plus both sides' balances after the transfer (address(0) = mint/burn), so holders need no separate balanceOf call. Missing β†’ use Transfer and read balances.
FlapTaxLiquidationError(bytes reason)OptionalThe token tried to swap its accumulated tax and the swap failed. The trade itself still succeeded.

Trades happen on the token's V2 pool: index the pool's standard Swap / Sync events for prices and volume.

Other Portal events

EventRequiredDescription Β· default when missing
FlapVaultFactoryRegistered(address factory, bool enabled, bool official, uint8 riskLevel)OptionalA vault factory's full current configuration. Missing β†’ unregistered (UNVERIFIED).
FlapAuditReportSubmitted(address indexed taxToken, address indexed auditor, uint8 riskLevel, string ipfsCid)OptionalA token's own risk level changed after an audit. Missing β†’ the token's level is its factory's.
FlapVaultWhitelisted(address indexed token, address indexed vault)OptionalA low-risk vault's reserve became eligible for the verified Token Dock (terminals can label it "reserved"). Missing β†’ the reserve stays in the open dock.
FlapVaultFactoryMultiplierSet(address factory, uint16 multiplierBps, bool enabled)OptionalA vault factory's floor multiplier or enabled flag changed.
FlapLaunchQuoteConfigSet(uint8 indexed kind, address indexed quoteToken, bool enabled, uint256 quotePriceUsd)OptionalA quote token was enabled or disabled for a kind, or its USD price (used for the graduation floor) changed.
FlapCreationPausedSet(bool paused)OptionalCreation was paused or resumed for callers that no enabled factory covers.

Suggested flow

  1. Subscribe to FlapNewLauncherCreated to list Launchers as soon as they are created.
  2. On FlapDirectLaunched, check schemaId, decode DirectLaunchV2Data, and store the token, its pool, quote token, supply and opening price.
  3. In the same transaction, pick up FlapNewVaultCreated, FlapDistributionExecuted and FlapTokenGraduated for the same token. No FlapTokenGraduated β†’ mark the token New.
  4. If the launch came from a Launcher, link the token to it: the Launcher calls launchTaxTokenV2 itself, so the launch transaction's to is the Launcher address, and getLauncher(to) confirms it is a recorded Launcher. Its FlapDirectLaunched.creator is the user the Launcher declared.
  5. Follow the token's FlapTokenBalanceChanged and its pool's swaps.

2. Read state from the chain

All reads below are views on the Pool Launch Portal proxy unless noted.

Launch state: getLaunchToken

struct LaunchTokenState {
    uint8 kind;                 // LaunchKind (0 = DIRECT_V2_POOL_TAX)
    uint8 tokenVersion;         // 0 = TOKEN_DIRECT_TAXED
    uint8 externalVenue;        // MigratorType the token lists on (V2_MIGRATOR)
    uint8 dexId;                // DEX
    uint8 quoteTokenType;       // native vs ERC-20
    address quoteTokenAddress;  // address(0) = native
    uint8 status;               // 6 = Created (pool not seeded yet), 4 = DEX
    uint256 circulatingSupply;
    uint256 pendingRemainder;   // dev-reserved tokens a distribution may still hand out
    address pool;               // the V2 pool
    uint8 poolType;             // 0 = V2
    bytes venueData;            // empty for V2
    uint256 totalSupply;
}

function getLaunchToken(address token) external view returns (LaunchTokenState memory state);

It reverts LaunchTokenNotFound for a token that was not launched through Pool Launch, so it doubles as "is this a Pool Launch token?". Enum fields are uint8, so a newer enum value never breaks decoding.

Initial seed: getLaunchAllocation

struct LaunchAllocationView {
    uint256 totalSupply;
    uint256 poolToken;   // tokens put into the pool at launch
    uint256 poolQuote;   // quote put into the pool at launch
}

function getLaunchAllocation(address token) external view returns (LaunchAllocationView memory allocation);

The opening price is poolQuote / poolToken, and totalSupply - poolToken is the reserved supply (vault or dev). Tokens launched before this record existed return 0 for both seed amounts.

Vault: getLaunchVault / getLaunchVaultInfo

function getLaunchVault(address token) external view returns (address vault, address vaultFactory);

struct LaunchVaultInfo {
    address vault;
    address vaultFactory;
    bool isOfficial;
    uint8 riskLevel;     // 0 UNVERIFIED Β· 1 LOW Β· 2 LOW_MEDIUM Β· 3 MEDIUM Β· 4 HIGH
    uint8 category;
}

function getLaunchVaultInfo(address token) external view returns (LaunchVaultInfo memory info);

Both are zero when the launch had no VAULT fee slot.

Launcher: getLauncher

struct LauncherRecord { address launcherFactory; }

function getLauncher(address launcher) external view returns (LauncherRecord memory record);
function launcherFactoryMultiplier(address factory) external view returns (bool enabled, uint16 multiplierBps);

getLauncher is zero for an address that was not created through Portal.newLauncher, so it verifies a Launcher's origin. On the Launcher itself, description() returns its current v1 JSON (the same data as in FlapNewLauncherCreated, possibly newer after a beacon upgrade).

The Portal records which Launcher launched each token, but has no public read for it: link a token to its Launcher from the launch transaction, as in the suggested flow.

Graduation preview: graduationFloorV2

Before a launch, graduationFloorV2 tells you whether the planned seed meets the creation floor and the graduation floor (the formulas are in Basic Mechanism β†’ Graduation):

struct GraduationFloorParams {
    address quoteToken;     // address(0) = native
    uint256 quotePriceUsd;  // quote token USD price, 18-decimal (launchQuoteConfig returns it)
    uint256 poolQuote;
    uint256 poolToken;
    uint256 totalSupply;
    address vaultFactory;   // the launch's VAULT-slot factory, address(0) = none
}

struct GraduationFloorResultV2 {
    bool meetsFloor;           // will graduate
    uint256 floorUsdWad;       // graduation floor, USD of pool quote (18-decimal)
    uint256 quoteUsdWad;       // USD value of poolQuote
    uint16 multiplierBps;      // the multiplier used (10000 = 1x)
    bool meetsCreateFloor;     // false => the launch reverts LaunchLiquidityTooLow
    uint256 createFloorUsdWad; // creation floor
}

function graduationFloorV2(GraduationFloorParams calldata params) external view returns (GraduationFloorResultV2 memory);
function graduationFloorV2ForLauncher(GraduationFloorParams calldata params, address launcher)
    external view returns (GraduationFloorResultV2 memory);
  • For a launch a Launcher will make, use graduationFloorV2ForLauncher: it also applies the Launcher factory's multiplier (an enabled Vault factory's multiplier comes first).
  • Both floors are the total USD value of the pool quote, not a price per token.
  • What is missing to graduate: shortfallUsd = max(0, floorUsdWad - quoteUsdWad), and in quote units shortfallUsd * 1e18 / quotePriceUsd (then scale to the quote token's decimals).
  • The older graduationFloor() returns only the first four fields. After a launch, graduation is final: use the FlapTokenGraduated event.

Quote token configuration: launchQuoteConfig

function launchQuoteConfig(uint8 kind, address quoteToken) external view returns (bool enabled, uint256 quotePriceUsd);

The token and its pool

A Pool Launch token is a FlapDirectTaxToken (ERC-20). Besides the standard ERC-20 reads:

ReadReturns
mainPool()The V2 pool it was launched into
quoteToken()Quote token of the pool
buyTaxRate(), sellTaxRate(), taxRate()Tax in bps (taxRate is the larger of the two)
taxExpirationTime()When the tax ends
taxProcessor(), dividendContract()The contracts that split the tax and pay dividends
metaURI()IPFS CID of the metadata JSON (fetch through the Flap IPFS Gateway)
maxSupply(), launchBlock(), launchTime()Supply and launch time

For the pool's price and depth, call getReserves() and token0() on mainPool(); the price is the quote reserve divided by the token reserve.

TaxTokenHelper.getTaxTokenInfoV2(token) also works for Pool Launch tokens: it returns the tax split, cumulative statistics, dividend token and the vault with its risk level in one call (see the bonding curve indexer page for the struct).