# Table of Contents - [Brief Overview of Flap | Flap](#brief-overview-of-flap-flap) - [Developers | Flap](#developers-flap) - [Flap Official Links | Flap](#flap-official-links-flap) - [Deployed Contract Addresses | Flap](#deployed-contract-addresses-flap) - [Basic & Mechanism | Flap](#basic-mechanism-flap) - [Portal vs VaultPortal | Flap](#portal-vs-vaultportal-flap) - [Flap Tax Token | Flap](#flap-tax-token-flap) - [Migrated To DEX | Flap](#migrated-to-dex-flap) - [PreBond Tax | Flap](#prebond-tax-flap) - [Tax Token V2 | Flap](#tax-token-v2-flap) - [Wallet & Terminal & Bot Developers | Flap](#wallet-terminal-bot-developers-flap) - [Tax Token V1 | Flap](#tax-token-v1-flap) - [Deployed Contract Addresses | Flap](#deployed-contract-addresses-flap) - [A Quick Start For Wallet & Terminal & Bot Developers | Flap](#a-quick-start-for-wallet-terminal-bot-developers-flap) - [Tax Token V3 | Flap](#tax-token-v3-flap) - [Bonding Curve | Flap](#bonding-curve-flap) - [Parse Token Meta | Flap](#parse-token-meta-flap) - [Index Token Created Events | Flap](#index-token-created-events-flap) - [Bonding Curve In Developers' Perspective | Flap](#bonding-curve-in-developers-perspective-flap) - [Inspect A Token | Flap](#inspect-a-token-flap) - [Media Kit | Flap](#media-kit-flap) - [Deployed Contract Addresses | Flap](#deployed-contract-addresses-flap) - [Token Launcher Developers | Flap](#token-launcher-developers-flap) - [Quick start for token launcher developers | Flap](#quick-start-for-token-launcher-developers-flap) - [OpenClaw Skills | Flap](#openclaw-skills-flap) - [Token Migration | Flap](#token-migration-flap) - [Vault Developers | Flap](#vault-developers-flap) - [Deployed Contract Addresses | Flap](#deployed-contract-addresses-flap) - [Token Version Specification | Flap](#token-version-specification-flap) - [Quick start for Vault Developers | Flap](#quick-start-for-vault-developers-flap) - [Audit Reports | Flap](#audit-reports-flap) - [Preview | Flap](#preview-flap) - [Registered vaults | Flap](#registered-vaults-flap) - [Tutorials | Flap](#tutorials-flap) - [Inspect A Tax Token | Flap](#inspect-a-tax-token-flap) - [Launch token through VaultPortal | Flap](#launch-token-through-vaultportal-flap) - [Beta: PVE Curve | Flap](#beta-pve-curve-flap) - [Flap Tax Vault | Flap](#flap-tax-vault-flap) - [Flap Trigger Service | Flap](#flap-trigger-service-flap) - [Gift Vault V2 | Flap](#gift-vault-v2-flap) - [Flap Candy Box | Flap](#flap-candy-box-flap) - [Trade Tokens | Flap](#trade-tokens-flap) - [World Cup Resolver | Flap](#world-cup-resolver-flap) - [Using an LP Token or Child Token as the Dividend Token | Flap](#using-an-lp-token-or-child-token-as-the-dividend-token-flap) - [Building Upgradeable Vaults — Beacon Proxy Pattern | Flap](#building-upgradeable-vaults-beacon-proxy-pattern-flap) - [Flap AI Oracle | Flap](#flap-ai-oracle-flap) - [Gift Vault (FlapXVault) | Flap](#gift-vault-flapxvault-flap) - [Vault & VaultFactory Specification | Flap](#vault-vaultfactory-specification-flap) - [Launch token through Portal | Flap](#launch-token-through-portal-flap) - [World Cup Viewer | Flap](#world-cup-viewer-flap) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) --- # Brief Overview of Flap | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/readme.md) . ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252FmvnHT1CMyrLxyaeRbRb4%252FFlap%2520Banner.png%3Falt%3Dmedia%26token%3D35e4edd8-4a93-40e2-b062-6f330ac93f9d&width=768&dpr=3&quality=100&sign=ee241f5f&sv=2) Flap is an on-chain token launch protocol on EVM, born out of an ETHGlobal hackathon victory. Flap's mission is to empower projects and creators with decentralized, on-chain tools to launch tokens and design customized token standards seamlessly. #### [](https://docs.flap.sh/flap#rich-terminal-and-wallets-support) Rich Terminal & Wallets Support Tokens launched via Flap are supported by a broad ecosystem of wallets, trading platforms, and bots, including **OKX Wallet, Binance Wallet, GMGN, Debot, Ave.ai, Dragun**, **Axiom** and more—ensuring strong discoverability and liquidity from day one. #### [](https://docs.flap.sh/flap#non-tax-and-tax-token-support) Non-Tax & Tax Token Support Flap supports both **non-tax tokens** and **tax tokens**. For tax tokens, creators can customize trading tax fee rates at **1%, 3%, 5%, or 10%**, enabling flexible use cases such as community incentives, marketing, treasury funding, or charitable contributions. ### [](https://docs.flap.sh/flap#flap-tax-vaults) Flap Tax Vaults Flap offers the ability for developers to create custom smart contracts for tax token management. By adhering to the provided vault specifications, enhanced insights and details can be accessed via our website and supported terminals. Developers can also build and deploy their own vault templates (Vault Factory), offering a monetization opportunity when users opt to utilize their templates. [NextFlap Official Links](https://docs.flap.sh/flap/flap-official-links) Last updated 1 month ago --- # Developers | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers.md) . This guide is structured to introduce you to the different facets of developing with Flap. It's recommended to first familiarize yourself with the **basic mechanisms of Flap**. Once you're comfortable with the fundamentals, proceed to the section that suits your specific role: * **Wallets & Terminal & Bot Developers:** For developers who want to build an indexer for Flap and trade tokens launched from Flap * **Launcher Developers:** For applications leveraging Flap's infrastructure to launch tokens. * **Vault Developers:** For those interested in building upon Flap's Vault system. Please read the section corresponding to your role for detailed instructions and guidance. * [Basic & Mechanism](https://docs.flap.sh/flap/developers/basic-and-mechanism) * [Wallets & Terminal & Bot Developers](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers) * [Launcher Developers](https://docs.flap.sh/flap/developers/token-launcher-developers) * [Vault Developers](https://docs.flap.sh/flap/developers/vault-developers) [PreviousFlap Official Links](https://docs.flap.sh/flap/flap-official-links) [NextDeployed Contract Addresses](https://docs.flap.sh/flap/developers/deployed-contract-addresses) Last updated 5 months ago --- # Flap Official Links | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/flap-official-links.md) . Flap Website: [](https://www.gamesverse.io/)[https://flap.sh/](https://flap.sh/) Flap X (Formally Twitter): [](https://twitter.com/0xGameVerse)[https://twitter.com/flapdotsh](https://twitter.com/flapdotsh) Flap Telegram: [https://t.me/FlapOfficial](https://t.me/FlapOfficial) Flap Discord: [https://discord.gg/Flap](https://discord.gg/Flap) Flap Farcaster: [https://warpcast.com/flap](https://warpcast.com/flap) [PreviousBrief Overview of Flap](https://docs.flap.sh/flap) [NextDevelopers](https://docs.flap.sh/flap/developers) Last updated 7 months ago --- # Deployed Contract Addresses | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/deployed-contract-addresses.md) . This page lists all deployed contract addresses across supported chains. Each chain has a table with the contract name, address, and any relevant notes. [](https://docs.flap.sh/flap/developers/deployed-contract-addresses#mainnet) Mainnet ----------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/deployed-contract-addresses#bnb-chain) BNB Chain Name Address Notes Portal `0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0` v5.8.6 Standard Token Impl `0x8b4329947e34b6d56d71a3385cac122bade7d78d` Vanity suffix: 8888 Tax Token V1 Impl `0x29e6383F0ce68507b5A72a53c2B118a118332aA8` Vanity suffix: 7777 Tax Token V2 Impl `0xae562c6A05b798499507c6276C6Ed796027807BA` Tax Token V3 Impl `0x024f18294970B5c76c0691b87f138A0317156422` Standard Token V3 Impl `0x88881b6f03090462a969eC7f48385744Eeb63333` Vanity suffix: 8888 VaultPortal `0x90497450f2a706f1951b5bdda52B4E5d16f34C06` Trigger Service `0xcf4EE25035CF883895110f367F5BA8172416a7F9` See [Flap Trigger Service](https://docs.flap.sh/flap/developers/preview/flap-trigger-service) AI Oracle `0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39` Preview. See [Flap AI Oracle](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle) Candy Box `0x6255fbd731272a517022e99f6CaCf6A5De9414Ee` Preview. See [Flap Candy Box](https://docs.flap.sh/flap/developers/preview/flap-candy-box) Tax Token Helper `0x53841c73217735F37BC1775538b03b23feFD8346` ### [](https://docs.flap.sh/flap/developers/deployed-contract-addresses#robinhood) Robinhood Name Address Notes Portal `0x26605f322f7fF986f381bB9A6e3f5DAb0bEaEb09` v5.14.15 Tax Token V3 Impl `0x7777C8743C88B3aff3cf262135beF2c8b2e83333` Vanity suffix: 7777 VaultPortal `0xe9F7AB7DE8FB8756acbB6a1cd13316a43308197B` Tax Token Helper `0xb10bD2672aE63735d677164A54B573a016f0203C` ### [](https://docs.flap.sh/flap/developers/deployed-contract-addresses#toshimart) Toshimart Name Address Notes Portal `0x1ea172Fb88C24DFC21Ff6Fa38762511C123bA948` v4.11.0 Standard Token Impl `0xF3c514E04f83166E80718f29f0d34F206be40A0A` Vanity suffix: 8453 ### [](https://docs.flap.sh/flap/developers/deployed-contract-addresses#x-layer) X Layer Name Address Notes Portal `0xb30D8c4216E1f21F27444D2FfAee3ad577808678` v4.12.1 Standard Token Impl `0x12Dc83157Bf1cfCB8Db5952b3ba5bb56Cc38f8C9` Vanity suffix: 1111 Tax Token V1 Impl `0xa9918579C9eD0899eCc7e449B9c59916Fb89bAF1` Vanity suffix: 7777 ### [](https://docs.flap.sh/flap/developers/deployed-contract-addresses#muffun) Muffun Name Address Notes Portal `0x4267F317adee7C6478a5EE92985c2BD5D855E274` v4.13.2 Standard Token Impl `0x2a770E952bB2700393238199B5889013693A8271` Vanity suffix: 8888 ### [](https://docs.flap.sh/flap/developers/deployed-contract-addresses#monad) Monad Name Address Notes Portal `0x30e8ee7b5881bf2E158A0514f2150aabe2c68b23` v5.1.2 Standard Token Impl `0xB88189aA1162850D75A1c1e16F837b7979994184` Vanity suffix: 8888 Tax Token V1 Impl `0x1C8847736521f5cD725dFB8f33c7c610826e7C42` Vanity suffix: 1111 [](https://docs.flap.sh/flap/developers/deployed-contract-addresses#testnet) Testnet ----------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/deployed-contract-addresses#bnb-testnet) BNB Testnet Name Address Notes Portal `0x5bEacaF7ABCbB3aB280e80D007FD31fcE26510e9` v5.8.5 Standard Token Impl `0x87D5f292ba33011997641C7a7Bd2b17799aaA814` Vanity suffix: 8888 Tax Token V1 Impl `0x87d8D03d0c3E064ACdb48E42fecbE8a8538dE6Fc` Vanity suffix: 7777 Tax Token V2 Impl `0x2486e3ff5502bac48D2D86457e7c24B2bB0dDDb5` Tax Token V3 Impl `0xE6Ff967a887084c16D0fD71548CF709542cc1557` VaultPortal `0x027e3704fC5C16522e9393d04C60A3ac5c0d775f` Trigger Service `0x560E9830926C9e0EB98a59c6b9902383Fc0D9Eb2` See [Flap Trigger Service](https://docs.flap.sh/flap/developers/preview/flap-trigger-service) AI Oracle `0xFfddcE44e8cFf7703Fd85118524bfC8B2f70b744` Preview. See [Flap AI Oracle](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle) Candy Box `0x8e6C16Bf07022a7Da9398B543f38846E9355Bd70` Preview. See [Flap Candy Box](https://docs.flap.sh/flap/developers/preview/flap-candy-box) Tax Token Helper `0xD64441e5FcD02D342B8cf6eBA10Ef6E40d0dA90f` [PreviousDevelopers](https://docs.flap.sh/flap/developers) [NextBasic & Mechanism](https://docs.flap.sh/flap/developers/basic-and-mechanism) Last updated 2 days ago --- # Basic & Mechanism | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/basic-and-mechanism.md) . This chapter explains the core mechanisms behind Flap and the foundational components developers need to understand before integrating. It covers bonding curve behavior, the DEX migration flow, Portal vs VaultPortal differences, and the Flap Tax Token model. [](https://docs.flap.sh/flap/developers/basic-and-mechanism#in-this-chapter) In this chapter ------------------------------------------------------------------------------------------------- * [📈 Bonding Curve](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve) * [⚖️ Migrated To DEX](https://docs.flap.sh/flap/developers/basic-and-mechanism/list-on-dex) * [Portal vs VaultPortal](https://docs.flap.sh/flap/developers/basic-and-mechanism/portal-vs-vaultportal) * Flap Tax Token * [PreBond Tax](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax) * [Tax Token V1](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1) * [Tax Token V2](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2) [PreviousDeployed Contract Addresses](https://docs.flap.sh/flap/developers/deployed-contract-addresses) [NextBonding Curve](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve) Last updated 5 months ago --- # Portal vs VaultPortal | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/basic-and-mechanism/portal-vs-vaultportal.md) . [](https://docs.flap.sh/flap/developers/basic-and-mechanism/portal-vs-vaultportal#overview) Overview --------------------------------------------------------------------------------------------------------- The `Portal` is the entry point of the Flap bonding curve protocol. It launches standard tokens and tax tokens and wires them to the bonding curve lifecycle. For each tax token launched, if the "funds recipient wallet" percentage is set to non-zero, a portion or all tax revenue is sent to that wallet. And when that wallet is a smart contract and implements Vault interface, it is called a `Vault`. The `VaultPortal` builds on top of the `Portal` to add Vault functionality for tax tokens. It launches a tax token and sets up a Vault in one flow. For Vault concepts and behavior, see [developers/vault-developers/flap-tax-vault.md](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault) . [](https://docs.flap.sh/flap/developers/basic-and-mechanism/portal-vs-vaultportal#when-to-use-which) When to use which --------------------------------------------------------------------------------------------------------------------------- * Use `Portal` when you only need to launch a standard token or a tax token without Vault features or your custom Vault implementation. * Use `VaultPortal` when you want Vault-backed tax tokens and using a vault factory. (You can use any Vault Factory, this is permissionless.) [](https://docs.flap.sh/flap/developers/basic-and-mechanism/portal-vs-vaultportal#relationship-diagram) Relationship diagram --------------------------------------------------------------------------------------------------------------------------------- ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252Fgit-blob-56780fc409e642507100ba5aa3f4626a767bde85%252Fvault-vs-vaultportal.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=5835ada8&sv=2) Vault vs VaultPortal relationship [PreviousMigrated To DEX](https://docs.flap.sh/flap/developers/basic-and-mechanism/list-on-dex) [NextFlap Tax Token](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token) Last updated 3 months ago --- # Flap Tax Token | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token.md) . [PreBond Tax](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax) [Tax Token V1](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1) [Tax Token V2](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2) [Tax Token V3](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3) [PreviousPortal vs VaultPortal](https://docs.flap.sh/flap/developers/basic-and-mechanism/portal-vs-vaultportal) [NextPreBond Tax](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax) Last updated 5 months ago --- # Migrated To DEX | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/basic-and-mechanism/list-on-dex.md) . [](https://docs.flap.sh/flap/developers/basic-and-mechanism/list-on-dex#overview) Overview ----------------------------------------------------------------------------------------------- Each token launched on flap has a maximum supply of 1 Billion. When a token reaches the milestone to be listed on the DEX, the remaining supply (non circulating) tokens and all the reserve (i.e., the quote token, e.g., ETH/BNB) will be added as liquidity to DEX. For different deployment, the milestone to be listed on DEX is different. In previous section ( [Bonding Curve](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve) ), we know that the reserve of a token can be found by its circulating supply. So we can represent the milestone for migrating to DEX in either circulating supply or reserve. We list both of them in the following table for BNB chain when quote is BNB: Deployment Circulating Supply Reserve BSC (Legacy before block #42042177 ) 800M 30.1 BNB BSC (Legacy since block #42042177) 800M 16 BNB BSC (Legacy since block 47855189 and before [51314696](https://bscscan.com/block/51314696) ) 800M 8 ETH BSC ( After [51314696](https://bscscan.com/block/51314696) ) 800M 16 ETH / 10000USD1 Let's take a look at the BSC Legacy deployment as an example. The purple point is the milestone when the token can be listed on the DEX, which is a circulating supply of 800M, or a reserve of 16 BNB or a market cap of 100 BNB. When the token reaches the milestone, it will be listed on DEX. If we are adding this token to Uniswap V3 (or its fork, e.g, PancakeSwap V3), the reserve (16 BNB) and the remaining token (200M) will be added as liquidity. ~Besides, the Flap protocol is flexible and allows more token to be sold on the bonding curve. For example (the red curve on the following graph), if the last buyer buys a lot of token on the bonding curve, the resulting circulating supply would be 900M , and the reserve will be 36 BNB giving a market cap of 400 BNB. The token will still be listed on DEX with the remaining 100M token and 36 BNB reserve.~ (CHANGES: You can not buy more than 800M no matter it is an old token or new one.) When a token is a tax token, it can only be migrated to Uniswap V2 or its forks. For non-tax token, we will first try to add liquidity to Uniswap V3 (or its fork). If it is not viable, we will fallback to Uniswap V2 (or its fork). [](https://docs.flap.sh/flap/developers/basic-and-mechanism/list-on-dex#adding-liquidity-to-uniswap-v3-or-its-fork) Adding Liquidity To Uniswap V3 (or its fork) --------------------------------------------------------------------------------------------------------------------------------------------------------------------- When adding liquidity to Uniswap V3, we only add liquidity to a concentrated range of prices, giving the token a better depth. The lowest price is the initial price on the bonding curve (i.e., the price when no one has bought the token yet), the current price is the last price on the bonding curve before the token is listed on DEX, and the max price is 10000 times of the current price. ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252FvGvCdUzOpRlJHUov59rS%252Fimage.png%3Falt%3Dmedia%26token%3D6cea8a96-a22b-4049-98d0-cd58a00eab09&width=768&dpr=3&quality=100&sign=dc22c70&sv=2) [PreviousBonding Curve](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve) [NextPortal vs VaultPortal](https://docs.flap.sh/flap/developers/basic-and-mechanism/portal-vs-vaultportal) Last updated 5 months ago --- # PreBond Tax | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax.md) . Flap supports Tax tokens. Since V5.4.0 (on BNB Chain & XLayer) , tax is not only applied to migrated tax tokens but also the ones that are still on the bonding curve. When a tax token has already migrated DEX, it will charges a tax on every trade through the main pool and accumulate the tax in the token first. Once the accumulated token amount reaches a specific liquidation threshold, an automatic liquidation happens to sell the token for quote and sent the the beneficiary through the Tax Splitter. (Check below to learn more detaills) When a tax token is still on the bonding curve, we don’t implement it as a “real tax” like a migrated one. To make it simple and easy to do an off-chain quote, we implement it as an `extra fee` added upon the current bonding curve fee. For example: * For a non-tax token, the current bonding curve fee on BNB chain is 1%. * For a token with a tax of 3%, the effective trading fee becomes: 1% + 3% = 4% * For a token with a tax of 1%, the effective trading fee is 2% [PreviousFlap Tax Token](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token) [NextTax Token V1](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1) Last updated 5 months ago --- # Tax Token V2 | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2.md) . [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2#overview) Overview --------------------------------------------------------------------------------------------------------------- The V2 tax token let you split the tax for different usage: ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252FolhAX5erGo4Q7uPNBHoH%252Fimage.png%3Falt%3Dmedia%26token%3Dd5aa6fd2-2aa7-4bb1-b37a-5ea5a762a2c1&width=768&dpr=3&quality=100&sign=b09b1b5e&sv=2) When the "Funds Recipient Wallet" section is 100%, a v1 tax token will be created instead ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2#tax-processor-and-dividend) Tax Processor & Dividend ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252F9Ysh83xjntwFHSxzijPg%252Fimage.png%3Falt%3Dmedia%26token%3D680166fd-ccdb-4a36-ac70-b7df9bbc08fe&width=768&dpr=3&quality=100&sign=19dca351&sv=2) For every Tax Token V2 launch, the following contracts are deployed: 1. **Tax Processor** - Always deployed 2. **Dividend Contract** - Optionally deployed if dividend distribution is enabled (when `dividendBps > 0`) When the token is still trading on the bonding curve (before DEX migration): Copy User buys/sells on Portal bonding curve ↓ Portal collects extra trading fee as tax in quote tokens (WETH/BNB/USDT/etc.) ↓ Portal sends extra fee to TaxProcessor.processBondingCurveTax(quoteAmount) ↓ Tax Processor accumulates quote tokens for later distribution While the token is migrated to DEX and when users trade the Flap Tax Token V2 on DEX: Copy User trades token on DEX ↓ Tax Token V2 applies tax (e.g., 5%) on transfer ↓ Tax tokens accumulate in Token contract ↓ When threshold reached → Triggers tax liquidation ↓ Token contract calls TaxProcessor.processTaxTokens(taxAmount) **Anyone** can call `TaxProcessor.dispatch()` to distribute accumulated funds: Copy Anyone calls dispatch() ↓ 1. Send feeQuoteBalance → Funds Recipient Wallet 2. Send marketQuoteBalance → Market Address 3. Send dividendQuoteBalance → Dividend Contract 4. Process preBondBurnFunds (the quote accumuated on bonding curve phase for the burn): - If token on bonding curve: Use Portal to buyback & burn - If token on DEX: Swap quote tokens for tax tokens & burn 5. Process burn: - Send token to flapBlackHole or Burn address * Flap Tax Trigger Bot monitors balances and calls `dispatch()` when economically viable * Bot pays gas fees to ensure timely distribution * Anyone can call dispatch() if they want to pay the gas If dividend contract is deployed (`dividendBps > 0`): Copy Token holder balance changes (buy/sell/transfer) ↓ Token contract calls Dividend.setShare(user, newBalance) ↓ Dividend contract updates user's share ↓ Settles any pending rewards before share change **Excluded from dividends:** * DEX pool addresses * Token contract itself * Dividend contract * Dead address (0x000...dEaD) * FlapBlackHole * Zero address * Addresses with balance below `minimumShareBalance` **Manual Claiming:** Copy Token holder calls withdrawDividends() ↓ Dividend contract calculates withdrawable amount ↓ If dividendToken == WETH: Unwraps to ETH and sends If dividendToken == other: Sends ERC20 tokens ↓ Updates withdrawnDividends tracking **Automated Claiming (by Bot):** Copy Flap Tax Trigger Bot monitors pending dividends ↓ When holder's pending > threshold: Bot calls distributeDividend([holder1, holder2, ...]) ↓ Dividend contract sends rewards to holders ↓ Bot pays gas fees **Anyone Can Trigger:** Copy Any external caller can call: - withdrawDividendsFor(user) - claim for specific user - distributeDividend([users]) - batch claim for multiple users - Caller pays gas fees [PreviousTax Token V1](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1) [NextTax Token V3](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3) Last updated 5 months ago --- # Wallet & Terminal & Bot Developers | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers.md) . [A Quick Start For Wallet & Terminal & Bot Developers](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/a-quick-start-for-wallet-terminal-bot-developers) [Deployed Contract Addresses](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/deployed-contract-addresses) [Index Token Created Events](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events) [Bonding Curve In Developers' Perspective](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective) [Inspect A Token](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token) [Parse Token Meta](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta) [Token Version Specification](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification) [Inspect A Tax Token](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token) [Trade Tokens](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens) [Token Migration](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-migration) [PreviousTax Token V3](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3) [NextA Quick Start For Wallet & Terminal & Bot Developers](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/a-quick-start-for-wallet-terminal-bot-developers) --- # Tax Token V1 | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1.md) . Tax Token V1 is still used and maintained. A V1 Tax token is created when you are using the vault mode or when 100% tax to Funds Recipient Wallet. [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1#overview) Overview --------------------------------------------------------------------------------------------------------------- In V1 tax token, all the tax will be sent to a single beneficiary. However, you can build a smart contract as the beneficiary to receive the tax, where you can implement your own logics. We have a recommended interface for such contracts (check [https://github.com/flap-sh/gitbook/blob/main/developers/basic-and-mechanism/flap-tax-token/flap-tax-vault.md](https://github.com/flap-sh/gitbook/blob/main/developers/basic-and-mechanism/flap-tax-token/flap-tax-vault.md) for more details) [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1#tax-splitter) Tax Splitter ----------------------------------------------------------------------------------------------------------------------- To avoid DOS, we do not liquidate and distribute the Tax in the tax token directly. For each v1 tax token, a "Tax Splitter" contract will be deployed on creation. Both the liquidated Tax (when token is on DEX) and the Tax on the bonding curve will be directly sent to the "Tax Splitter" first. Then anyone could trigger the tax distribution by calling the "Tax Splitter" contract's `split` method. However, for most of the time you don't need to manually call "Tax Splitter" yourself , Flap will run a bot to automatically trigger the distribution when needed. ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252FqK4wyplxJRg7marop3hj%252Fimage.png%3Falt%3Dmedia%26token%3D9dd01e18-673f-451c-8710-9179c1f40a0b&width=768&dpr=3&quality=100&sign=1ea14ff&sv=2) [PreviousPreBond Tax](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax) [NextTax Token V2](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2) Last updated 5 months ago --- # Deployed Contract Addresses | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/deployed-contract-addresses.md) . See [Deployed Contract Addresses](https://docs.flap.sh/flap/developers/deployed-contract-addresses) . [PreviousA Quick Start For Wallet & Terminal & Bot Developers](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/a-quick-start-for-wallet-terminal-bot-developers) [NextIndex Token Created Events](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events) Last updated 1 month ago --- # A Quick Start For Wallet & Terminal & Bot Developers | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/a-quick-start-for-wallet-terminal-bot-developers.md) . This page outlines the minimal steps to integrate with Flap for wallets, terminals and bots. Each step links to the detailed documentation. 1. **Find the** `**Portal**` **entrypoint.** Our main protocol has a single entrypoint contract called `Portal`. Use the deployed addresses listed in [Deployed Contract Addresses](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/deployed-contract-addresses) . 2. **Index tokens (recommended).** To index tokens launched on Flap, listen to `TokenCreated` and related events emitted by `Portal`. See [Index Token Created Events](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events) for the full event list and indexing strategy. The `TokenCreated` event only contains the IPFS CID of the metadata. To parse token metadata, follow [Parse Token Meta](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta) . 3. **Skip indexing and query on demand (alternative).** If you do not want to index, you can query token info directly from `Portal` with the `getTokenV*` methods. See [Inspect A Token](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token) for details. 4. **Tax tokens: query dynamic info on-chain.** Tax tokens have dynamic fields (e.g., accumulated stats) that are best retrieved on-chain. Use the Tax Token Helper as described in [Inspect A Tax Token](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token) . For vault-specific data, see [how to inspect a tax token's vault](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#how-to-inspect-a-tax-tokens-vault) . 5. **Trade tokens.** You can quote on-chain using `quoteExactInput`, or compute quotes off-chain and submit swaps using `swapExactInput`. See [Trade Tokens](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens) for details. [PreviousWallet & Terminal & Bot Developers](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers) [NextDeployed Contract Addresses](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/deployed-contract-addresses) Last updated 5 months ago --- # Tax Token V3 | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3.md) . `FlapTaxTokenV3` (`TOKEN_TAXED_V3`) is the recommended token type for all new integrations. It is launched via the `newTokenV6` entry point on Portal v5.9.0+. [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#overview) Overview --------------------------------------------------------------------------------------------------------------- Tax Token V3 introduces four major capabilities over V1/V2: 1. **Asymmetric buy/sell tax rates** — independent rates for buys and sells. 2. **Commission receiver** — a permanent, protocol-enforced fee share for third-party launchpad integrators. 3. **Unified launcher (**`**newTokenV6**`**)** — single entry point for all current token types. 4. **Bidirectional dynamic liquidation threshold** — self-correcting threshold that prevents permanent drift in either direction. * * * [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#id-1.-asymmetric-buy-sell-tax-rates) 1\. Asymmetric buy / sell tax rates --------------------------------------------------------------------------------------------------------------------------------------------------------------------- `FlapTaxTokenV3` supports independent buy and sell tax rates, enabling configurations such as a low buy tax (to encourage accumulation) and a higher sell tax (to discourage dumping), or the reverse. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#reading-tax-rates-on-chain) Reading tax rates on-chain Copy IFlapTaxTokenV3 token = IFlapTaxTokenV3(tokenAddress); uint16 buy = token.buyTaxRate(); // e.g. 300 (3%) uint16 sell = token.sellTaxRate(); // e.g. 1000 (10%) uint16 eff = token.taxRate(); // max(buy, sell) — backward-compatible single rate `taxRate()` always returns `max(buyTaxRate, sellTaxRate)`. This preserves compatibility with existing off-chain systems and aggregators that only understand a single tax rate: they will always see the worst-case value and will never under-report the tax. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#indexing-tax-rates-from-events) Indexing tax rates from events Two events are emitted at launch for every V3 tax token: Event Contents Purpose `FlapTokenTaxSet(address token, uint256 tax)` `max(buyTax, sellTax)` Backward-compatible single-rate event; existing indexers continue to work unchanged. `FlapTokenAsymmetricTaxSet(address token, uint256 buyTax, uint256 sellTax)` Both rates New event for systems that support asymmetric rates. For symmetric V1/V2 tokens the two events carry the same value. For V3 tokens, always prefer `FlapTokenAsymmetricTaxSet` when available. **Example log output (buy=300, sell=1000):** Copy FlapTokenTaxSet(token, 1000) // max of buy and sell FlapTokenAsymmetricTaxSet(token, 300, 1000) // full asymmetric detail Old indexers that only listen to `FlapTokenTaxSet` remain fully functional — they will display the effective (worst-case) rate. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#inspecting-a-token-with-gettokenv8) Inspecting a token with `getTokenV8` The `getTokenV8` helper returns the full token state including separate buy and sell tax rates: Copy IPortalLens.TokenStateV8 memory state = portal.getTokenV8(tokenAddress); state.buyTaxRate // e.g. 300 (3%) state.sellTaxRate // e.g. 1000 (10%) state.status // TokenStatus enum (BondingCurve, DEX, …) state.price // current bonding curve price state.progress // progress toward DEX graduation (0–1e18) `getTokenV8` is the recommended state query endpoint for any system that needs to display asymmetric tax information. It is a view function and can be called by any EOA or contract. * * * [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#id-2.-commission-receiver) 2\. Commission receiver ----------------------------------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#what-it-is) What it is The commission receiver feature allows any third-party launchpad operator or integrator to earn a **permanent, protocol-enforced fee share** from every tax token they deploy through Flap. Tokens launched with a commission receiver will be visible on Flap and the commission accrues automatically on every taxable transaction (bonding curve buy, DEX buy, and DEX sell), for the entire lifetime of the tax. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#how-to-enable-it) How to enable it Set `commissionReceiver` to any non-zero address in `NewTokenV6Params`. Only `TOKEN_TAXED_V3` tokens support commission — passing a non-zero receiver for a non-tax token reverts. Copy IPortalTypes.NewTokenV6Params memory params = IPortalTypes.NewTokenV6Params({ // … other fields … tokenVersion: IPortalTypes.TokenVersion.TOKEN_TAXED_V3, commissionReceiver: 0xYourLaunchpadAddress, // receives commission // NOTE: commissionBps is NOT set here — the protocol calculates it }); ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#how-the-commission-rate-is-determined) How the commission rate is determined The commission rate is **calculated entirely by the protocol** at token creation time via `_commissionForTax(effectiveTaxRate)`. The integrator cannot specify it. The formula is: Copy effectiveTaxRate = max(buyTaxRate, sellTaxRate) if effectiveTaxRate <= 100 bps (1%): commissionBps = 600 (6% of the post-fee remainder) else: commissionBps = floor(10000 * 6 / effectiveTaxRate) This means commission is inversely proportional to the tax rate: Effective tax rate Commission bps Commission % 1% (100 bps) 600 6.0% 2% (200 bps) 300 3.0% 3% (300 bps) 200 2.0% 5% (500 bps) 120 1.2% 10% (1000 bps) 60 0.6% The maximum possible commission is **6%** of the post-protocol-fee tax remainder, achievable only when the effective tax rate is ≤ 1%. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#reading-commission-configuration) Reading commission configuration Copy ITaxProcessor processor = ITaxProcessor(IFlapTaxTokenV3(token).taxProcessor()); address receiver = processor.commissionReceiver(); // address(0) if disabled uint16 bps = processor.commissionBps(); // rate the protocol set uint256 pending = processor.commissionQuoteBalance(); // accrued, not yet dispatched Call `processor.dispatch()` to push accumulated balances (fee, commission, marketing, dividends) to their respective receivers. * * * [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#id-3.-newtokenv6-unified-token-launcher) 3\. `newTokenV6` — unified token launcher ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- `newTokenV6` is the single entry point for creating **all** current token types. The specific token implementation is selected via the `tokenVersion` field: `tokenVersion` Enum value Token type Allowed tax rates Commission `TOKEN_V2_PERMIT` 1 Standard ERC-20 with permit none (must be 0) not allowed `TOKEN_TAXED` 2 FlapTaxToken V1 symmetric only; `mktBps` must be 10000 not allowed `TOKEN_TAXED_V2` 4 FlapTaxTokenV2 symmetric only; `mktBps` must not be 10000 not allowed `TOKEN_TAXED_V3` **6** FlapTaxTokenV3 asymmetric allowed; any valid distribution supported `TOKEN_TAXED` and `TOKEN_TAXED_V2` are deprecated and will be removed in a future release. New integrations should always use `TOKEN_TAXED_V3`. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#salt-computation) Salt computation When computing the vanity salt off-chain, always use the `**tokenImplTaxedV3**` **implementation address** as the clone base, regardless of the `mktBps` or distribution parameters you intend to use: Copy // Always use tokenImplTaxedV3 for TOKEN_TAXED_V3 salt vanity search bytes32 salt = _findVanitySalt(VanityType.VANITY_7777, tokenImplTaxedV3, portalAddress); ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#launching-with-a-vault-ivaultportal.newtokenv6withvault) Launching with a vault: `IVaultPortal.newTokenV6WithVault` `IVaultPortal` exposes `newTokenV6WithVault` for integrators that want to attach a revenue vault to a `TOKEN_TAXED_V3` token in the same transaction. Only `TOKEN_TAXED_V3` is accepted — any other `tokenVersion` reverts with `OnlyV3TaxTokenAllowed`. Copy IVaultPortalTypes.NewTokenV6WithVaultParams memory params = IVaultPortalTypes.NewTokenV6WithVaultParams({ // … same fields as NewTokenV6Params (without beneficiary) … tokenVersion: IPortalTypes.TokenVersion.TOKEN_TAXED_V3, vaultFactory: 0xYourVaultFactory, // any factory; unregistered → UNVERIFIED vaultData: abi.encode(/* vault-specific init data */) }); address token = vaultPortal.newTokenV6WithVault(params); The vault address can be retrieved after creation via `IVaultPortal.getVault(token)`. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#dividend-token) Dividend token `FlapTaxTokenV3` always deploys a Dividend contract, regardless of the `dividendBps` value. This means you can launch a token with `dividendBps == 0` and still have a fully functional share-tracking contract attached. #### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#supported-dividend-token-modes) Supported dividend token modes There are three modes for the `dividendToken` field: Mode `dividendToken` value Description **Case 1 — Launching Token** `0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd` (`MAGIC_DIVIDEND_SELF`) The tax token itself is distributed as dividend. Holders earn more of the same token. **Case 2 — Quote Token** `address(0)` (native) or `quoteToken` address The quote token (e.g. WBNB, USDT) is distributed as dividend. This is the simplest and most common setup. **Case 3 — Any ERC-20** Any ERC-20 contract address Any token with sufficient DEX liquidity can be used. The protocol swaps the collected quote token into the dividend token automatically on every dispatch via `SwapRegistry`. `**MAGIC_DIVIDEND_SELF**` (`0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd`) is a sentinel address recognised by the Portal. When you pass this value, the protocol resolves it to the actual tax token address after the token is created. #### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#case-3-eligibility-swapregistry) Case 3 eligibility — SwapRegistry For Case 3, the protocol checks at launch time that a valid swap path exists from the quote token to the chosen dividend token. If the pair is not supported, the transaction reverts with `DividendSwapNotSupported`. You can check eligibility before launching: Copy ISwapRegistry registry = ISwapRegistry(swapRegistryAddress); // Simple boolean check bool supported = registry.isSwapSupported(quoteToken, dividendToken); // Detailed check — returns (supported, errorCode, errorMessage) (bool ok, uint8 code, string memory reason) = registry.isSwapSupportedWithDetailedErrors(quoteToken, dividendToken); **Trust status** — the `SwapRegistry` assigns a trust level to every dividend token: Value Meaning `0x00` Unknown — token is not vetted; the Flap UI shows a warning to users `0x01` Whitelisted — token is audited/vetted by Flap `0x02` Blacklisted — token is known-malicious; dividend conversion is silently skipped (no revert during dispatch) Copy uint8 trust = registry.getTrustStatus(dividendToken); bool blacklisted = registry.isBlacklisted(dividendToken); #### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#tracker-only-mode-dividendbps-0) Tracker-only mode (`dividendBps == 0`) When `dividendBps == 0`, no tax revenue is automatically routed to the Dividend contract, but the contract is still deployed and tracks holder share balances. This is useful for off-chain reward systems that read share data on-chain but push rewards via their own pipeline. In tracker-only mode, `dividendToken` may be `MAGIC_DIVIDEND_SELF` (`0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd`) or any ERC-20 — the swap-path check is skipped because no automatic conversion occurs. Integrators can call `dividend.deposit(token, amount)` manually at any time to distribute any ERC-20 to current share holders. `**dividendBps**` **summary:** `dividendBps` Behaviour `> 0` Tax revenue is automatically routed to the Dividend contract. `dividendToken` must be Case 1, 2, or an eligible Case 3 token. `== 0` No automatic payout. Dividend contract exists and tracks shares only. Any `dividendToken` value is accepted. #### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#retrieving-the-dividend-contract) Retrieving the dividend contract Copy address dividendContract = IFlapTaxTokenV3(token).dividendContract(); address sameAddress = ITaxProcessor(IFlapTaxTokenV3(token).taxProcessor()).dividendAddress(); // dividendContract == sameAddress always holds; both are non-zero for all V3 tokens #### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#code-examples) Code examples Copy // Case 1 — tax token itself as dividend params.quoteToken = address(0); // BNB params.dividendBps = 500; // 5% params.dividendToken = 0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd; // MAGIC_DIVIDEND_SELF // Case 2 — native quote token (address(0) resolves to WBNB) params.quoteToken = address(0); params.dividendBps = 500; params.dividendToken = address(0); // OK — same as quoteToken // Case 2 — ERC-20 quote token params.quoteToken = USDT; params.dividendBps = 500; params.dividendToken = USDT; // must match quoteToken for Case 2 // Case 3 — any ERC-20 (must be supported by SwapRegistry) params.quoteToken = address(0); // BNB params.dividendBps = 500; params.dividendToken = CAKE; // any ERC-20 with a registered swap path // Tracker-only — any dividendToken accepted, no swap-path check params.dividendBps = 0; params.dividendToken = MY_TOKEN; // no payout, just share tracking #### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#swapregistry-deployed-addresses) SwapRegistry deployed addresses Network SwapRegistry Address **BNB Chain Mainnet** `0x644A8f560138418bAD4EdEFC7c17878a3c2fBEB6` **BNB Chain Testnet** `0xEd62Df92e52b89C8100F8EC3B8Eefea073df3408` * * * [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#id-4.-bidirectional-dynamic-liquidation-threshold) 4\. Bidirectional dynamic liquidation threshold ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#background) Background Tax tokens accumulate their own tokens from buy/sell taxes and periodically liquidate (sell) the accumulated balance to the DEX to convert it to the quote token. The **liquidation threshold** governs how many tokens must accumulate before a liquidation is triggered. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#the-v1-problem) The V1 problem `FlapTaxTokenV1` had a one-directional threshold: it could only decrease (making liquidations more frequent) but could never recover upward. In adverse market conditions this caused the threshold to drift to its minimum floor permanently, resulting in constant small liquidations that negatively impacted price. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#v3-fix-bidirectional-adjustment) V3 fix: bidirectional adjustment `FlapTaxTokenV3` introduces a **fully bidirectional** threshold that self-corrects in both directions after every liquidation. The `TaxProcessor` records a reference expected output amount (`liqExpectedOutputAmount`) at initialization and compares actual DEX swap output against it after each liquidation, returning an `int8 liqThresholdDirection` to the token: `liqThresholdDirection` Market condition Threshold action `> 0` (positive) Swap output **below** reference — price is weak Increase threshold by 1% (wait for more tokens before next liquidation) `< 0` (negative) Swap output **above** reference — price is strong Decrease threshold by 1% (liquidate smaller amounts more frequently) `== 0` Output matched reference No change The threshold is bounded between two implementation-level immutables shared by all clones: Copy FlapTaxTokenV3.MIN_LIQ_THRESHOLD // hard floor — threshold never goes below this FlapTaxTokenV3.START_LIQ_THRESHOLD // starting value and hard ceiling for recovery An individual token also retains `initialLiquidationThreshold` (set to `START_LIQ_THRESHOLD` at creation), which acts as the recovery ceiling. The threshold gradually climbs back toward `initialLiquidationThreshold` as market conditions improve, up to +1% per liquidation event. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#inspecting-the-current-threshold) Inspecting the current threshold Copy uint256 threshold = IFlapTaxTokenV3(token).liquidationThreshold(); uint256 initial = IFlapTaxTokenV3(token).initialLiquidationThreshold(); uint256 minLimit = IFlapTaxTokenV3(token).MIN_LIQ_THRESHOLD(); uint256 startLimit = IFlapTaxTokenV3(token).START_LIQ_THRESHOLD(); * * * [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#id-5.-deployed-contract-addresses) 5\. Deployed contract addresses --------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#flaptaxtokenv3-implementation-clone-base) `FlapTaxTokenV3` implementation (clone base) Use this address when computing vanity salts off-chain. Network Address **BNB Chain Mainnet** `0x024f18294970B5c76c0691b87f138A0317156422` **BNB Chain Testnet** `0xE6Ff967a887084c16D0fD71548CF709542cc1557` ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#example-launching-a-token_taxed_v3-token) Example: launching a `TOKEN_TAXED_V3` token Copy address tokenImplTaxedV3 = 0x024f18294970B5c76c0691b87f138A0317156422; // BSC mainnet bytes32 salt = _findVanitySalt(VanityType.VANITY_7777, tokenImplTaxedV3, address(portal)); IPortalTypes.NewTokenV6Params memory params = IPortalTypes.NewTokenV6Params({ name: "My Token", symbol: "MTK", meta: "ipfs://...", dexThresh: IPortalCommonTypes.DexThreshType.FOUR_FIFTHS, salt: salt, migratorType: IPortalTypes.MigratorType.V2_MIGRATOR, quoteToken: address(0), // BNB quoteAmt: 0, beneficiary: msg.sender, permitData: "", extensionID: bytes32(0), extensionData: "", dexId: IPortalTypes.DEXId.DEX0, lpFeeProfile: IPortalTypes.V3LPFeeProfile.LP_FEE_PROFILE_STANDARD, buyTaxRate: 300, // 3% buy tax sellTaxRate: 1000, // 10% sell tax (asymmetric) taxDuration: 365 days, antiFarmerDuration: 1 days, mktBps: 10000, // 100% of remainder → beneficiary deflationBps: 0, dividendBps: 0, lpBps: 0, minimumShareBalance: 0, dividendToken: address(0), // native → resolved to WETH commissionReceiver: 0xYourAddress, // enable commission tokenVersion: IPortalTypes.TokenVersion.TOKEN_TAXED_V3 }); address token = portal.newTokenV6(params); [PreviousTax Token V2](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2) [NextWallet & Terminal & Bot Developers](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers) Last updated 2 months ago --- # Bonding Curve | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve.md) . Since v2.4.0, Flap supports multiple bonding curves, but only one is active for a quote token. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve#what-is-a-bonding-curve) What is a Bonding Curve Before migrating to DEX, each token launched on flap is bonded to a bonding curve. When you buy tokens from the bonding curve, you send your ETH ( or other quote token: BNB, USDT etc) to the bonding curve as a reserve, and the bonding curve mints tokens to your address. Or if you sell your tokens to the bonding curve, the bonding curve will burn your selling tokens and send the ETH back to you. Under the hood, we don't implement it as burn & mint. Initially, all the tokens are in our bonding curve contract until the user buys 80% of the total supply and the remaining token will be added to DEX as liquidity. ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252FxYdFM0ADkLAeAIJo56Kh%252Fimage.png%3Falt%3Dmedia%26token%3Da0bf6998-b2e0-4a40-9c4d-bc568f96cfa7&width=768&dpr=3&quality=100&sign=64aebae7&sv=2) A bonding curve defines the relationship between the trading token's supply and the reserve (i.e. Quote tokens like ETH or BNB). The change of the reserve respect to the supply is the price. Our bonding curve is based on a constant product equation. You may have heard about this equation, which Uniswap popularized. You may even wonder what is the difference between a bonding curve token launching platform like flap and Unsiwap? The answer is the liquidity. The liquidity does not change on the bonding curve. However, anyone can add liquidity to the Uniswap pools. ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve#flaps-bonding-curve-v2) Flap's Bonding Curve V2 The token created on our platform has the same max supply of 10910^9109 , with 18 decimals. For each token, the amounts of token and the Quote (ETH, BNB or USD\*) follow the following constant product equation: (x+h)(y+r)\=K(x+ h)(y + r) = K(x+h)(y+r)\=K rrr ,hhh and KKK are constant parameters dependent on the target chain ( check [Bonding Curves On Different Chains](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve#bonding-curves-on-different-chains) for more details). (x+h)(y+r)\=K(x+ h)(y + r) = K(x+h)(y+r)\=K * xxx is the amount of token in the bonding curve (i.e: token reserve), initially, it is 10910^9109, which means all the tokens are in the bonding curve in the beginning. * yyy is the amount of quote token (i.e: quote reserve) , it is 0 in the beginning. * Our curve have 3 constant parameters: * rrr can be interpreted as the virtual reserve of quote token in the bonding curve * hhh can be interpreted as the virtual reserve of the launched token in the bonding curve * KKK is the square of the virtual liquidity ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve#bonding-curves-on-different-chains) Bonding Curves On Different Chains The following table may be out of date. We don't recommend that you hardcode the bonding curve parameters in your application. Instead, you should use `getTokenV5` method from the Portal contract to get the parameters for each token. The parameters are immutable for each token, so you only need to fetch them once and cache them in your application. For different and different quote token , the constants are different: Chain r h K BSC (Legacy before block #42042177 ) 15 0 15⋅10915 \\cdot 10^{9}15⋅109 BSC (Legacy since block 42042177 and before 47855189 ) 4 0 4⋅1094 \\cdot 10^{9}4⋅109 BSC (Legacy since block 47855189 and before [51314696](https://bscscan.com/block/51314696) ) 2 0 2⋅1092 \\cdot 10^{9}2⋅109 BSC(before 61837444, BNB as payment Token) 4 0 4⋅1094 \\cdot 10^{9}4⋅109 BSC(before 61837444, USD1/lisUSD as payment token) 2500 0 2500⋅1092500 \\cdot 10^{9}2500⋅109 BSC(lastest, BNB as payment Token) 6.14 107036752 6797205657.28 BSC(latest, USD1/lisUSD as payment token) 3837 107036752 4247700017424 ToshiMart 0.5 0 0.5⋅1090.5 \\cdot 10^{9}0.5⋅109 XLayer (Before block 31564005) 28 0 28⋅10928 \\cdot 10^{9}28⋅109 XLayer (Before block 32470187) 21.25 0 21.25⋅10921.25 \\cdot 10^{9}21.25⋅109 XLayer(Since block 32470187) 28.25 108002126 31301060059 Monad (mainnet) 50000 107036752 55351837600000 ### [](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve#example-curve-bnb) Example Curve BNB [PreviousBasic & Mechanism](https://docs.flap.sh/flap/developers/basic-and-mechanism) [NextMigrated To DEX](https://docs.flap.sh/flap/developers/basic-and-mechanism/list-on-dex) Last updated 5 months ago * [What is a Bonding Curve](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve#what-is-a-bonding-curve) * [Flap's Bonding Curve V2](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve#flaps-bonding-curve-v2) * [Bonding Curves On Different Chains](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve#bonding-curves-on-different-chains) * [Example Curve BNB](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve#example-curve-bnb) --- # Parse Token Meta | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta.md) . Whenever a token is launched, a `TokenCreated` event will be emitted: Copy /// @notice emitted when a new token is created /// /// @param ts The timestamp of the event /// @param creator The address of the creator /// @param nonce The nonce of the token /// @param token The address of the token /// @param name The name of the token /// @param symbol The symbol of the token /// @param meta The meta URI of the token event TokenCreated( uint256 ts, address creator, uint256 nonce, address token, string name, string symbol, string meta ); The `TokenCreated` event contains the IPFS CID of the token’s metadata, which can be used to retrieve the metadata from IPFS. Alternatively, you can also get the metadata through the token’s contract by calling the `metaURI` method of the token contract. The `meta` method returns the IPFS CID of the token’s metadata. Copy interface IFlapToken { function metaURI() external view returns (string memory); } Let’s take the JACK token as an example to illustrate how to get the image: If you call the `metaURI` method of the 0x4122a43daecd13cfb2543ad339470fd87bc11111 [token](https://x.flap.sh/0x4122a43daecd13cfb2543ad339470fd87bc11111) , you will get the following cid: `bafkreieepx7lh6zcpqsneo2jdeqmcgxjg7facam34w5ezh4myajjauuc24`. You can get the meta json from any ipfs gateway. However, it would be fast to use our ipfs gateway. Our gateway is located at [https://flap.mypinata.cloud/](https://flap.mypinata.cloud/) . Such that, you can get the meta json here: `https://flap.mypinata.cloud/ipfs/bafkreieepx7lh6zcpqsneo2jdeqmcgxjg7facam34w5ezh4myajjauuc24`. The meta json is as follows: Note that the image field is also a cid, which is the cid of the image. Similarly, we can get the image here: `https://flap.mypinata.cloud/ipfs/bafkreibwjuzzns6yf4wytfr5nk6vujb4yja4iejff2k76nshn2z5jkmiy4`. [PreviousInspect A Token](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token) [NextToken Version Specification](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification) Last updated 6 months ago Copy { "buy": null, "creator": "0x7F403F924dDE32bFd4D4432E3996291aeFCe2f89", "description": "OKIE,OKIE", "image": "bafkreibwjuzzns6yf4wytfr5nk6vujb4yja4iejff2k76nshn2z5jkmiy4", "sell": null, "telegram": null, "twitter": null, "website": null } --- # Index Token Created Events | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events.md) . This page describes how to index newly launched tokens by consuming events emitted by the `Portal` contract. [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#overview) Overview --------------------------------------------------------------------------------------------------------------------------------- For backward compatibility, a token launch can emit multiple events instead of a single one. Always index `TokenCreated`, then enrich the token record with any optional events that appear in the same transaction. `TokenCreated` only includes the IPFS CID of the metadata. To resolve and parse token metadata, see [Parse Token Meta](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta) . [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#events-to-index) Events to index ----------------------------------------------------------------------------------------------------------------------------------------------- **Required** * `TokenCreated`: emitted for every token launch. **Optional (apply defaults if missing)** * `TokenCurveSet`: if missing, curve defaults to the first item in `CurveType` (legacy curve, `curveParameter = 16 ether`). * `TokenCurveSetV2`: starting from v4.7.0, always emitted even for legacy curve. * `TokenDexSupplyThreshSet`: if missing, defaults to the first item in `DexThreshType` (6.67e8 ether). * `TokenQuoteSet`: if missing, defaults to native gas token (zero address). * `TokenMigratorSet`: if missing, defaults to `V3_MIGRATOR`. * `TokenVersionSet`: if missing, defaults to legacy token version (see [Token version specification](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification) ). * `FlapTokenTaxSet`: if missing, tax is 0 (non-tax token). * `FlapTokenStaged`: emitted when a token is staged but not yet created (two-step token launch). * `TokenExtensionEnabled`: emitted when an extension is enabled for a token. * `TokenDexPreferenceSet`: if missing, defaults to DEX0 with STANDARD fee profile. * `FlapTokenAsymmetricTaxSet`: emitted alongside `FlapTokenTaxSet` for Tax Token V3 launches; carries separate buy and sell tax rates. If missing, treat buy and sell rates as equal to the value from `FlapTokenTaxSet`. [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#event-reference-arguments-and-meaning) Event reference (arguments and meaning) --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokencreated) `TokenCreated` Emitted for every token launch. * `ts`: block timestamp when the token is created. * `creator`: address that initiated the token creation. * `nonce`: portal nonce for this creation (unique per `Portal`). * `token`: deployed token address. * `name`: token name. * `symbol`: token symbol. * `meta`: IPFS CID of the token metadata JSON. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#flaptokenstaged) `FlapTokenStaged` Emitted when a token is staged (two-step launch) but not yet created. * `ts`: block timestamp when staging happens. * `creator`: address that staged the token. * `token`: predetermined token address (not yet deployed). ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokencurveset) `TokenCurveSet` Emitted when the bonding curve configuration is set for legacy curve format. * `token`: token address. * `curve`: curve contract address. * `curveParameter`: curve parameter for the legacy curve (defaults to `16 ether` if missing). ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokencurvesetv2) `TokenCurveSetV2` Emitted when the bonding curve parameters are set in the newer format. * `token`: token address. * `r`: virtual ETH reserve parameter. * `h`: virtual token reserve parameter. * `k`: square of virtual liquidity parameter. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokendexsupplythreshset) `TokenDexSupplyThreshSet` Emitted when the DEX listing supply threshold is set. * `token`: token address. * `dexSupplyThresh`: circulating supply threshold for DEX listing (defaults to the first `DexThreshType` if missing). ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokenquoteset) `TokenQuoteSet` Emitted when the quote token is set. * `token`: token address. * `quoteToken`: quote token address (zero address means native gas token). ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokenmigratorset) `TokenMigratorSet` Emitted when the migrator type is set. * `token`: token address. * `migratorType`: migrator enum value (`V3_MIGRATOR` or `V2_MIGRATOR`). ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokenversionset) `TokenVersionSet` Emitted when the token implementation version is set. * `token`: token address. * `version`: token version enum value (see [Token version specification](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification) ). ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#flaptokentaxset) `FlapTokenTaxSet` Emitted when a tax is set for a token. * `token`: token address. * `tax`: tax rate in basis points (0 means non-tax token). ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokenextensionenabled) `TokenExtensionEnabled` Emitted when an extension is enabled for a token. * `token`: token address. * `extensionID`: extension identifier (bytes32). * `extensionAddress`: extension contract address. * `version`: extension interface version. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokendexpreferenceset) `TokenDexPreferenceSet` Emitted when DEX preference and fee profile are set. * `token`: token address. * `dexId`: preferred DEX ID (`DEX0`, `DEX1`, `DEX2`). * `lpFeeProfile`: preferred V3 LP fee profile (`STANDARD`, `LOW`, `HIGH`). ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#flaptokenasymmetrictaxset) `FlapTokenAsymmetricTaxSet` Emitted for Tax Token V3 (`TOKEN_TAXED_V3`) launches alongside `FlapTokenTaxSet`. Carries the full asymmetric buy and sell tax rates. * `token`: token address. * `buyTax`: buy tax rate in basis points. * `sellTax`: sell tax rate in basis points. `FlapTokenTaxSet` is always emitted for all tax tokens and carries `max(buyTax, sellTax)` for backward compatibility. `FlapTokenAsymmetricTaxSet` is only emitted for V3 tokens. Prefer `FlapTokenAsymmetricTaxSet` when available to get the full asymmetric rate detail. [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#suggested-indexing-flow) Suggested indexing flow --------------------------------------------------------------------------------------------------------------------------------------------------------------- 1. Listen to `TokenCreated` events on `Portal`. 2. In the same transaction, collect optional events for the same token address. 3. Apply defaults for any missing optional events. 4. Persist the token record and metadata CID. [PreviousDeployed Contract Addresses](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/deployed-contract-addresses) [NextBonding Curve In Developers' Perspective](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective) Last updated 3 months ago * [Overview](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#overview) * [Events to index](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#events-to-index) * [Event reference (arguments and meaning)](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#event-reference-arguments-and-meaning) * [TokenCreated](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokencreated) * [FlapTokenStaged](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#flaptokenstaged) * [TokenCurveSet](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokencurveset) * [TokenCurveSetV2](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokencurvesetv2) * [TokenDexSupplyThreshSet](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokendexsupplythreshset) * [TokenQuoteSet](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokenquoteset) * [TokenMigratorSet](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokenmigratorset) * [TokenVersionSet](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokenversionset) * [FlapTokenTaxSet](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#flaptokentaxset) * [TokenExtensionEnabled](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokenextensionenabled) * [TokenDexPreferenceSet](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#tokendexpreferenceset) * [FlapTokenAsymmetricTaxSet](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#flaptokenasymmetrictaxset) * [Suggested indexing flow](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events#suggested-indexing-flow) --- # Bonding Curve In Developers' Perspective | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective.md) . [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective#how-to-find-the-reserve-price-fdv-from-the-circulating-supply-of-a-token) How to find the reserve/price/fdv from the circulating supply of a token? ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective#option1-read-from-the-chain) Option1: Read from the Chain You can directly read the information about a token through the `getTokenV2` call to the on chain contract. Copy /// @notice Get token state (V5) /// @param token The address of the token /// @return state The state of the token (V5) with all curve parameters (r, h, k) function getTokenV5(address token) external view returns (TokenStateV5 memory state); /// @dev Token version /// Which token implementation is used enum TokenVersion { TOKEN_LEGACY_MINT_NO_PERMIT, TOKEN_LEGACY_MINT_NO_PERMIT_DUPLICATE, // for historical reasons, both 0 and 1 are the same: TOKEN_LEGACY_MINT_NO_PERMIT TOKEN_V2_PERMIT, TOKEN_GOPLUS } /// @notice the status of a token /// The token has 4 statuses: // - Tradable: The token can be traded(buy/sell) // - InDuel: (obsolete) The token is in a battle, it can only be bought but not sold. // - Killed: (obsolete) The token is killed, it can not be traded anymore. Can only be redeemed for another token. // - DEX: The token has been added to the DEX enum TokenStatus { Invalid, // The token does not exist Tradable, InDuel, // obsolete Killed, // obsolete DEX } /// @notice the state of a token (with all V4 fields plus all curve parameters) struct TokenStateV5 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; } ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective#option-2-derive-price-fdv-reserve-from-the-current-circulating-supply) Option 2: Derive price/fdv/reserve from the current circulating supply Check [Bonding Curves On Different Chains](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve#bonding-curves-on-different-chains) for the r of each deployment We don't recomend that you hardcode the bonding curve parameters in your application. Instead, you should use `getTokenV5` method from the Portal contract to get the parameters for each token. The parameters are immutable for each token, so you only need to fetch them once and cache them in your application. Whenever a user buys or sells token on the bonding curve, the following event would be emitted: You can simply index this event to get the latest circulating supply of any token. With the circulating supply of the token, you can get the reserve, price and FDV with the following `typescript` snippet: [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective#how-to-calculate-the-progress-of-a-token) How to calculate the progress of a token -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The progress is defined as the ratio of the current reserve to the required reserve for the token to migrate. You need the following information to calculate the progress: * `Circulating Supply`: The current circulating supply of the token. You can either index the `FlapTokenCirculatingSupplyChanged` event, or use the `getTokenV5` method to get the `circulatingSupply` of the token, or simply use the `balanceOf(portal)` to estimate the circulating supply (This estimation is accurate as long as no one directly sends token to our contract). * `Curve Parameters`: You can use the `getTokenV5` to get the curve parameters: `r`, `h` and `k`. Or you can index the `TokenCurveSetV2` event. The curve parameters are immutable for a token, you only need to get them once. * `Dex Threshold`: You can use the `getTokenV5` to get the `dexThreshold` of the token, or index the `TokenDexThresholdSet` event. The dex threshold is immutable, you only need to get it once. It represents the circulating supply at which the token will be migrated. The latter two variables are immutable for a token, you only need to get them once. We highly recommend that you to use the the `getTokenV5` method to get all the parameters at once. With all the above information, you can calculate the prorgress of the token: * We first construct a `Curve` instance using the curve parameters. Note the curve parameteres are in 18 decimals, so we need to divide them by `1e18` to get the actual values (or use formatEther from viem). * We first calculate the `required reserve` for the token to migrate * Then we derive the `current reserve` * The progress is the ratio betweeen `current reserve` and `required reserve` [PreviousIndex Token Created Events](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events) [NextInspect A Token](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token) Last updated 5 months ago * [How to find the reserve/price/fdv from the circulating supply of a token?](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective#how-to-find-the-reserve-price-fdv-from-the-circulating-supply-of-a-token) * [Option1: Read from the Chain](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective#option1-read-from-the-chain) * [Option 2: Derive price/fdv/reserve from the current circulating supply](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective#option-2-derive-price-fdv-reserve-from-the-current-circulating-supply) * [How to calculate the progress of a token](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective#how-to-calculate-the-progress-of-a-token) Copy /// @notice emitted when the circulating supply of a token changes /// @param token The address of the token /// @param newSupply The new circulating supply event FlapTokenCirculatingSupplyChanged(address token, uint256 newSupply); Copy import { Decimal } from "decimal.js"; const BILLION: Decimal = new Decimal("1000000000"); // The latest curve is CDPV2 export class CDPV2 { // the initial virtual reserve private r: number; private h: number; private k: number; static defaultDexSupplyThreshold() { return new Decimal(8e8); } static getCurve(r: number, h?: number, k?: number): CDPV2 { if (h == null) { return new CDPV2(r, 0, 1e9 * r); } return new CDPV2(r, h, k); } constructor(r: number, h: number = 0, k: number = 0) { this.r = r; this.h = h; this.k = k; } estimateSupply(reserve: string): Decimal { // s = 1e9 + h - k/(r + eth) if (!reserve) return new Decimal(0); return new Decimal(BILLION).add(this.h).sub( new Decimal(this.k).div(new Decimal(reserve).add(this.r)) ); } estimateReserve(amount: string): Decimal { // eth = k/(h + 1e9 - s) - r if (!amount) return new Decimal(0); return new Decimal(this.k) .div(new Decimal(BILLION).add(this.h).sub(new Decimal(amount))) .sub(this.r); } mc(reserve: string): Decimal { return this.fdv(this.totalSupply(reserve).toString()); } price(supply: string): Decimal { // Price: k/(h + 1e9 - s)^2 const denominator = new Decimal(BILLION).add(this.h).sub(new Decimal(supply || 0)); return new Decimal(this.k).div(denominator.pow(2)); } fdv(supply: string): Decimal { return this.price(supply).mul(new Decimal(BILLION)); } } Copy // assume we have all the following parameters from getTokenV5 // Note: all parameters are in 18 decimals // curve parameters let r,h,k: bigint; // circulating supply let circulatingSupply: bigint; // dex threshold let dexThreshold: bigint; const curve = CDPV2.getCurve( Number( formatEther(r) ), Number( formatEther(h) ), Number( formatEther(k) ) ); // The reserve required at the dex threshold const reserveRequired = curve.estimateReserve( formatEther(dexThreshold) ); // The current reserve at the current circulating supply const currentReserve = curve.estimateReserve( formatEther(circulatingSupply) ); // The progress can be calculated as: const progress = currentReserve / reserveRequired; --- # Inspect A Token | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token.md) . [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token#inspect-a-token) Inspect A Token ------------------------------------------------------------------------------------------------------------------------------------ We have multiple methods for querying the state of a token. The latest ones are `getTokenV8` and `getTokenV8Safe` (currently only available on BNB mainnet and BNB testnet). For other networks, use `getTokenV7`. To avoid the broken code problem, we still reserve the legacy methods `getTokenV2` and `getTokenV3`. We always recommend using the newest available method for your network. Copy /// @notice the state of a token (with dex related fields) struct TokenStateV2 { TokenStatus status; // the status of the token uint256 reserve; // the reserve of the token uint256 circulatingSupply; // the circulatingSupply of the token uint256 price; // the price of the token TokenVersion tokenVersion; // the version of the token implementation this token is using uint256 r; // the r of the curve of the token uint256 dexSupplyThresh; // the cirtulating supply threshold for adding the token to the DEX } /// @notice the state of a token (with all V2 fields plus quoteTokenAddress and nativeToQuoteSwapEnabled) struct TokenStateV3 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; } /// @notice the state of a token (with all V3 fields plus extensionID and 'r' curve parameter only) struct TokenStateV4 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; } /// @notice the state of a token (with all V4 fields plus all curve parameters) struct TokenStateV5 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; } /// @notice the state of a token (with all V5 fields plus taxRate, pool, and progress) struct TokenStateV6 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The tax rate in basis points (0 if not a tax token) uint256 taxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; } /// @notice the state of a token (with all V6 fields plus lpFeeProfile) struct TokenStateV7 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The tax rate in basis points (0 if not a tax token) uint256 taxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; /// The V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; /// The Dex Id DEXId dexId; } struct TokenStateV8 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The buy tax rate in basis points (0 if not a tax token) uint256 buyTaxRate; /// The sell tax rate in basis points (0 if not a tax token) uint256 sellTaxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; /// The V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; /// The Dex Id DEXId dexId; } /// @notice A forward-compatible version of TokenStateV8, where enum-typed fields are returned /// as uint8 instead of their Solidity enum types. struct TokenStateV8Safe { uint8 status; uint256 reserve; uint256 circulatingSupply; uint256 price; uint8 tokenVersion; uint256 r; uint256 h; uint256 k; uint256 dexSupplyThresh; address quoteTokenAddress; bool nativeToQuoteSwapEnabled; bytes32 extensionID; uint256 buyTaxRate; uint256 sellTaxRate; address pool; uint256 progress; uint8 lpFeeProfile; uint8 dexId; } /// @notice Get token state /// @param token The address of the token /// @return state The state of the token function getTokenV2(address token) external view returns (TokenStateV2 memory state); /// @notice Get token state (V3) /// @param token The address of the token /// @return state The state of the token (V3) function getTokenV3(address token) external view returns (TokenStateV3 memory state); /// @notice Get token state (V4) /// @param token The address of the token /// @return state The state of the token (V4) with only 'r' curve parameter function getTokenV4(address token) external view returns (TokenStateV4 memory state); /// @notice Get token state (V5) /// @param token The address of the token /// @return state The state of the token (V5) with all curve parameters (r, h, k) function getTokenV5(address token) external view returns (TokenStateV5 memory state); /// @notice Get token state (V6) /// @param token The address of the token /// @return state The state of the token (V6) with all V5 fields plus taxRate, pool, and progress function getTokenV6(address token) external view returns (TokenStateV6 memory state); /// @notice Get token state (V7) /// @param token The address of the token /// @return state The state of the token (V7) with all V6 fields plus lpFeeProfile function getTokenV7(address token) external view returns (TokenStateV7 memory state); /// @notice Get token state (V8) /// @param token The address of the token /// @return state The state of the token (V8) with asymmetric buyTaxRate and sellTaxRate function getTokenV8(address token) external view returns (TokenStateV8 memory state); /// @notice Get token state (V8Safe) /// @dev Returns enum-typed fields (TokenStatus, TokenVersion, V3LPFeeProfile, DEXId) as uint8 /// instead of their Solidity enum types, preventing ABI-decoding reverts when new enum /// variants are introduced. Use this method when you need forward/backward compatibility /// with future contract upgrades that may add new enum values. /// @param token The address of the token /// @return state The state of the token with enum fields encoded as uint8 function getTokenV8Safe(address token) external view returns (TokenStateV8Safe memory state); /// @dev Token version /// Which token implementation is used enum TokenVersion { TOKEN_LEGACY_MINT_NO_PERMIT, TOKEN_LEGACY_MINT_NO_PERMIT_DUPLICATE, // for historical reasons, both 0 and 1 are the same: TOKEN_LEGACY_MINT_NO_PERMIT TOKEN_V2_PERMIT, // 2 TOKEN_GOPLUS, // 3 TOKEN_TAXED, // 4: The original tax token (FlapTaxToken) TOKEN_TAXED_V2 // 5: The new advanced tax token (FlapTaxTokenV2) } /// @dev the quote token, i.e, the token as the reserve enum QuoteTokenType { NATIVE_GAS_TOKEN, // The native gas token ERC20_TOKEN_WITH_PERMIT, // The ERC20 token with permit ERC20_TOKEN_WITHOUT_PERMIT // The ERC20 token without permit } /// @notice the status of a token /// The token has 5 statuses: // - Tradable: The token can be traded(buy/sell) // - InDuel: (obsolete) The token is in a battle, it can only be bought but not sold. // - Killed: (obsolete) The token is killed, it can not be traded anymore. Can only be redeemed for another token. // - DEX: The token has been added to the DEX // - Staged: The token is staged but not yet created (address is predetermined) enum TokenStatus { Invalid, // The token does not exist Tradable, InDuel, // obsolete Killed, // obsolete DEX, Staged // The token is staged (address determined, but not yet created) } /// @notice the V3 LP fee profile /// @dev determines the LP fee tier to use when migrating tokens to Uniswap V3 or Pancake V3 enum V3LPFeeProfile { LP_FEE_PROFILE_STANDARD, // Standard fee tier: 0.25% on PancakeSwap, 0.3% on Uniswap LP_FEE_PROFILE_LOW, // Low fee tier: typically, 0.01% on PancakeSwap, 0.05% on Uniswap LP_FEE_PROFILE_HIGH // High fee tier (1% for exotic pairs) } /// @notice the DEX ID /// @dev determines the DEX we want to migrate to /// On BSC: /// - only DEX0 will be enabled, which is PancakeSwap /// On xLayer: /// - only DEX0 will be enabled, which is PotatoSwap /// On Monad: /// - DEX0 is Uniswap /// - DEX1 is PancakeSwap /// - DEX2 is Monday /// Note that, currently, we only support at most 3 DEXes /// We may add more DEXes in the future if needed enum DEXId { DEX0, DEX1, DEX2 } [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token#what-is-the-difference-between-gettokenv2-gettokenv3-gettokenv4-gettokenv5-gettokenv6-gettokenv7-get) What is the difference between getTokenV2, getTokenV3, getTokenV4, getTokenV5, getTokenV6, getTokenV7, getTokenV8 and getTokenV8Safe? ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Each version of the `getToken` method returns progressively more information about a token's state. The protocol maintains backward compatibility by preserving all legacy methods while recommending the latest available version for new integrations. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token#version-comparison-table) Version Comparison Table Version New Fields Added Use Case **V2** Base fields only Legacy - basic token state **V3** `quoteTokenAddress`, `nativeToQuoteSwapEnabled` Multi-quote token support **V4** `extensionID` Extension/plugin support (e.g., Farcaster) **V5** `h`, `k` Complete bonding curve parameters **V6** `taxRate`, `pool`, `progress` Tax tokens, DEX pool info, progress tracking **V7** `lpFeeProfile`, `dexId` Multi-DEX support with custom fee tiers **V8** `buyTaxRate`, `sellTaxRate` (replaces `taxRate`) Asymmetric tax rates for Tax Token V3 **V8Safe** Same as V8, but enum fields returned as `uint8` Forward-compatible; safe against new enum values ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token#recommendation) Recommendation Use `getTokenV8` / `getTokenV8Safe` on BNB mainnet and BNB testnet. For other networks, use `getTokenV7`. `getTokenV8Safe` is recommended when you need forward/backward compatibility with future contract upgrades that may add new enum variants. `getTokenV5` is supported on all mainnets (BSC, xLayer, Monad, Morph). `V6` is available on all mainnets except Morph, while `V7` is available on all mainnets except Morph and Base. `getTokenV8` and `getTokenV8Safe` are currently only available on BNB mainnet and BNB testnet. We will gradually make `getTokenV8` and `getTokenV8Safe` available on other mainnets in the future. [PreviousBonding Curve In Developers' Perspective](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective) [NextParse Token Meta](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta) Last updated 3 months ago * [Inspect A Token](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token#inspect-a-token) * [What is the difference between getTokenV2, getTokenV3, getTokenV4, getTokenV5, getTokenV6, getTokenV7, getTokenV8 and getTokenV8Safe?](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token#what-is-the-difference-between-gettokenv2-gettokenv3-gettokenv4-gettokenv5-gettokenv6-gettokenv7-get) * [Version Comparison Table](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token#version-comparison-table) * [Recommendation](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token#recommendation) --- # Media Kit | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/media-kit.md) . 1MB [Media Kit.zip](https://2671086575-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5KujUBwRoHVjn8OZEgtZ%2Fuploads%2FXr4eQG72byr4voALZ4Ye%2FMedia%20Kit.zip?alt=media&token=0e8ed0f0-fa41-4bd4-a9b4-f587fea71d10) archive Download[Open](https://2671086575-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5KujUBwRoHVjn8OZEgtZ%2Fuploads%2FXr4eQG72byr4voALZ4Ye%2FMedia%20Kit.zip?alt=media&token=0e8ed0f0-fa41-4bd4-a9b4-f587fea71d10) [PreviousAudit Reports](https://docs.flap.sh/flap/audit-reports) [NextOpenClaw Skills](https://docs.flap.sh/flap/skills) Last updated 5 months ago --- # Deployed Contract Addresses | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/token-launcher-developers/deployed-contract-addresses.md) . See [Deployed Contract Addresses](https://docs.flap.sh/flap/developers/deployed-contract-addresses) . [PreviousQuick start for token launcher developers](https://docs.flap.sh/flap/developers/token-launcher-developers/quick-start-token-launcher-developers) [NextLaunch token through Portal](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal) Last updated 3 months ago --- # Token Launcher Developers | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/token-launcher-developers.md) . [Quick start for token launcher developers](https://docs.flap.sh/flap/developers/token-launcher-developers/quick-start-token-launcher-developers) [Deployed Contract Addresses](https://docs.flap.sh/flap/developers/token-launcher-developers/deployed-contract-addresses) [Launch token through Portal](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal) [Launch token through VaultPortal](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal) [Registered vaults](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults) [PreviousToken Migration](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-migration) [NextQuick start for token launcher developers](https://docs.flap.sh/flap/developers/token-launcher-developers/quick-start-token-launcher-developers) --- # Quick start for token launcher developers | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/token-launcher-developers/quick-start-token-launcher-developers.md) . This page outlines the minimal steps to integrate with Flap for token launcher developers. Each step links to the detailed documentation. 1. **Find the** `**Portal**` **entrypoint.** Use the deployed addresses listed in [Deployed Contract Addresses](https://docs.flap.sh/flap/developers/token-launcher-developers/deployed-contract-addresses) . 2. **Launch a token through** `**Portal**`**.** `Portal` is the core protocol for launching standard tokens and tax tokens. See [Portal vs VaultPortal](https://docs.flap.sh/flap/developers/basic-and-mechanism/portal-vs-vaultportal) for the conceptual difference, then follow [Launch token through Portal](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal) for the implementation details. 3. **Launch a token through** `**VaultPortal**` **(tax tokens with vaults).** `VaultPortal` builds on top of `Portal` to add Vault functionality for tax tokens. Follow [Launch token through VaultPortal](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal) . Each vault factory defines its own vault data schema—see [Registered vaults](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults) before encoding `vaultData`. [PreviousToken Launcher Developers](https://docs.flap.sh/flap/developers/token-launcher-developers) [NextDeployed Contract Addresses](https://docs.flap.sh/flap/developers/token-launcher-developers/deployed-contract-addresses) Last updated 5 months ago --- # OpenClaw Skills | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/skills.md) . Flap's published skills are available on the ClawHub registry. You can invoke them directly from any OpenClaw-compatible agent. * * * [](https://docs.flap.sh/flap/skills#launch-a-bnb-token-on-flap) Launch a BNB Token on Flap ----------------------------------------------------------------------------------------------- **Registry page:** [clawhub.ai/flapguy/launch-bnb-token-on-flap](https://clawhub.ai/flapguy/launch-bnb-token-on-flap) This skill walks an AI assistant through every step required to launch a token on Flap (BNB Chain): uploading metadata to IPFS, mining a vanity salt, encoding calldata, and broadcasting the transaction. ### [](https://docs.flap.sh/flap/skills#how-to-use) How to use Give your OpenClaw-enabled assistant a prompt like one of the examples below. The skill handles the rest. * * * ### [](https://docs.flap.sh/flap/skills#example-1-tax-token-gifted-to-an-x-account-gift-vault) Example 1 — Tax token gifted to an X account (Gift Vault) Launch a tax token whose trading fees are gifted to the X account `@elonmusk` using the **Gift Vault**. No vault factory address is needed — just refer to it by name. Copy Launch a tax token on Flap BNB called "Elon's Rocket" with symbol ROKYT. Use the Gift Vault and set the gift owner to the X account @elonmusk. Set buy tax and sell tax both to 5%. Spend 0.00001 BNB on the initial buy. * * * ### [](https://docs.flap.sh/flap/skills#example-2-tax-token-with-a-custom-vault-factory) Example 2 — Tax token with a custom vault factory Launch a tax token and route its fees through a specific vault factory you have already deployed. * * * ### [](https://docs.flap.sh/flap/skills#example-3-tax-token-without-a-vault) Example 3 — Tax token without a vault Launch a straightforward tax token with no vault. Fees go directly to a beneficiary address. * * * ### [](https://docs.flap.sh/flap/skills#example-4-non-tax-token) Example 4 — Non-tax token Launch a basic token with no taxes at all. This is the simplest type of token. [PreviousMedia Kit](https://docs.flap.sh/flap/media-kit) Last updated 3 months ago * [Launch a BNB Token on Flap](https://docs.flap.sh/flap/skills#launch-a-bnb-token-on-flap) * [How to use](https://docs.flap.sh/flap/skills#how-to-use) * [Example 1 — Tax token gifted to an X account (Gift Vault)](https://docs.flap.sh/flap/skills#example-1-tax-token-gifted-to-an-x-account-gift-vault) * [Example 2 — Tax token with a custom vault factory](https://docs.flap.sh/flap/skills#example-2-tax-token-with-a-custom-vault-factory) * [Example 3 — Tax token without a vault](https://docs.flap.sh/flap/skills#example-3-tax-token-without-a-vault) * [Example 4 — Non-tax token](https://docs.flap.sh/flap/skills#example-4-non-tax-token) Copy Launch a tax token on Flap BNB called "Alpha Fund" with symbol ALFX. Use the vault factory at address 0xAbCd1234AbCd1234AbCd1234AbCd1234AbCd1234 to set up the vault. Set buy tax to 3% and sell tax to 3%. Skip the initial buy. Copy Launch a tax token on Flap BNB called "Simple Tax" with symbol SMTX. No vault. Set buy tax to 0% and sell tax to 3%. Set the beneficiary to 0xYourBeneficiaryAddressHere. Spend 0.05 BNB on the initial buy. Copy Launch a non-tax token on Flap BNB called "Pure Coin" with symbol PURE. Spend 0.01 BNB on the initial buy. --- # Token Migration | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-migration.md) . When a token's circulating supply reaches its dexSupplyThresh, it will be listed on a DEX, and a new pool will be created for the token. The event `LaunchedToDEX` is emitted to indicate that the token is listed on DEX. Copy /// @notice emitted when adding liquidity to DEX /// @param token The address of the token /// @param pool The address of the pool /// @param amount The amount of token added /// @param eth The amount of quote Token added event LaunchedToDEX(address token, address pool, uint256 amount, uint256 eth); [PreviousTrade Tokens](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens) [NextToken Launcher Developers](https://docs.flap.sh/flap/developers/token-launcher-developers) Last updated 9 months ago --- # Vault Developers | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/vault-developers.md) . [Quick start for Vault Developers](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers) [Deployed Contract Addresses](https://docs.flap.sh/flap/developers/vault-developers/deployed-contract-addresses) [Vault & VaultFactory Specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification) [Flap Tax Vault](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault) [Gift Vault (FlapXVault)](https://docs.flap.sh/flap/developers/vault-developers/gift-vault) [Gift Vault V2](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2) [Building Upgradeable Vaults — Beacon Proxy Pattern](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault) [Tutorials](https://docs.flap.sh/flap/developers/vault-developers/tutorials) [PreviousRegistered vaults](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults) [NextQuick start for Vault Developers](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers) --- # Deployed Contract Addresses | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/vault-developers/deployed-contract-addresses.md) . See [Deployed Contract Addresses](https://docs.flap.sh/flap/developers/deployed-contract-addresses) . [PreviousQuick start for Vault Developers](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers) [NextVault & VaultFactory Specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification) Last updated 3 months ago --- # Token Version Specification | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification.md) . ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification#overview) Overview The `TokenVersion` field is an enum that indicates which token implementation is being used for a specific token. This field is crucial for determining the capabilities and features available for each token launched through the Portal contract. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification#tokenversion-enum-values) TokenVersion Enum Values Copy /// @dev Token version /// Which token implementation is used enum TokenVersion { TOKEN_LEGACY_MINT_NO_PERMIT, TOKEN_LEGACY_MINT_NO_PERMIT_DUPLICATE, // for historical reasons, both 0 and 1 are the same: TOKEN_LEGACY_MINT_NO_PERMIT TOKEN_V2_PERMIT, // 2 TOKEN_GOPLUS, // 3 TOKEN_TAXED, // 4: The original tax token (FlapTaxToken) TOKEN_TAXED_V2, // 5: The new advanced tax token (FlapTaxTokenV2) TOKEN_TAXED_V3, // 6: The next-generation tax token with asymmetric buy/sell rates (FlapTaxTokenV3) TOKEN_V3_PERMIT // 7: Non-tax token using TokenV3 implementation with permit support } The `TokenVersion` has the following values: Value Name Description 0 `TOKEN_LEGACY_MINT_NO_PERMIT` Legacy token implementation without permit functionality 1 `TOKEN_LEGACY_MINT_NO_PERMIT_DUPLICATE` Historical duplicate (identical to value 0) 2 `TOKEN_V2_PERMIT` V2 token implementation with EIP-2612 permit support 3 `TOKEN_GOPLUS` Token implementation with GoPlus security integration 4 `TOKEN_TAXED` Original tax token implementation (FlapTaxToken) 5 `TOKEN_TAXED_V2` Advanced tax token implementation (FlapTaxTokenV2) 6 `TOKEN_TAXED_V3` Next-generation tax token with asymmetric buy/sell rates (FlapTaxTokenV3) 7 `TOKEN_V3_PERMIT` Non-tax TokenV3 implementation with permit support, designed for PancakeSwap Infinity CL or Uniswap v4 style integrations #### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification#version-details) Version Details * **TOKEN\_LEGACY\_MINT\_NO\_PERMIT (0 & 1)**: The obsolete token implementation. Due to historical reasons, both values 0 and 1 represent the same implementation type. * **TOKEN\_V2\_PERMIT (2)**: Enhanced token with permit functionality allowing gasless approvals via signed messages (EIP-2612 standard). * **TOKEN\_GOPLUS (3)**: Token integrated with GoPlus security features for enhanced safety and verification. (Not Used in current deployments) * **TOKEN\_TAXED (4)**: The first generation tax token (`FlapTaxToken`) that implements tax mechanisms on transfers. * **TOKEN\_TAXED\_V2 (5)**: The second generation tax token (`FlapTaxTokenV2`) with advanced tax features and improvements over the original tax token. * **TOKEN\_TAXED\_V3 (6)**: The third generation tax token (`FlapTaxTokenV3`) with asymmetric buy/sell tax rates, commission receiver support, and bidirectional dynamic liquidation threshold. Launched via `newTokenV6`. This is the recommended token type for all new integrations. * **TOKEN\_V3\_PERMIT (7)**: A zero-tax token (`taxRate = 0`) using the TokenV3 implementation with permit support. This version is intended for launches that combine with PancakeSwap Infinity CL or Uniswap v4 style liquidity flows, and is created via `newTokenV7`. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification#how-to-get-token-version) How to Get Token Version There are two primary methods to retrieve the token version for a specific token: #### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification#method-1-index-from-events) Method 1: Index from Events The protocol emits a `TokenVersionSet` event whenever a token's version is set or updated. You can listen to this event or query historical events to determine a token's version. **Event Definition:** **Example Usage:** * Listen for `TokenVersionSet` events on the Portal contract * Filter events by the token address * The `version` parameter contains the `TokenVersion` enum value #### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification#method-2-using-gettokenv-methods) Method 2: Using getTokenV\* Methods The Portal contract provides multiple view functions to query token state, each returning progressively more fields. All of these methods include the `tokenVersion` field in their return values. **Available Methods:** 1. **getTokenV5** - Returns `TokenStateV5` structure 2. **getTokenV6** - Returns `TokenStateV6` structure 3. **getTokenV7** - Returns `TokenStateV7` structure 4. **getTokenV8** - Returns `TokenStateV8` structure with asymmetric `buyTaxRate` and `sellTaxRate` (BNB mainnet & testnet only) 5. **getTokenV8Safe** - Returns `TokenStateV8Safe` with enum fields as `uint8` for forward compatibility (BNB mainnet & testnet only) **All TokenState structures include:** * `TokenVersion tokenVersion` - The version of the token implementation **Example Usage:** [PreviousParse Token Meta](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta) [NextInspect A Tax Token](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token) Last updated 1 month ago * [Overview](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification#overview) * [TokenVersion Enum Values](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification#tokenversion-enum-values) * [How to Get Token Version](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification#how-to-get-token-version) Copy event TokenVersionSet(address token, TokenVersion version); Copy function getTokenV5(address token) external view returns (TokenStateV5 memory state); Copy function getTokenV6(address token) external view returns (TokenStateV6 memory state); Copy function getTokenV7(address token) external view returns (TokenStateV7 memory state); Copy function getTokenV8(address token) external view returns (TokenStateV8 memory state); Copy function getTokenV8Safe(address token) external view returns (TokenStateV8Safe memory state); Copy // Using getTokenV8 on BNB mainnet/testnet (recommended — includes asymmetric tax rates) TokenStateV8 memory state = portal.getTokenV8(tokenAddress); TokenVersion version = state.tokenVersion; uint256 buyTax = state.buyTaxRate; uint256 sellTax = state.sellTaxRate; // Using getTokenV7 on other networks TokenStateV7 memory state = portal.getTokenV7(tokenAddress); TokenVersion version = state.tokenVersion; --- # Quick start for Vault Developers | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers.md) . Welcome to the Vault Developers guide! This section will help you get started with developing and integrating with Flap's vault system. [](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers#overview) Overview ------------------------------------------------------------------------------------------------------------- Flap's vault system allows you to build a smart contract for managing and distributing BNB revenue generated from tax tokens. Vaults can be customized to fit various use cases, such as splitting revenue among multiple recipients or routing funds based on social proof, or funding a game treasury. The only limitation is your creativity! **Permissionless vault creation** — You can now create and deploy your own vaults and vault factories **without any permission or registration**. Vault factories no longer need to be registered in the VaultPortal before they can be used to launch tokens. Work through the [FlapVaultExample repository](https://github.com/flap-sh/FlapVaultExample) for a complete working example of a vault and vault factory implementation. It is the fastest way to understand the patterns you need to follow. Once you have written your vault, use the [FlapVaultSpecChecker](https://github.com/flap-sh/FlapVaultSpecChecker) AI toolkit to automatically verify that your implementation correctly follows the specification before deploying. [](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers#available-vault-types) Available vault types --------------------------------------------------------------------------------------------------------------------------------------- Flap currently supports the following official vault types: 1. **Split Vault** - Distributes BNB revenue across multiple recipients based on configurable basis points (percentage shares) 2. **Gift Vault (FlapXVault)** - Routes revenue based on X (Twitter) proof verification with fallback to snowball buyback [](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers#next-steps) Next steps ----------------------------------------------------------------------------------------------------------------- * Review the [Vault & VaultFactory specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification) to understand how to build your own vaults and vault factories * Read the [Flap Tax Vault](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault) documentation for an overview of the vault concept and VaultPortal integration * Explore registered (i.e, verified by Flap) vaults in the [Registered Vaults](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults) page to see available vault factories and their configuration schemas * Learn about the [Gift Vault (FlapXVault)](https://docs.flap.sh/flap/developers/vault-developers/gift-vault) and how to integrate X proof-based revenue routing [](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers#common-workflows) Common workflows ----------------------------------------------------------------------------------------------------------------------------- 1. **Launch a token with a custom vault** — You can launch a token and set the fund recipient wallet to your smart contract. If it follows the spec defined in the [Vault & VaultFactory specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification) , its info will be automatically available on our website and partner websites. No registration is required. 2. **Build a vault factory for others** — If you have an idea to build a vault to be used by others, you can build a `VaultFactoryBaseV2` and take a portion of the tax as a commission in each vault you create. Implement `vaultDataSchema()` so the UI can automatically render a launch form for your vault type. See the [Vault & VaultFactory specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification) for full details. [PreviousVault Developers](https://docs.flap.sh/flap/developers/vault-developers) [NextDeployed Contract Addresses](https://docs.flap.sh/flap/developers/vault-developers/deployed-contract-addresses) Last updated 4 months ago * [Overview](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers#overview) * [Available vault types](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers#available-vault-types) * [Next steps](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers#next-steps) * [Common workflows](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers#common-workflows) --- # Audit Reports | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/audit-reports.md) . * The Flap Launchpad Protocol V2, V4 and Flap Tax Token V1 were audited by Certik. Check the audit report on [Certik Skynet](https://skynet.certik.com/projects/flap). * Flap Tax Token V1 (Part of the V4 Protocol) passed a comprehensive security audit by BlockSec. * Flap Launchpad Protocol V5 (including Flap Tax Token V2 and Flap PreLaunch V1) passed a comprehensive security audit by BlockSec. 2MB [blocksec\_flap\_tax\_token\_v1.0-signed.pdf](https://2671086575-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5KujUBwRoHVjn8OZEgtZ%2Fuploads%2FkhrpkMsQlhcrM9L5QDLS%2Fblocksec_flap_tax_token_v1.0-signed.pdf?alt=media&token=18ad3b37-e321-4200-8b26-e3a1957a9744) PDF Download[Open](https://2671086575-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5KujUBwRoHVjn8OZEgtZ%2Fuploads%2FkhrpkMsQlhcrM9L5QDLS%2Fblocksec_flap_tax_token_v1.0-signed.pdf?alt=media&token=18ad3b37-e321-4200-8b26-e3a1957a9744) 2MB [blocksec\_flap\_protocol\_v5\_v1.0-signed.pdf](https://2671086575-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5KujUBwRoHVjn8OZEgtZ%2Fuploads%2FMLLrgVU4RSejW0xh7jLH%2Fblocksec_flap_protocol_v5_v1.0-signed.pdf?alt=media&token=99af0d87-ac74-408c-9ade-2d9b1c4a5a8e) PDF Download[Open](https://2671086575-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5KujUBwRoHVjn8OZEgtZ%2Fuploads%2FMLLrgVU4RSejW0xh7jLH%2Fblocksec_flap_protocol_v5_v1.0-signed.pdf?alt=media&token=99af0d87-ac74-408c-9ade-2d9b1c4a5a8e) [PreviousWorld Cup Viewer](https://docs.flap.sh/flap/developers/preview/worldcup-viewer) [NextMedia Kit](https://docs.flap.sh/flap/media-kit) Last updated 5 months ago --- # Preview | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/preview.md) . This section contains previews of upcoming products and features by Flap. Content here is subject to change before final release. [PreviousUsing an LP Token or Child Token as the Dividend Token](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend) [NextBeta: PVE Curve](https://docs.flap.sh/flap/developers/preview/beta-pve-curve) Last updated 4 months ago --- # Registered vaults | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults.md) . [](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#registered-vaults) Registered vaults ----------------------------------------------------------------------------------------------------------------------------- This page lists registered vault factories and the expected `vaultData` schema for each factory. ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#bnb-mainnet) BNB mainnet name description is official (yes/no) vaultFactory address vaultData schema description interface Split Vault Splits received BNB across up to 10 recipients based on basis points and allows dispatch/claim. yes 0xfab75Dc774cB9B38b91749B8833360B46a52345F ABI-encode `SplitVault.Recipient[]` where each item is `{address recipient, uint16 bps}`. `recipient` is the payout address (must be non-zero and unique across the array). `bps` is the share in basis points (1% = 100, 100% = 10,000). The array length must be 1–10 and total bps must sum to 10,000. [misc/interfaces/vaults/SplitVault.sol](https://github.com/flap-sh/gitbook/blob/main/developers/token-launcher-developers/misc/interfaces/vaults/SplitVault.sol) Gift Vault (FlapXVault) A gift vault that routes tax token revenue based on X (Twitter) proof and can fall back to snowball buyback. yes 0x025549F52B03cF36f9e1a337c02d3AA7Af66ab32 ABI-encode `VaultData` with `{string xHandle}` (the fee manager’s X handle). The `xHandle` must be all lowercase. [Gift Vault Guide](https://docs.flap.sh/flap/developers/vault-developers/gift-vault) ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#bnb-testnet) BNB testnet name description is official (yes/no) vaultFactory address vaultData schema description interface Split Vault Splits received BNB across up to 10 recipients based on basis points and allows dispatch/claim. yes 0x1ae091F75D593eb7dC6539600a185C8A6076A424 ABI-encode `SplitVault.Recipient[]` where each item is `{address recipient, uint16 bps}`. `recipient` is the payout address (must be non-zero and unique across the array). `bps` is the share in basis points (1% = 100, 100% = 10,000). The array length must be 1–10 and total bps must sum to 10,000. [misc/interfaces/vaults/SplitVault.sol](https://github.com/flap-sh/gitbook/blob/main/developers/token-launcher-developers/misc/interfaces/vaults/SplitVault.sol) Gift Vault (FlapXVault) A gift vault that routes tax token revenue based on X (Twitter) proof and can fall back to snowball buyback. yes 0xa02DA44D67DB6D692efa7f751b5952bd670d5326 ABI-encode `VaultData` with `{string xHandle}` (the fee manager’s X handle). The `xHandle` must be all lowercase. [Gift Vault Guide](https://docs.flap.sh/flap/developers/vault-developers/gift-vault) [](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#interfaces) Interfaces --------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#split-vault-interface) Split Vault interface Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @title ISplitVault /// @notice Interface for the Split Vault implementation. /// @dev Derived from the on-chain SplitVault contract. interface ISplitVault { /// @notice A payout recipient with a basis-point share. /// @param recipient The payout address (non-zero and unique). /// @param bps Share in basis points (1% = 100, 100% = 10,000). struct Recipient { address recipient; uint16 bps; } /// @notice Balance tracking for a recipient. /// @param accumulated Total BNB credited to the user. /// @param claimed Total BNB claimed by the user. struct UserBalance { uint128 accumulated; uint128 claimed; } /// @notice View structure returned by `getRecipientsInfo()`. /// @param recipient The payout address. /// @param bps Share in basis points. /// @param accumulated Total BNB credited to the user. /// @param claimed Total BNB claimed by the user. struct RecipientInfo { address recipient; uint16 bps; uint128 accumulated; uint128 claimed; } /// @notice Emitted when BNB is received and split across recipients. /// @param amount The total amount distributed. event FlapSplitVaultDistributed(uint256 amount); /// @notice Emitted when a recipient is dispatched funds. /// @param recipient The recipient address. /// @param amount The amount dispatched. event FlapSplitVaultDispatched(address recipient, uint256 amount); /// @notice Emitted when a recipient claims funds manually. /// @param user The recipient address. /// @param amount The amount claimed. event FlapSplitVaultClaimed(address user, uint256 amount); /// @notice Error when number of recipients exceeds the limit. error TooManyRecipients(); /// @notice Error when the total basis points is not 10,000. error InvalidBpsSum(); /// @notice Error when a recipient address is zero. error ZeroRecipient(); /// @notice Error when no recipients are provided. error NoRecipients(); /// @notice Error when recipients are duplicated. error DuplicateRecipient(); /// @notice Initialize the vault after cloning. /// @param _taxToken The associated tax token address. /// @param _recipients Array of recipients and their shares. function initialize(address _taxToken, Recipient[] calldata _recipients) external; /// @notice Dispatch claimable balances to all recipients. function dispatch() external; /// @notice Claim the caller’s accumulated balance. /// @param user The recipient address to claim for. function claim(address user) external; /// @notice Return detailed information for all recipients. /// @return info Array of recipient info entries. function getRecipientsInfo() external view returns (RecipientInfo[] memory info); /// @notice Retrieve a recipient by index. /// @param index The recipient index. /// @return recipient The recipient address. /// @return bps The recipient share in basis points. function recipients(uint256 index) external view returns (address recipient, uint16 bps); /// @notice Retrieve user balance totals for a recipient. /// @param user The recipient address. /// @return accumulated Total BNB credited to the user. /// @return claimed Total BNB claimed by the user. function userBalances(address user) external view returns (uint128 accumulated, uint128 claimed); /// @notice The associated tax token address. function taxToken() external view returns (address); /// @notice The factory address that created this vault. function factory() external view returns (address); /// @notice The total basis points constant (10,000). function TOTAL_BPS() external view returns (uint256); } ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#gift-vault-flapxvault-interface) Gift Vault (FlapXVault) interface For detailed API integration guide and the complete interface, see the [Gift Vault Guide](https://docs.flap.sh/flap/developers/vault-developers/gift-vault) . [PreviousLaunch token through VaultPortal](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal) [NextVault Developers](https://docs.flap.sh/flap/developers/vault-developers) Last updated 5 months ago * [Registered vaults](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#registered-vaults) * [BNB mainnet](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#bnb-mainnet) * [BNB testnet](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#bnb-testnet) * [Interfaces](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#interfaces) * [Split Vault interface](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#split-vault-interface) * [Gift Vault (FlapXVault) interface](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults#gift-vault-flapxvault-interface) --- # Tutorials | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/vault-developers/tutorials.md) . Step-by-step guides for building specific vault features. * [Using an LP Token or Child Token as the Dividend Token](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend) [PreviousBuilding Upgradeable Vaults — Beacon Proxy Pattern](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault) [NextUsing an LP Token or Child Token as the Dividend Token](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend) Last updated 23 days ago --- # Inspect A Tax Token | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token.md) . [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#overview) Overview -------------------------------------------------------------------------------------------------------------------------- You can get the tax info of any tax token using the Tax Token Helper contract. Our website uses this contract to get tax info for tokens and show them to users: ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252FeCNDbbhq7lDU8jAkw7bm%252Fimage.png%3Falt%3Dmedia%26token%3D82717aa1-3278-4d91-af71-31670a9c92d7&width=768&dpr=3&quality=100&sign=ac1a93c2&sv=2) [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#tax-token-helper) Tax Token Helper ------------------------------------------------------------------------------------------------------------------------------------------ Chain Tax Token Helper BNB Mainnet 0x53841c73217735F37BC1775538b03b23feFD8346 BNB Testnet 0xD64441e5FcD02D342B8cf6eBA10Ef6E40d0dA90f Robinhood Mainnet 0xb10bD2672aE63735d677164A54B573a016f0203C [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#gettaxtokeninfo-vs-gettaxtokeninfov2) getTaxTokenInfo vs getTaxTokenInfoV2 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The contract exposes two query functions. Use the table below to decide which one to call. `getTaxTokenInfo` `getTaxTokenInfoV2` **Return type** `TaxTokenInfo` `TaxTokenInfoV2` **Tax rate fields** Single `taxRate` Separate `buyTaxRate` and `sellTaxRate` **Marketing address** `marketingWallet` (plain address) `vaultInfo.addr` + full `MarketingWalletInfo` **Vault metadata** None `isVault`, `factory`, `riskLevel`, `isOfficialVault` **AI oracle flag** None `isAIConsumer` **Dividend reward token** Not available `dividendToken` (`address(0)` = native gas token) **Recommended for** Legacy integrations All new integrations ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#using-gettaxtokeninfo) Using getTaxTokenInfo `getTaxTokenInfo` is the original query function. Call it when you only need the core tax breakdown and cumulative statistics: For V1 tax tokens (`TOKEN_TAXED`), only `marketBps` (always `10000`), `taxRate`, `marketingWallet`, `quoteToken`, and `totalQuoteSentToMarketing` are populated. All other fields are zero. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#using-gettaxtokeninfov2) Using getTaxTokenInfoV2 `getTaxTokenInfoV2` is the recommended function for all new integrations. It returns everything `getTaxTokenInfo` returns, plus richer metadata about the marketing wallet and dividend token. #### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#reading-vault-risk-level) Reading vault risk level `riskLevel` maps to the `IVaultPortalTypes.RiskLevel` enum: Value Meaning 0 UNVERIFIED (default for non-vaults) 1 LOW\_RISK 2 LOW\_MEDIUM\_RISK 3 MEDIUM\_RISK 4 HIGH\_RISK #### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#detecting-ai-powered-vaults) Detecting AI-powered vaults `isAIConsumer` is `true` when either: * The vault's category in VaultPortal is `TYPE_AI_ORACLE_POWERED`, **or** * The marketing wallet is a smart contract (not an EOA or EIP-7702 delegate) that has submitted at least one request through `FlapAIProvider`. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#v1-tax-token-caveats) V1 tax token caveats For V1 tax tokens (`TOKEN_TAXED`), both functions populate only a subset of fields: `marketBps` (always `10000`, meaning 100% goes to the marketing/funds-recipient wallet), `buyTaxRate`/`sellTaxRate` (or `taxRate`), `vaultInfo.addr`/`marketingWallet`, `quoteToken`, and `totalQuoteSentToMarketing`. The `totalQuoteSentToMarketing` field may be zero for older V1 tokens. If that is the case, we provide a backend to get the `totalQuoteSentToMarketing` for V1 Tax tokens: > Note: you only need this fallback when `totalQuoteSentToMarketing` returned by `getTaxTokenInfo` is zero for V1 Tax tokens. For V2 Tax tokens, the `totalQuoteSentToMarketing` is always accurate. [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#how-to-inspect-a-tax-tokens-vault) how to inspect a tax token's vault ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When using `getTaxTokenInfoV2`, vault metadata is already included in the `vaultInfo` field of `TaxTokenInfoV2` — no separate VaultPortal call is needed for the basic case. For deeper, vault-type-specific data, you can still query VaultPortal directly. Use the VaultPortal `tryGetVault` method to get basic vault information; the returned `description` gives a human-readable summary of the vault. For details on the fields returned by `tryGetVault`, refer to [Get Vault Info](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#get-vault-info) . If you want vault-type-specific data, compare the `vaultFactory` returned by `tryGetVault` against the known registered vault factories and then query the vault using its own interface. The list of registered vault factories and their interfaces is available at [developers/token-launcher-developers/registered-vaults.md](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults) . [PreviousToken Version Specification](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification) [NextTrade Tokens](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens) Last updated 2 days ago * [Overview](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#overview) * [Tax Token Helper](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#tax-token-helper) * [getTaxTokenInfo vs getTaxTokenInfoV2](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#gettaxtokeninfo-vs-gettaxtokeninfov2) * [Using getTaxTokenInfo](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#using-gettaxtokeninfo) * [Using getTaxTokenInfoV2](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#using-gettaxtokeninfov2) * [V1 tax token caveats](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#v1-tax-token-caveats) * [how to inspect a tax token's vault](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token#how-to-inspect-a-tax-tokens-vault) Copy // SPDX-License-Identifier: MIT pragma solidity =0.8.24; /// @title ITaxTokenHelper /// @notice Interface for the TaxTokenHelper contract that provides utilities for interacting with tax tokens /// @dev This helper supports both legacy (TOKEN_TAXED) and new (TOKEN_TAXED_V2) tax token versions interface ITaxTokenHelper { /// @notice Structure containing comprehensive tax token configuration and statistics /// @dev All BPS values are in basis points (1 BPS = 0.01%) struct TaxTokenInfo { /// @notice Marketing tax allocation in basis points (0-10000) uint16 marketBps; /// @notice Deflation (burn) tax allocation in basis points (0-10000) uint16 deflationBps; /// @notice Liquidity provision tax allocation in basis points (0-10000) uint16 lpBps; /// @notice Dividend distribution tax allocation in basis points (0-10000) uint16 dividendBps; /// @notice Total tax rate applied to transfers in basis points (sum of all allocations) uint16 taxRate; /// @notice Cumulative amount of tokens burnt (sent to black hole and dead addresses) uint256 burntTokenAmount; /// @notice Cumulative amount of quote tokens distributed to dividend holders uint256 totalQuoteSentToDividend; /// @notice Cumulative amount of quote tokens added to liquidity pools uint256 totalQuoteAddedToLiquidity; /// @notice Cumulative amount of tax tokens added to liquidity pools uint256 totalTokenAddedToLiquidity; /// @notice Cumulative amount of quote tokens sent to marketing wallet uint256 totalQuoteSentToMarketing; /// @notice Address receiving marketing tax allocations address marketingWallet; /// @notice Address of the quote token used for tax swaps (address(0) for native token) address quoteToken; /// @notice Minimum token balance required to receive dividend distributions uint256 minimumShareBalance; } /// @notice Information about the marketing wallet or vault associated with a tax token struct MarketingWalletInfo { /// @notice The marketing wallet address, which could be a plain EOA, a Flap Vault, or any smart contract address addr; /// @notice The vault factory address that created this vault. /// Zero if the marketing wallet is not a Flap Vault or the factory is unknown. address factory; /// @notice Risk level as assessed by Flap auditors. /// UNVERIFIED(0), LOW_RISK(1), LOW_MEDIUM_RISK(2), MEDIUM_RISK(3), HIGH_RISK(4) uint8 riskLevel; /// @notice Whether the vault is an official Flap-endorsed vault bool isOfficialVault; /// @notice Whether the marketing wallet is a registered Flap Vault (via VaultPortal) bool isVault; /// @notice Whether the marketing wallet is an AI consumer. /// True when the vault category is TYPE_AI_ORACLE_POWERED, or when the address /// is a non-EOA contract that has made at least one request via FlapAIProvider. bool isAIConsumer; } /// @notice Extended tax token info with vault and AI oracle metadata. /// Prefer this over TaxTokenInfo for new integrations. struct TaxTokenInfoV2 { /// @notice Marketing tax allocation in basis points (0-10000) uint16 marketBps; /// @notice Deflation (burn) tax allocation in basis points (0-10000) uint16 deflationBps; /// @notice Liquidity provision tax allocation in basis points (0-10000) uint16 lpBps; /// @notice Dividend distribution tax allocation in basis points (0-10000) uint16 dividendBps; /// @notice Tax rate applied on buys, in basis points (e.g., 500 = 5%) uint16 buyTaxRate; /// @notice Tax rate applied on sells, in basis points (e.g., 500 = 5%). /// Currently equal to buyTaxRate as the contract uses a single taxRate. uint16 sellTaxRate; /// @notice Cumulative amount of tokens burnt (sent to black hole and dead addresses) uint256 burntTokenAmount; /// @notice Cumulative amount of quote tokens distributed to dividend holders uint256 totalQuoteSentToDividend; /// @notice Cumulative amount of quote tokens added to liquidity pools uint256 totalQuoteAddedToLiquidity; /// @notice Cumulative amount of tax tokens added to liquidity pools uint256 totalTokenAddedToLiquidity; /// @notice Cumulative amount of quote tokens sent to marketing wallet uint256 totalQuoteSentToMarketing; /// @notice The token distributed as dividends to shareholders. /// address(0) means the native gas token (e.g., BNB or ETH); /// any other address is the ERC20 reward token. address dividendToken; /// @notice Address of the quote token used for tax swaps (address(0) for native token) address quoteToken; /// @notice Minimum token balance required to receive dividend distributions uint256 minimumShareBalance; /// @notice Information about the marketing wallet / vault that receives tax revenue MarketingWalletInfo vaultInfo; } /// @notice Returns the version of the TaxTokenHelper contract /// @return Version string in semantic versioning format function version() external pure returns (string memory); /// @notice Retrieves comprehensive information about a tax token's configuration and statistics /// @dev Returns zero/empty values for non-tax tokens (TOKEN_V2_PERMIT) /// @dev For legacy TOKEN_TAXED tokens, only marketBps, taxRate, marketingWallet, quoteToken, and totalQuoteSentToMarketing are populated /// @dev For TOKEN_TAXED_V2 tokens, all fields are populated based on TaxProcessor state /// @param taxToken Address of the tax token to query /// @return info TaxTokenInfo struct containing all tax token configuration and statistics function getTaxTokenInfo(address taxToken) external view returns (TaxTokenInfo memory info); /// @notice Retrieves extended tax token info including vault and AI oracle metadata /// @dev Returns zero/empty values for non-tax tokens (TOKEN_V2_PERMIT) /// @dev For legacy TOKEN_TAXED tokens, marketBps, buyTaxRate/sellTaxRate, vaultInfo.addr, quoteToken, and totalQuoteSentToMarketing are populated /// @dev For TOKEN_TAXED_V2 tokens, all fields including vaultInfo are populated /// @param taxToken Address of the tax token to query /// @return info TaxTokenInfoV2 struct containing extended tax token configuration, statistics, and vault metadata function getTaxTokenInfoV2(address taxToken) external view returns (TaxTokenInfoV2 memory info); /// @notice Retrieves dividend information for a specific user and tax token /// @dev Returns (0, 0) if the tax token has no dividend contract configured /// @param taxToken Address of the tax token /// @param user Address of the user to query dividend information for /// @return claimed Total amount of dividends already claimed by the user /// @return pending Amount of dividends currently available for withdrawal function getDividendInfo(address taxToken, address user) external view returns (uint256 claimed, uint256 pending); /// @notice Claims accumulated dividends for the caller (msg.sender) /// @dev Reverts if the tax token has no dividend contract configured /// @dev For WETH dividends, automatically unwraps to native token /// @param taxToken Address of the tax token to claim dividends from function claimDividend(address taxToken) external; /// @notice Claims accumulated dividends for the caller with optional native token unwrapping /// @dev Reverts if the tax token has no dividend contract configured /// @param taxToken Address of the tax token to claim dividends from /// @param unwrapWETH If true, unwraps WETH dividends to native token; if false, sends WETH function claimDividend(address taxToken, bool unwrapWETH) external; /// @notice Claims accumulated dividends on behalf of a specified user /// @dev Reverts if the tax token has no dividend contract configured /// @dev Can be called by anyone to trigger dividend withdrawal for any user /// @param taxToken Address of the tax token to claim dividends from /// @param unwrapWETH If true, unwraps WETH dividends to native token; if false, sends WETH /// @param user Address of the user to claim dividends for function claimDividendForUser(address taxToken, bool unwrapWETH, address user) external; } Copy ITaxTokenHelper helper = ITaxTokenHelper(TAX_TOKEN_HELPER); ITaxTokenHelper.TaxTokenInfo memory info = helper.getTaxTokenInfo(taxToken); // Tax allocation pie (all in BPS, sum = taxRate) // info.marketBps — share going to marketing wallet // info.deflationBps — share burnt // info.lpBps — share added to liquidity // info.dividendBps — share distributed to dividend holders // Tax rate // info.taxRate — total tax charged on every transfer (BPS) // Cumulative statistics // info.burntTokenAmount — tokens sent to black hole + dead address // info.totalQuoteSentToDividend — quote tokens paid to dividend holders // info.totalQuoteAddedToLiquidity // info.totalTokenAddedToLiquidity // info.totalQuoteSentToMarketing // Addresses // info.marketingWallet — wallet receiving marketing revenue // info.quoteToken — address(0) means native gas token (BNB/ETH) // Dividend gating // info.minimumShareBalance — min token balance to receive dividends Copy ITaxTokenHelper helper = ITaxTokenHelper(TAX_TOKEN_HELPER); ITaxTokenHelper.TaxTokenInfoV2 memory info = helper.getTaxTokenInfoV2(taxToken); // Tax allocation — same BPS fields as TaxTokenInfo // info.marketBps / deflationBps / lpBps / dividendBps // Separate buy and sell tax rates (replaces the single taxRate field) // info.buyTaxRate — tax on buys (BPS) // info.sellTaxRate — tax on sells (BPS, currently equal to buyTaxRate) // Cumulative statistics — same as TaxTokenInfo // info.burntTokenAmount / totalQuoteSentToDividend / totalQuoteAddedToLiquidity // info.totalTokenAddedToLiquidity / totalQuoteSentToMarketing // Dividend reward token (new in V2) // info.dividendToken — address(0) = native gas token; otherwise the ERC20 reward token // info.quoteToken // info.minimumShareBalance // Marketing wallet / vault metadata (new in V2) // info.vaultInfo.addr — the marketing wallet or vault address // info.vaultInfo.isVault — true if registered via VaultPortal // info.vaultInfo.factory — vault factory that created it (zero if not a vault) // info.vaultInfo.riskLevel — 0 UNVERIFIED · 1 LOW · 2 LOW_MEDIUM · 3 MEDIUM · 4 HIGH // info.vaultInfo.isOfficialVault — true if Flap-endorsed // info.vaultInfo.isAIConsumer — true if vault uses Flap AI Oracle Copy https://t3w1p53k7a.execute-api.eu-west-3.amazonaws.com/donation?token=0x36F2FD027F5f27C59B8C6d64dF64bcC8E8C97777 --- # Launch token through VaultPortal | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal.md) . `VaultPortal` builds on top of `Portal` to add Vault functionality for vault-backed token launches. It creates the token and its associated Vault in a single transaction. Use `VaultPortal` when you need a Vault-backed launch. For launches without a Vault, use `Portal` instead. For the `VaultPortal` contract address, see [VaultPortal deployed addresses](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#deployed-addresses) . [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#key-interfaces) Key interfaces -------------------------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#recommended-newtokenv6withvault) Recommended: `newTokenV6WithVault` The preferred entry point for new integrations is `newTokenV6WithVault`, which supports `TOKEN_TAXED_V3` (Tax Token V3) with a vault in a single transaction: Copy /// @notice Create a new tax token via Portal's newTokenV6 with an associated vault in a single transaction /// @dev Only TOKEN_TAXED_V3 is currently supported as tokenVersion; any other version reverts with FeatureDisabled. /// @param params The parameters for creating the tax token and vault (mirrors NewTokenV6Params, minus beneficiary) /// @return token The address of the newly created tax token function newTokenV6WithVault(NewTokenV6WithVaultParams calldata params) external payable returns (address token); `NewTokenV6WithVaultParams` mirrors `NewTokenV6Params` (without `beneficiary`) and adds vault-specific fields: Copy struct NewTokenV6WithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.DEXId dexId; IPortalTypes.V3LPFeeProfile lpFeeProfile; // V3 tax fields uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; uint64 antiFarmerDuration; uint16 mktBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; uint256 minimumShareBalance; address dividendToken; address commissionReceiver; IPortalTypes.TokenVersion tokenVersion; // must be TOKEN_TAXED_V3 // vault fields address vaultFactory; bytes vaultData; } `newTokenV6WithVault` only accepts `TOKEN_TAXED_V3`. Passing any other `tokenVersion` reverts with `FeatureDisabled`. For new integrations, always use Tax Token V3 with `newTokenV6WithVault`. Legacy tax token versions (`TOKEN_TAXED`, `TOKEN_TAXED_V2`) are not supported by `newTokenV6WithVault` and are not recommended for new launches. The old `newTaxTokenWithVault` entry point is deprecated and should not be used for new or existing integrations. ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#new-newtokenv7withvault) New: `newTokenV7WithVault` `newTokenV7WithVault` is the VaultPortal wrapper around `Portal.newTokenV7`. It creates the vault first, then rewrites the active `MARKETING_OR_VAULT` fee receiver to that vault before forwarding the launch to `Portal`. `NewTokenV7WithVaultParams` mirrors `Portal`'s `NewTokenV7Params` and adds vault-specific fields: ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#current-newtokenv7withvault-behavior) Current `newTokenV7WithVault` behavior The interface comment describes both `TOKEN_V3_PERMIT` and `TOKEN_TAXED_V3`, but the current implementation in `FlapTaxVaults/src/VaultPortalLaunch.sol` is narrower: * it currently reverts with `FeatureDisabled()` unless `tokenVersion == TOKEN_V3_PERMIT` * `quoteToken` must be `address(0)` or the wrapper reverts with `UnsupportedQuoteToken(...)` * exactly one active `MARKETING_OR_VAULT` slot is required * active `feeConfigs` must sum to exactly `10000` bps * after validation, the wrapper overwrites that single `MARKETING_OR_VAULT` slot's `marketingAddress` with the freshly created vault address * the current source hard-codes an allowlist of supported vault factories for this path The supplied interface and simulation script describe a broader V7-with-vault design, but the current source implementation is stricter. When integrating, follow the behavior enforced in `FlapTaxVaults/src/VaultPortalLaunch.sol` rather than assuming every upstream `newTokenV7` combination is already live through `VaultPortal`. ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#deprecated-newtaxtokenwithvault) Deprecated: `newTaxTokenWithVault` `newTaxTokenWithVault` still exists in the ABI for compatibility, but the current `VaultPortal` implementation no longer supports it. The function always reverts and should be treated as deprecated. Do not integrate `newTaxTokenWithVault`. Use `newTokenV6WithVault` for `TOKEN_TAXED_V3`, or `newTokenV7WithVault` for the V7 vault-backed flow. [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#how-to-launch-with-newtokenv6withvault) How to launch with `newTokenV6WithVault` ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 1. **Choose a registered vault factory.** Vault factories are registered in `VaultPortal`. Each factory defines its own `vaultData` schema. See [Registered vaults](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults) . 2. **Prepare metadata and core token parameters.** Use the same metadata flow as `Portal` (see [launch-token-through-portal.md](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal) ). Set `tokenVersion = TOKEN_TAXED_V3` and both `buyTaxRate`/`sellTaxRate` > 0. 3. **Encode** `**vaultData**` **for the selected factory.** The `vaultData` bytes are factory-specific. Always follow the factory's schema (see [Registered vaults](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults) ). 4. **Call** `**newTokenV6WithVault**`**.** The contract emits `FlapTaxVaultTokenCreated` with the token, vault, and vault factory addresses. Example: Each vault factory can define a different `vaultData` schema. Always validate the factory and its expected payload before encoding. [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#how-to-launch-with-newtokenv7withvault) How to launch with `newTokenV7WithVault` ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 1. **Choose a supported vault factory.** The current implementation validates the V7-with-vault path against a hard-coded factory allowlist inside `VaultPortalLaunch`. Verify your factory is supported before integrating this path. 2. **Prepare metadata and V7 token parameters.** Use the same metadata flow as `Portal` (see [launch-token-through-portal.md](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal) ). 3. **Configure** `**feeConfigs**` **with exactly one vault receiver slot.** Provide exactly one active `MARKETING_OR_VAULT` slot. Its address is only a placeholder at input time — `VaultPortal` will replace it with the newly created vault address before forwarding to `Portal.newTokenV7`. 4. **Encode** `**vaultData**` **for the selected factory.** The schema is factory-specific. See [Registered vaults](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults) and the factory's own docs. 5. **Call** `**newTokenV7WithVault**`**.** On success, `VaultPortal` stores the launched token → vault mapping and emits `FlapTaxVaultTokenCreated`. Example based on `FlapTaxVaults/script/testnets/bsc_test/simulation/001-simulate-new-token-v7-with-flap-xvault.sol`: Key notes: * `newTokenV7WithVault` does not expose a standalone `beneficiary`; the vault-bound receiver comes from the single active `MARKETING_OR_VAULT` slot. * If you configure zero or more than one active `MARKETING_OR_VAULT` slot, the wrapper reverts with `InvalidFeeConfig()`. * The wrapper predicts the token address before vault creation, so your salt must already match the correct vanity suffix for the chosen token implementation. * The current V7-with-vault implementation only accepts `quoteToken = address(0)`. [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#find-the-salt-vanity-suffix) Find the salt (vanity suffix) ------------------------------------------------------------------------------------------------------------------------------------------------------------------ `VaultPortal` uses CREATE2 through `Portal`, so the `salt` must produce a token address with the required suffix. Follow the same salt-finding flow as [launch-token-through-portal.md](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal) , using the implementation that matches the launch path: * `newTokenV6WithVault` with `TOKEN_TAXED_V3`: use **Tax Token V3 Impl** (`tokenImplTaxedV3`), suffix `7777` * `newTokenV7WithVault` with `TOKEN_V3_PERMIT`: use **Standard Token V3 Impl** (`tokenImplV3` / Standard Token V3 Impl), suffix `8888` [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#salt-locking) Salt locking ---------------------------------------------------------------------------------------------------------------------------------- Portal supports locking a vanity salt so that only you can use it to launch a token. This works seamlessly with `VaultPortal` — lock the salt directly on Portal, then launch through VaultPortal as normal. No changes to `NewTokenV6WithVaultParams` are needed. VaultPortal communicates the real caller back to Portal automatically during the launch transaction. The same salt-lock behavior also applies to `newTokenV7WithVault`. [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#reading-vault-info) Reading vault info ---------------------------------------------------------------------------------------------------------------------------------------------- You can query vault information after launch: * `getVault(taxToken)` returns the full vault info and reverts if not found. * `tryGetVault(taxToken)` returns `(found, info)` without reverting. [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#ivaultportal.sol) IVaultPortal.sol ------------------------------------------------------------------------------------------------------------------------------------------ Full interface reference for developers: [PreviousLaunch token through Portal](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal) [NextRegistered vaults](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults) Last updated 1 month ago * [Key interfaces](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#key-interfaces) * [Recommended: newTokenV6WithVault](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#recommended-newtokenv6withvault) * [New: newTokenV7WithVault](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#new-newtokenv7withvault) * [Current newTokenV7WithVault behavior](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#current-newtokenv7withvault-behavior) * [Deprecated: newTaxTokenWithVault](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#deprecated-newtaxtokenwithvault) * [How to launch with newTokenV6WithVault](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#how-to-launch-with-newtokenv6withvault) * [How to launch with newTokenV7WithVault](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#how-to-launch-with-newtokenv7withvault) * [Find the salt (vanity suffix)](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#find-the-salt-vanity-suffix) * [Salt locking](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#salt-locking) * [Reading vault info](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#reading-vault-info) * [IVaultPortal.sol](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal#ivaultportal.sol) Copy /// @notice Create a new token via a V7-style fee-config API with an associated vault. /// @dev Supports TOKEN_V3_PERMIT (non-tax) and TOKEN_TAXED_V3 (tax). The wrapper creates the vault first, /// then rewrites MARKETING_OR_VAULT receivers to that vault before calling Portal.newTokenV7. /// @param params The parameters for creating the token and vault (mirrors upstream newTokenV7 launch UX) /// @return token The address of the newly created token function newTokenV7WithVault(NewTokenV7WithVaultParams calldata params) external payable returns (address token); Copy struct NewTokenV7WithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.TokenVersion tokenVersion; IPortalTypes.DEXId dexId; uint64 antiFarmerDuration; uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; address commissionReceiver; IPortalTypes.FeeConfig[4] feeConfigs; address vaultFactory; bytes vaultData; } Copy /// @notice Deprecated. Use newTokenV6WithVault instead. /// @dev This function previously created a V1/V2 tax token via Portal's newTokenV5. /// It is no longer supported. Calling it will always revert. /// @param params Unused — kept for ABI compatibility only. function newTaxTokenWithVault(NewTaxTokenWithVaultParams calldata params) external payable returns (address token); Copy import { encodeAbiParameters, parseEther, zeroAddress } from "viem"; const params = { name: "My Token", symbol: "MTK", meta: "", dexThresh: DexThreshType.TWO_THIRDS, salt, // must produce 7777 vanity suffix migratorType: MigratorType.V2_MIGRATOR, quoteToken: zeroAddress, quoteAmt: parseEther("0.01"), permitData: "0x", extensionID: "0x0000000000000000000000000000000000000000000000000000000000000000", extensionData: "0x", dexId: DEXId.DEX0, lpFeeProfile: V3LPFeeProfile.LP_FEE_PROFILE_STANDARD, buyTaxRate: 300, sellTaxRate: 1000, taxDuration: 365n * 24n * 60n * 60n, antiFarmerDuration: 60n * 60n, mktBps: 10000, deflationBps: 0, dividendBps: 0, lpBps: 0, minimumShareBalance: 0n, dividendToken: zeroAddress, commissionReceiver: zeroAddress, tokenVersion: TokenVersion.TOKEN_TAXED_V3, vaultFactory: vaultFactoryAddress, vaultData: encodeAbiParameters( [\ // replace with the selected vault factory's expected schema\ ], [] ), }; const hash = await walletClient.writeContract({ address: vaultPortalAddress, abi: vaultPortalAbi, functionName: "newTokenV6WithVault", args: [params], value: parseEther("0.01"), }); Copy function launchV7WithVault(IVaultPortal vaultPortal, bytes32 salt, address vaultFactoryAddress) external payable returns (address token) { IVaultPortalTypes.NewTokenV7WithVaultParams memory params; params.name = "My V7 Vault Token"; params.symbol = "MV7"; params.meta = ""; params.dexThresh = IPortalTypes.DexThreshType.FOUR_FIFTHS; params.salt = salt; // TOKEN_V3_PERMIT currently requires the 8888 suffix params.migratorType = IPortalTypes.MigratorType.PCS_INFINITY_CL_MIGRATOR; params.quoteToken = address(0); params.quoteAmt = 0.1 ether; params.permitData = ""; params.extensionID = bytes32(0); params.extensionData = ""; params.tokenVersion = IPortalTypes.TokenVersion.TOKEN_V3_PERMIT; params.dexId = IPortalTypes.DEXId.DEX0; params.antiFarmerDuration = 0; params.buyTaxRate = 0; params.sellTaxRate = 0; params.taxDuration = 0; params.commissionReceiver = address(0); params.vaultFactory = vaultFactoryAddress; params.vaultData = abi.encode(/* vault init data */); params.feeConfigs[0] = IPortalTypes.FeeConfig({ feeType: IPortalTypes.FeeType.MARKETING_OR_VAULT, bps: 10000, marketingAddress: msg.sender, // placeholder; replaced with the created vault address dividendToken: address(0), minimumShareBalance: 0 }); params.feeConfigs[1] = IPortalTypes.FeeConfig({ feeType: IPortalTypes.FeeType.NONE, bps: 0, marketingAddress: address(0), dividendToken: address(0), minimumShareBalance: 0 }); params.feeConfigs[2] = IPortalTypes.FeeConfig({ feeType: IPortalTypes.FeeType.NONE, bps: 0, marketingAddress: address(0), dividendToken: address(0), minimumShareBalance: 0 }); params.feeConfigs[3] = IPortalTypes.FeeConfig({ feeType: IPortalTypes.FeeType.NONE, bps: 0, marketingAddress: address(0), dividendToken: address(0), minimumShareBalance: 0 }); token = vaultPortal.newTokenV7WithVault{value: msg.value}(params); } Copy function lockAndLaunch( IPortal portal, IVaultPortal vaultPortal, bytes32 mySalt, IPortalTypes.TokenVersion tokenVersion, IVaultPortalTypes.NewTokenV6WithVaultParams memory params, uint256 quoteAmt ) external payable returns (address token) { // 1. Lock the salt on Portal directly portal.lockSalt{value: msg.value}(mySalt, tokenVersion); // 2. Launch through VaultPortal — the lock is honoured automatically token = vaultPortal.newTokenV6WithVault{value: quoteAmt}(params); } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {IPortalTypes} from "src/interfaces/IPortal.sol"; /// @title IVaultPortalTypes /// @notice Type definitions for the VaultPortal contract /// @dev Contains all structs and custom types used by VaultPortal interface IVaultPortalTypes { /// @notice Risk level classification for vault audits (8 bits) /// @dev Used to represent the security assessment of a vault or factory enum RiskLevel { UNVERIFIED, // 0 - Not yet verified/audited LOW_RISK, // 1 - Low risk LOW_MEDIUM_RISK, // 2 - Low to medium risk MEDIUM_RISK, // 3 - Medium risk HIGH_RISK // 4 - High risk } /// @notice Category classification for vaults (8 bits) /// @dev Used to categorize vaults by their type or functionality enum VaultCategory { NONE, // 0 - Not in any category (default) TYPE_AI_ORACLE_POWERED // 1 - AI Oracle powered vaults } /// @notice Information about a registered vault factory /// @dev Packed into a single storage slot for gas efficiency /// @param enabled Whether this factory can be used to create new vaults /// @param official Whether vaults created by this factory are marked as official/endorsed /// @param riskLevel The risk level classification from audit /// @param category The category classification for vaults from this factory (1 byte) /// @param reserved Reserved space for future upgrades (28 bytes) struct VaultFactoryInfo { bool enabled; bool official; RiskLevel riskLevel; VaultCategory category; bytes28 reserved; } /// @notice Packed vault information stored in contract storage /// @dev Uses three storage slots (64 bytes total) /// @param vault The address of the vault contract (20 bytes) /// @param vaultFactory The address of the vault factory that created this vault (20 bytes) /// @param adapter The address of the adapter contract for legacy vaults (20 bytes) /// @param isOfficial Whether this vault is marked as official/endorsed (1 byte) /// @param riskLevel The risk level classification from audit (1 byte) /// @param category The category classification for this vault (1 byte) struct VaultedTaxTokenInfo { // slot 0 address vault; bool isOfficial; RiskLevel riskLevel; VaultCategory category; bytes9 reserved0; // slot 1 address vaultFactory; bytes12 reserved1; // slot 2 address adapter; bytes12 reserved2; } /// @notice Audit report for a tax token /// @param auditor The address of the auditor who submitted this report /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report document struct AuditReport { address auditor; RiskLevel riskLevel; string ipfsCid; } /// @notice Complete vault information returned to external callers /// @dev This struct is used for view functions and includes human-readable description /// @param vault The address of the vault contract /// @param vaultFactory The address of the vault factory that created this vault (zero address if unknown) /// @param description Human-readable description of the vault's purpose and functionality /// @param isOfficial Whether this vault is marked as official/endorsed by the protocol /// @param riskLevel The risk level classification from audit struct VaultInfo { address vault; address vaultFactory; string description; bool isOfficial; RiskLevel riskLevel; } /// @notice Parameters required to create a new tax token with an associated vault /// @dev All parameters are passed as a single struct to avoid stack-too-deep errors /// @param name The name of the tax token (e.g., "MyToken") /// @param symbol The symbol of the tax token (e.g., "MTK") /// @param meta Metadata URI or string for additional token information /// @param dexThresh The DEX supply threshold type /// @param salt A unique salt for deterministic address generation (must produce vanity suffix) /// @param taxRate The tax rate in basis points (e.g., 100 = 1%, max 1000 = 10%) /// @param migratorType The migrator type (see MigratorType enum) /// @param quoteToken The token used for initial liquidity (address(0) for native token) /// @param quoteAmt The amount of quote token to provide as initial liquidity /// @param permitData The optional permit data for the quote token /// @param extensionID The ID of the extension to be used for the new token if not zero /// @param extensionData Additional extension specific data /// @param dexId The preferred DEX ID for the token /// @param lpFeeProfile The preferred V3 LP fee profile for the token /// @param taxDuration Tax duration in seconds (max: 100 years) /// @param antiFarmerDuration Anti-farmer duration in seconds (max: 1 year) /// @param mktBps Market allocation basis points (to beneficiary) /// @param deflationBps Deflation basis points (burned) /// @param dividendBps Dividend basis points (to dividend contract) /// @param lpBps Liquidity provision basis points (LP to dead address) /// @param minimumShareBalance Minimum balance for dividend eligibility /// @param vaultFactory The address of the vault factory to use for creating the vault (any factory is allowed; unregistered/disabled factories result in unverified vaults) /// @param vaultData Encoded data specific to the vault type being created struct NewTaxTokenWithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; uint16 taxRate; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.DEXId dexId; IPortalTypes.V3LPFeeProfile lpFeeProfile; uint64 taxDuration; uint64 antiFarmerDuration; uint16 mktBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; uint256 minimumShareBalance; address vaultFactory; bytes vaultData; } /// @notice Parameters required to create a new V3 tax token with an associated vault /// @dev tokenVersion must be TOKEN_TAXED_V3; if it is not, the implementation reverts with FeatureDisabled struct NewTokenV6WithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.DEXId dexId; IPortalTypes.V3LPFeeProfile lpFeeProfile; // V3 tax fields uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; uint64 antiFarmerDuration; uint16 mktBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; uint256 minimumShareBalance; address dividendToken; address commissionReceiver; /// The token version to use (must be TOKEN_TAXED_V3, otherwise reverts with FeatureDisabled) IPortalTypes.TokenVersion tokenVersion; // vault fields address vaultFactory; bytes vaultData; } /// @notice Parameters required to create a V7-style token with an associated vault. /// @dev Mirrors upstream `newTokenV7`, then swaps every MARKETING_OR_VAULT receiver to the /// freshly created vault before calling `Portal.newTokenV7`. struct NewTokenV7WithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.TokenVersion tokenVersion; IPortalTypes.DEXId dexId; uint64 antiFarmerDuration; uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; address commissionReceiver; IPortalTypes.FeeConfig[4] feeConfigs; address vaultFactory; bytes vaultData; } /* ========== EVENTS ========== */ /// @notice Emitted when a new tax token with an associated vault is successfully created /// @param token The address of the newly deployed tax token /// @param vault The address of the newly created vault that will receive tax revenue /// @param vaultFactory The vault factory address that was used to create the vault event FlapTaxVaultTokenCreated(address indexed token, address indexed vault, address indexed vaultFactory); /// @notice Emitted when a vault factory is registered or its configuration is updated /// @param factory The address of the vault factory being registered/updated /// @param enabled Whether the factory is enabled for creating new vaults /// @param official Whether vaults from this factory should be marked as official /// @param riskLevel The risk level classification for vaults from this factory event FlapTaxVaultFactoryRegistered(address factory, bool enabled, bool official, RiskLevel riskLevel); /// @notice Emitted when a vault factory's category is set during registration or update /// @param factory The address of the vault factory /// @param category The category classification for vaults from this factory event FlapTaxVaultFactoryCategorySet(address factory, VaultCategory category); /// @notice Emitted when an adapter is registered for a legacy tax token /// @param taxToken The address of the tax token /// @param adapter The address of the adapter contract event AdapterRegistered(address indexed taxToken, address indexed adapter); /// @notice Emitted when a new audit report is submitted /// @param taxToken The address of the tax token being audited /// @param auditor The address of the auditor /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report event AuditReportSubmitted(address indexed taxToken, address indexed auditor, RiskLevel riskLevel, string ipfsCid); /// @notice Emitted when a new audit report is submitted for a vault factory /// @param factory The address of the vault factory being audited /// @param auditor The address of the auditor /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report event FactoryAuditReportSubmitted( address indexed factory, address indexed auditor, RiskLevel riskLevel, string ipfsCid ); /// @notice Emitted when a vault's category is updated /// @param taxToken The address of the tax token /// @param category The new category event VaultCategoryUpdated(address indexed taxToken, VaultCategory category); /// @notice Emitted when a factory's category is updated /// @param factory The address of the vault factory /// @param category The new category event FactoryCategoryUpdated(address indexed factory, VaultCategory category); /* ========== ERRORS ========== */ /// @notice Thrown when the provided tax rate is invalid (0 or > 1000 basis points) /// @param taxRate The invalid tax rate that was provided error InvalidTaxRate(uint256 taxRate); /// @notice Thrown when mktBps is invalid (must be > 0) error InvalidMktBps(); /// @notice Thrown when V7-style fee configs are malformed. /// @dev Used for wrapper-level validation such as missing MARKETING_OR_VAULT buckets. error InvalidFeeConfig(); /// @notice Thrown when an unsupported quote token is specified /// @param quoteToken The address of the unsupported quote token error UnsupportedQuoteToken(address quoteToken); /// @notice Thrown when attempting to use a vault factory that is not registered /// @param factory The address of the unregistered vault factory error VaultFactoryNotRegistered(address factory); /// @notice Thrown when the predicted token address does not match the required vanity suffix /// @param predictedAddress The predicted address that failed the vanity check error InvalidVanity(address predictedAddress); /// @notice Thrown when trying to get vault info for a token that has no associated vault /// @param taxToken The address of the tax token with no vault error VaultNotFound(address taxToken); /// @notice Thrown when token address does not match predicted address error TokenAddressMismatch(); /// @notice Thrown when BNB transfer fails error BnbTransferFailed(); /// @notice Thrown when a non-V3 tax token version is specified (only TOKEN_TAXED_V3 is allowed) error OnlyV3TaxTokenAllowed(); /// @notice Thrown when a requested feature is not yet enabled error FeatureDisabled(); /// @notice Thrown when portal address is zero error ZeroPortalAddress(); /// @notice Thrown when token implementation address is zero error ZeroTokenImplAddress(); /// @notice Thrown when trying to perform an operation on a non-existent token /// @param taxToken The address of the non-existent tax token error TokenNotFound(address taxToken); /// @notice error when user is rate limited from creating tokens /// @param user The address that is rate limited /// @param lastCreationTime The timestamp of the user's last successful token creation error RateLimitExceeded(address user, uint256 lastCreationTime); } /// @title IVaultPortal /// @notice Interface for the VaultPortal contract that manages vault-backed token launches /// @dev VaultPortal acts as a registry and factory orchestrator for different vault types /// @dev It coordinates token launches with their associated revenue vaults interface IVaultPortal is IVaultPortalTypes { /* ========== FUNCTIONS ========== */ /// @notice Get information about a registered vault factory /// @param factory The address of the vault factory /// @return enabled Whether this factory can be used to create new vaults /// @return official Whether vaults created by this factory are marked as official/endorsed /// @return riskLevel The risk level classification for vaults from this factory /// @return reserved Reserved space for future upgrades (29 bytes) function vaultFactories(address factory) external view returns (bool enabled, bool official, RiskLevel riskLevel, bytes29 reserved); /* ========== FUNCTIONS ========== */ /// @notice Deprecated. Use newTokenV6WithVault instead. /// @dev This function previously created a V1/V2 tax token via Portal's newTokenV5. /// It is no longer supported. Calling it will always revert. /// @param params Unused — kept for ABI compatibility only. function newTaxTokenWithVault(NewTaxTokenWithVaultParams calldata params) external payable returns (address token); /// @notice Create a new tax token via Portal's newTokenV6 with an associated vault in a single transaction /// @dev Only TOKEN_TAXED_V3 is currently supported as tokenVersion; any other version reverts with FeatureDisabled. /// @param params The parameters for creating the tax token and vault (mirrors NewTokenV6Params, minus beneficiary) /// @return token The address of the newly created tax token function newTokenV6WithVault(NewTokenV6WithVaultParams calldata params) external payable returns (address token); /// @notice Create a new token via a V7-style fee-config API with an associated vault. /// @dev Supports TOKEN_V3_PERMIT (non-tax) and TOKEN_TAXED_V3 (tax). The wrapper creates the vault first, /// then rewrites MARKETING_OR_VAULT receivers to that vault before calling Portal.newTokenV7. /// @param params The parameters for creating the token and vault (mirrors upstream newTokenV7 launch UX) /// @return token The address of the newly created token function newTokenV7WithVault(NewTokenV7WithVaultParams calldata params) external payable returns (address token); /// @notice Predict the address of a tax token V1 given a salt /// @dev Uses CREATE2 deterministic address prediction /// @dev Useful for generating valid salts that produce the required vanity suffix /// @param salt The salt for deterministic deployment /// @return predictedAddress The predicted address of the tax token function predictTaxTokenV1Address(bytes32 salt) external view returns (address predictedAddress); /// @notice Get the complete vault information for a tax token /// @dev Reverts if no vault is found for the given tax token /// @dev Attempts to fetch the vault's description by calling its description() function /// @param taxToken The address of the tax token /// @return info The VaultInfo struct containing complete vault details function getVault(address taxToken) external view returns (VaultInfo memory info); /// @notice Attempt to get vault information for a tax token without reverting /// @dev First checks the internal mapping, then falls back to searching the Portal /// @dev For fallback results, isOfficial and isVerified will always be false /// @param taxToken The address of the tax token /// @return found Whether a vault was found for this tax token /// @return info The VaultInfo struct (empty if not found) function tryGetVault(address taxToken) external view returns (bool found, VaultInfo memory info); /// @notice Register a new vault factory or update an existing one (backward-compatible overload) /// @dev Only accounts with VAULT_ADMIN_ROLE can call this function /// @dev Allows enabling/disabling factories and updating their official/riskLevel status /// @dev Category defaults to VaultCategory.NONE /// @param factory The address of the vault factory to register/update /// @param enabled Whether the factory should be enabled for creating new vaults /// @param official Whether vaults from this factory should be marked as official /// @param riskLevel The risk level classification for vaults from this factory function registerVaultFactory(address factory, bool enabled, bool official, RiskLevel riskLevel) external; /// @notice Register a new vault factory or update an existing one with a specific category /// @dev Only accounts with VAULT_ADMIN_ROLE can call this function /// @dev Allows enabling/disabling factories and updating their official/riskLevel/category status /// @param factory The address of the vault factory to register/update /// @param enabled Whether the factory should be enabled for creating new vaults /// @param official Whether vaults from this factory should be marked as official /// @param riskLevel The risk level classification for vaults from this factory /// @param category The category classification for vaults from this factory function registerVaultFactory( address factory, bool enabled, bool official, RiskLevel riskLevel, VaultCategory category ) external; /// @notice Register an adapter for a legacy tax token vault /// @dev Only accounts with AUDITOR_ROLE can call this function /// @dev The adapter must implement the VaultBase interface for compatibility /// @param taxToken The address of the tax token /// @param adapter The address of the adapter contract function registerAdapter(address taxToken, address adapter) external; /// @notice Submit a new audit report for a tax token /// @dev Only accounts with AUDITOR_ROLE can call this function /// @dev The tax token must exist (have a vault or adapter registered) /// @dev This may change the risk level of the token /// @param taxToken The address of the tax token being audited /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report document function submitAuditReport(address taxToken, RiskLevel riskLevel, string calldata ipfsCid) external; /// @notice Submit a new audit report for a vault factory /// @dev Only accounts with VAULT_ADMIN_ROLE can call this function /// @dev The factory should be registered or have existing audit reports /// @param factory The address of the vault factory being audited /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report document function submitFactoryAuditReport(address factory, RiskLevel riskLevel, string calldata ipfsCid) external; /// @notice Get recent audit reports for a tax token with pagination /// @dev Returns reports starting from the most recent (end of array) /// @dev If the token has no audit reports, falls back to its factory's audit reports /// @param taxToken The address of the tax token /// @param offset The number of reports to skip from the end (0 = most recent) /// @param limit The maximum number of reports to return /// @return reports The array of audit reports /// @return total The total number of audit reports for this token function getAuditReports(address taxToken, uint256 offset, uint256 limit) external view returns (AuditReport[] memory reports, uint256 total); /// @notice Get the category of a vault /// @param taxToken The tax token address /// @return category The category of the vault function getVaultCategory(address taxToken) external view returns (VaultCategory category); /// @notice Get the category of a vault factory /// @param factory The vault factory address /// @return category The category of the factory function getFactoryCategory(address factory) external view returns (VaultCategory category); /// @notice Set the category of a vault /// @dev Only accounts with VAULT_ADMIN_ROLE can call this function /// @param taxToken The tax token address /// @param category The new category function setVaultCategory(address taxToken, VaultCategory category) external; /// @notice Set the category of a vault factory /// @dev Only accounts with VAULT_ADMIN_ROLE can call this function /// @param factory The vault factory address /// @param category The new category function setFactoryCategory(address factory, VaultCategory category) external; } --- # Beta: PVE Curve | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/preview/beta-pve-curve.md) . This feature is in **beta preview**. The curve parameters and behavior described here are subject to change before final release. Currently, the PVE curve is **only available for non-tax tokens** — i.e. tokens that will be migrated to **PancakeSwap V4** upon graduation. Tax tokens continue to use the original `CURVE_RH_BNB` curve. [](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#background-pve-vs-pvp-in-the-bonding-curve) Background: PVE vs PVP in the Bonding Curve ----------------------------------------------------------------------------------------------------------------------------------------------------------- In the context of Flap's bonding curve, we use two terms to describe the participant dynamics **within the bonding curve phase itself** (before the token ever reaches a DEX): * **PVP (Player vs Player)** — participants are pitted against each other. The curve's pricing creates large profit asymmetries between early and late buyers within the bonding phase, enabling extractive behavior. * **PVE (Player vs Environment)** — participants interact primarily with the curve's deterministic pricing. The profit asymmetry between early and late buyers is compressed, so participants cannot easily extract value from each other. The original BNB bonding curve (`CURVE_RH_BNB`) has a **13× price ratio** — the price at graduation (80% supply sold) is 13× higher than the price at launch. This makes the bonding curve phase highly PVP. ### [](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#the-problem-a-13-bonding-curve-is-high-pvp) The Problem: A 13× Bonding Curve is High-PVP On the original curve, the price starts **very low** and then **increases 13 times** by the end of the bonding curve. This extreme price ramp creates a dangerous dynamic: Original Curve (13×) Launch FDV ~$4.3k Migration FDV (80%) ~$41k Price increase during bonding **13×** Because the token is extremely cheap at launch and rockets upward, early participants can use **snipers, MEV bots, or automated scripts** to buy a large amount of tokens at the very beginning of the bonding curve. They acquire tokens at a price that is up to 13× cheaper than what latecomers will pay. These early accumulators can then **gradually dump their holdings onto the late entrants** who are forced to buy near the top of the bonding curve. In other words, the bonding curve phase itself becomes a PVP arena: the steep 13× ascent guarantees that whoever gets in early — especially with automation — can extract value from those who arrive later. Late participants are effectively providing exit liquidity to the early snipers. [](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#the-pve-curve-curve_rh_bnb_1x5_32bnb) The PVE Curve: `CURVE_RH_BNB_1X5_32BNB` ------------------------------------------------------------------------------------------------------------------------------------------------- The PVE curve redesigns the bonding curve to **flatten the price increase** during the bonding phase, turning it from a PVP arena back into a PVE process. Instead of a 13× price jump, the price increases by only **1.5×** (a 50% increase) from launch to graduation. ### [](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#why-1.5-eliminates-the-sniper-advantage) Why 1.5× Eliminates the Sniper Advantage The key insight is about **profit ceiling**: * **13× curve**: An early sniper can buy at the bottom and sell to a latecomer at up to 13× the price. This massive profit margin makes it worthwhile to deploy bots, snipers, and MEV infrastructure to抢购 tokens at launch. * **1.5× curve**: The maximum price difference between the first bonding-curve buyer and the last is only **50%**. You simply **cannot achieve very large profits** by sniping the launch and dumping on latecomers — the curve doesn't give you the 13× multiplier to exploit. With the profit ceiling compressed to 1.5×, the incentive to deploy snipers and bots largely disappears. There is no longer a massive arbitrage between early and late bonding-curve buyers. Instead of racing to snipe the launch, participants are encouraged to **take their time and build a position gradually** during the bonding phase, because there is no steep cliff to front-run. ### [](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#design-goals) Design Goals Goal How it's achieved **Reduce bonding-phase PVP** Lower price ratio: 1.5× instead of 13× — no large profit for snipers **Higher launch FDV** The curve starts at a higher initial price, so the token is never "too cheap" at launch **Raise more reserve** 32 BNB net graduation (vs 16 BNB on the original), providing deeper DEX liquidity on migration **Predictable pricing** Still a deterministic bonding curve — no liquidity manipulation possible **Scope:** The PVE curve is currently applied only to **non-tax tokens** that migrate to **PancakeSwap V4**. Tax tokens (which use a different migrator and fee distribution path) are unaffected and continue to use the original `CURVE_RH_BNB`. ### [](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#curve-parameters) Curve Parameters The PVE curve uses the same constant-product family as all Flap bonding curves: (x+h)(y+r)\=k(x + h)(y + r) = k(x+h)(y+r)\=k Parameter Original (`CURVE_RH_BNB`) PVE (`CURVE_RH_BNB_1X5_32BNB`) `r` (virtual quote reserve) 6.14 **142.38367176** `h` (virtual token reserve) 107,036,752 **3,359,591,794** `k` (invariant) 6,797,205,657.28 **620,734,687,004.49** Price ratio (graduation / launch) 13× **1.5×** Net BNB at graduation (80%) 16 BNB **32 BNB** Graduation threshold 80% (800M / 1B) 80% (800M / 1B) **Key design notes:** * The price ratio is determined solely by the virtual reserves: $\\text{ratio} = \\left(\\frac{S\_{max} + h}{S\_{max} + h - x\_0}\\right)^2$ where $x\_0 = 800{,}000{,}000$ (the graduation supply). Setting $h = 3{,}359{,}591{,}794$ yields exactly 1.5×. * The net graduation reserve $R(x\_0) = 32$ BNB is achieved by calibrating $r$ and $k$ together. With $r = 142.38367176$ and $k = r \\times (S\_{max} + h)$, the curve satisfies $R(0) = 0$ (no reserve at launch) and $R(800M) = 32$ BNB exactly. * The graduation threshold remains at 80% — the token migrates to DEX when 80% of the total supply has been purchased from the bonding curve. [](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#behavior-comparison) Behavior Comparison ------------------------------------------------------------------------------------------------------------ The following graph shows the original 13× curve alongside the new PVE 1.5× curve. All data comes from BSC mainnet fork simulation (not pure math), at a BNB price of $580. ![PVE curve comparison: original 13x vs new 1.5x 32 BNB](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252Fgit-blob-54d31d1eedbec5f2e5caa9085721441da19674d4%252Fpve-curve-comparison.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=7d3df745&sv=2) Comparison of the original 13× bonding curve (red) and the new PVE 1.5× curve (green). Top: full range including DEX phase. Bottom: bonding curve phase only, with critical points marked. ### [](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#simulation-results) Simulation Results Curve BNB at Migration Launch FDV Migration FDV (80%) **Original** (`CURVE_RH_BNB`) 16.0 BNB 7.5 BNB / $4.3k 70.8 BNB / $41.1k **PVE** (`CURVE_RH_BNB_1X5_32BNB`) 32.0 BNB 33.1 BNB / $19.2k 48.8 BNB / $28.3k ### [](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#how-the-pve-curve-reduces-bonding-phase-pvp) How the PVE Curve Reduces Bonding-Phase PVP **1\. Compressed profit ceiling (1.5× instead of 13×)** On the original curve, the price increases by 13× from the first bonding-curve buy to graduation. A buyer at step 1 pays 13× less than a buyer at step 16. This 13× asymmetry is the root cause of PVP: it makes sniping the launch extremely profitable, so participants deploy bots and MEV to抢购 tokens early and dump them on latecomers. On the PVE 1.5× curve, the price increases by only **50%** over the entire bonding phase. A buyer at step 1 pays only 1.5× the price of a buyer at step 32. The profit that an early sniper can extract from a late buyer is capped at 50% — not enough to justify the cost and risk of running snipers and bots. The PVP dynamic collapses. **2\. No more "cheap launch" sniping** The original curve launches at a very low FDV (~$4.3k), making the token extremely cheap at launch — the perfect target for snipers. The PVE curve launches at a higher FDV (~$19.2k), establishing a healthier initial valuation that removes the "penny token" sniping opportunity from the start. **3\. Participants build positions gradually** Because there is no steep price cliff to front-run, participants are no longer pressured to race into the bonding curve at launch. They can take their time, evaluate the token, and build a position gradually over the bonding phase. The bonding curve behaves more like an "environment" (PVE) — a predictable, fair pricing mechanism — rather than a PVP arena where speed and automation determine who extracts value from whom. **4\. Deeper DEX liquidity on migration** The PVE curve raises 32 BNB net before graduation (vs 16 BNB on the original). This means the token migrates to DEX with **twice the liquidity**, reducing slippage and making post-graduation trading healthier for everyone. [](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#summary) Summary ------------------------------------------------------------------------------------ Property Original (13×) PVE (1.5×, 32 BNB) Price ratio during bonding 13× **1.5×** Bonding-phase PVP High — sniping is profitable **Low — no profit cliff to exploit** Launch FDV ~$4.3k ~$19.2k Migration FDV ~$41k ~$28k Net BNB raised (DEX liquidity) 16 BNB **32 BNB** Graduation threshold 80% 80% The PVE curve transforms the bonding curve phase from a high-PVP arena — where snipers and bots front-run the 13× price ramp to extract value from latecomers — into a low-PVP environment where the 1.5× price ceiling makes sniping unprofitable. Participants are encouraged to build positions gradually rather than racing to snipe the launch, and the doubled reserve (32 BNB) provides healthier DEX liquidity on migration. [PreviousPreview](https://docs.flap.sh/flap/developers/preview) [NextFlap AI Oracle](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle) Last updated 16 days ago * [Background: PVE vs PVP in the Bonding Curve](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#background-pve-vs-pvp-in-the-bonding-curve) * [The Problem: A 13× Bonding Curve is High-PVP](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#the-problem-a-13-bonding-curve-is-high-pvp) * [The PVE Curve: CURVE\_RH\_BNB\_1X5\_32BNB](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#the-pve-curve-curve_rh_bnb_1x5_32bnb) * [Why 1.5× Eliminates the Sniper Advantage](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#why-1.5-eliminates-the-sniper-advantage) * [Design Goals](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#design-goals) * [Curve Parameters](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#curve-parameters) * [Behavior Comparison](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#behavior-comparison) * [Simulation Results](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#simulation-results) * [How the PVE Curve Reduces Bonding-Phase PVP](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#how-the-pve-curve-reduces-bonding-phase-pvp) * [Summary](https://docs.flap.sh/flap/developers/preview/beta-pve-curve#summary) --- # Flap Tax Vault | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault.md) . [](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#table-of-contents) Table of Contents ----------------------------------------------------------------------------------------------------------------- * [Overview](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#overview) * [VaultPortal](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#vaultportal) * [Deployed Addresses](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#deployed-addresses) * [Get Vault Info](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#get-vault-info) * [Get Audit Reports for A token](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#get-audit-reports-for-a-token) * [Launch A Token With A Vault](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#launch-a-token-with-a-vault) [](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#overview) Overview ----------------------------------------------------------------------------------------------- When you use a smart contract as the "Funds Recipient Wallet", our system will try to parse it as a `vault`. ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252FA6wu9nXyZMOsoNAt3IgO%252Fimage.png%3Falt%3Dmedia%26token%3D956f3ffe-53a4-418f-94c7-43bb68a9da34&width=768&dpr=3&quality=100&sign=d4ac14db&sv=2) If you implement your smart contract following our Vault Specification, your Vault's information can be shown on our website. ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252FhIYk0hlhj6pK0ExMueWT%252Fimage.png%3Falt%3Dmedia%26token%3Dcf218161-b486-4c8d-b646-f9fb7815f422&width=768&dpr=3&quality=100&sign=21407c6b&sv=2) Furthermore, if your vault is verified by our team or our auditing partners, your vault's "unverified" risk level can be updated to the corresponding risk level based on the audit results. And all audit reports will be uploaded to ipfs and available on our website for users to check. Users will be able to do their own research on the vaults before investing in your token. You can now create and deploy your own tax vaults **without any permission or registration**. Users can also use any vault they want when launching tokens. Vault factories no longer need to be registered in the VaultPortal before they can be used. See the [Vault & VaultFactory specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification) page for full details on how to build your own vaults and vault factories. If you are a developer, you can build a vault factory that deploys vaults following our Vault Specification. Once your vault factory is verified by our team or our auditing partners, all vaults deployed from your factory will have the same risk level as your factory. This way, you don't need to submit each vault for verification individually. Besides, you can charge a fee for each vault deployed from your factory to build your business upon Flap. Please refer to the [Vault & VaultFactory specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification) page for more details. [](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#vaultportal) VaultPortal ----------------------------------------------------------------------------------------------------- The `VaultPortal` is a registry that manages vault factories and their deployed vaults. It provides a unified interface for interacting with vaults across different chains. ### [](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#deployed-addresses) Deployed Addresses Chain VaultPortal Address BNB mainnet 0x90497450f2a706f1951b5bdda52B4E5d16f34C06 BNB testnet 0x027e3704fC5C16522e9393d04C60A3ac5c0d775f The ABI of the VaultPortal contract is as follows: 0B [vaultportal.json](https://2671086575-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5KujUBwRoHVjn8OZEgtZ%2Fuploads%2Fgit-blob-e69de29bb2d1d6434b8b29ae775ad8c2e48c5391%2Fvaultportal.json?alt=media) Download[Open](https://2671086575-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5KujUBwRoHVjn8OZEgtZ%2Fuploads%2Fgit-blob-e69de29bb2d1d6434b8b29ae775ad8c2e48c5391%2Fvaultportal.json?alt=media) ### [](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#get-vault-info) Get Vault Info We have two methods for fetching the vault info of a token: * `tryGetVault(address taxToken)` : This method first tries to find the vault created through the VaultPortal. If not found, it will try to find the vault by searching all registered vault factories and manually verified vaults. **You should use this method for most cases to avoid unnecessary reverts.** * `getVault(address taxToken)` : This method returns vault that created through the VaultPortal. ### [](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#get-audit-reports-for-a-token) Get Audit Reports for A token For each token, you can get its audit reports with pagination support using the getAuditReports method. If the token itself has no audit reports, it will fall back to its vault's factory's audit reports. ### [](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#launch-a-token-with-a-vault) Launch A Token With A Vault How to launch a token using a vault? Vault is a system built outside of the main Flap Bonding Curve Protocol. To launch a token with a vault, you need to use the `VaultPortal` contract: The parameters are similar to the `newTokenV5` of the `Portal` contract, with two additional parameters at the end: `vaultFactory` and `vaultData`. [PreviousVault & VaultFactory Specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification) [NextGift Vault (FlapXVault)](https://docs.flap.sh/flap/developers/vault-developers/gift-vault) Last updated 4 months ago * [Table of Contents](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#table-of-contents) * [Overview](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#overview) * [VaultPortal](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#vaultportal) * [Deployed Addresses](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#deployed-addresses) * [Get Vault Info](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#get-vault-info) * [Get Audit Reports for A token](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#get-audit-reports-for-a-token) * [Launch A Token With A Vault](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault#launch-a-token-with-a-vault) Copy /// @notice Complete vault information returned to external callers /// @dev This struct is used for view functions and includes human-readable description /// @param vault The address of the vault contract /// @param vaultFactory The address of the vault factory that created this vault (zero address if unknown) /// @param description Human-readable description of the vault's purpose and functionality /// @param isOfficial Whether this vault is marked as official/endorsed by the protocol /// @param riskLevel The risk level classification from audit struct VaultInfo { address vault; address vaultFactory; string description; bool isOfficial; RiskLevel riskLevel; } /// @notice Get the complete vault information for a tax token /// @dev Reverts if no vault is found for the given tax token /// @dev Attempts to fetch the vault's description by calling its description() function /// @param taxToken The address of the tax token /// @return info The VaultInfo struct containing complete vault details function getVault(address taxToken) external view returns (VaultInfo memory info); /// @notice Attempt to get vault information for a tax token without reverting /// @dev First checks the internal mapping, then falls back to searching the Portal /// @dev For fallback results, isOfficial and isVerified will always be false /// @param taxToken The address of the tax token /// @return found Whether a vault was found for this tax token /// @return info The VaultInfo struct (empty if not found) function tryGetVault(address taxToken) external view returns (bool found, VaultInfo memory info); Copy /// @notice Audit report for a tax token /// @param auditor The address of the auditor who submitted this report /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report document struct AuditReport { address auditor; RiskLevel riskLevel; string ipfsCid; } /// @notice Get recent audit reports for a tax token with pagination /// @dev Returns reports starting from the most recent (end of array) /// @dev If the token has no audit reports, falls back to its factory's audit reports /// @param taxToken The address of the tax token /// @param offset The number of reports to skip from the end (0 = most recent) /// @param limit The maximum number of reports to return /// @return reports The array of audit reports /// @return total The total number of audit reports for this token function getAuditReports(address taxToken, uint256 offset, uint256 limit) external view returns (AuditReport[] memory reports, uint256 total); Copy pragma solidity ^0.8.13; import {IPortalTypes} from "./IPortal.sol"; /// @title IVaultPortalTypes /// @notice Type definitions for the VaultPortal contract /// @dev Contains all structs and custom types used by VaultPortal interface IVaultPortalTypes { /// @notice Parameters required to create a new tax token with an associated vault /// @dev All parameters are passed as a single struct to avoid stack-too-deep errors /// @param name The name of the tax token (e.g., "MyToken") /// @param symbol The symbol of the tax token (e.g., "MTK") /// @param meta Metadata URI or string for additional token information /// @param dexThresh The DEX supply threshold type /// @param salt A unique salt for deterministic address generation (must produce vanity suffix) /// @param taxRate The tax rate in basis points (e.g., 100 = 1%, max 1000 = 10%) /// @param migratorType The migrator type (see MigratorType enum) /// @param quoteToken The token used for initial liquidity (address(0) for native token) /// @param quoteAmt The amount of quote token to provide as initial liquidity /// @param permitData The optional permit data for the quote token /// @param extensionID The ID of the extension to be used for the new token if not zero /// @param extensionData Additional extension specific data /// @param dexId The preferred DEX ID for the token /// @param lpFeeProfile The preferred V3 LP fee profile for the token /// @param taxDuration Tax duration in seconds (max: 100 years) /// @param antiFarmerDuration Anti-farmer duration in seconds (max: 1 year) /// @param mktBps Market allocation basis points (to beneficiary) /// @param deflationBps Deflation basis points (burned) /// @param dividendBps Dividend basis points (to dividend contract) /// @param lpBps Liquidity provision basis points (LP to dead address) /// @param minimumShareBalance Minimum balance for dividend eligibility /// @param vaultFactory The address of the vault factory to use for creating the vault /// @param vaultData Encoded data specific to the vault type being created struct NewTaxTokenWithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; uint16 taxRate; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.DEXId dexId; IPortalTypes.V3LPFeeProfile lpFeeProfile; uint64 taxDuration; uint64 antiFarmerDuration; uint16 mktBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; uint256 minimumShareBalance; address vaultFactory; bytes vaultData; } } --- # Flap Trigger Service | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/preview/flap-trigger-service.md) . [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#overview) Overview -------------------------------------------------------------------------------------------- FlapTriggerService is a decentralized on-chain scheduler that allows smart contracts to request delayed or immediate function callbacks executed by a trusted backend. It bridges on-chain logic with off-chain coordination, enabling complex time-sensitive operations without requiring the caller to manage execution timing directly. **Deployed addresses:** Network Address Max Gas Limit BSC Mainnet `0xcf4EE25035CF883895110f367F5BA8172416a7F9` 2,000,000 BSC Testnet `0x560E9830926C9e0EB98a59c6b9902383Fc0D9Eb2` 2,000,000 **Max Gas Limit:** The maximum gas limit supported for a `trigger` callback. **Primary use cases:** * Time-delayed operations (vesting unlocks, periodic distributions, deferred settlements) * Backend-coordinated operations requiring MEV protection * Operations that need external computation before on-chain execution ### [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#how-it-works) How it works **Step by step:** 1. The requester contract calls `requestTrigger()`, paying the required gas fee and specifying an `executeAfter` timestamp (or `0` for immediate execution). 2. The service records the request and emits a `FlapTriggerRequested` event. 3. The off-chain backend monitors for these events and indexes all pending requests. 4. When the scheduled time arrives, the backend submits a `trigger(requestId)` transaction via an MEV-protected RPC (e.g., Flashbots, BloXroute). 5. FlapTriggerService calls back `requester.trigger(requestId)` with a bounded gas limit. 6. The requester's callback uses the `requestId` to identify and execute the intended operation. ### [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#timing-guarantees) Timing guarantees `executeAfter` is a lower bound, not a hard deadline. The service only guarantees that execution happens **after** `executeAfter`. Integrators **must** assume there can be an unpredictable delay due to: * Network congestion * Backend processing latency * Block inclusion delays * MEV protection overhead Requester contracts **must** be designed to handle late execution gracefully and **must not** rely on execution at a precise time. * * * [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#trigger-pricing) Trigger pricing ---------------------------------------------------------------------------------------------------------- A fixed native-currency fee (BNB on BSC) is charged per trigger request. The current price is **0.0002 BNB per request**. This fee covers: * Gas costs for the backend's `trigger()` transaction * Gas costs for the callback to the requester contract * A small service fee **Getting the current fee:** The fee is paid upfront when calling `requestTrigger()` as `msg.value`. Excess payment above the required fee is accumulated as protocol fees — there is no refund for overpayment. **Callback gas limit:** Each trigger callback is forwarded at most `getMaxCallbackGas()` gas. Requester contracts must ensure their `trigger()` callback completes within this limit. Exceeding the limit will cause the callback to fail (status becomes `FAILED`). **Failed callbacks and retry:** If a callback fails (e.g., out of gas or revert), the request status is set to `FAILED`. Anyone can retry a failed request by calling `retryTrigger(requestId)` — this forwards all available gas to the callback, allowing the caller to supply sufficient gas. The fee stored in the request is transferred to the fee receiver on successful retry. * * * [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#how-to-integrate) How to integrate ------------------------------------------------------------------------------------------------------------ ### [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#step-1-import-the-interfaces) Step 1 — Import the interfaces ### [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#step-2-implement-itriggerreceiver) Step 2 — Implement `ITriggerReceiver` Your contract must implement the `trigger(uint256 requestId)` callback. This function is called by FlapTriggerService when the scheduled time has passed. **Security requirements for the callback:** * **Must** validate `msg.sender == address(triggerService)` * **Should** implement a reentrancy guard if performing external calls or state changes * **Must** complete within `getMaxCallbackGas()` gas * **Must not** assume execution happens exactly at `executeAfter` — always assume potential delay ### [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#step-3-schedule-a-trigger) Step 3 — Schedule a trigger Call `requestTrigger()` with the desired `executeAfter` timestamp. Store the returned `requestId` to map it back to the intended operation in your callback. ### [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#pseudo-code-example) Pseudo-code example ### [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#scheduling-for-immediate-execution) Scheduling for immediate execution Pass `0` as `executeAfter` to request execution as soon as the backend processes the event: ### [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#querying-request-status) Querying request status ### [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#retrying-a-failed-trigger) Retrying a failed trigger If a callback fails (e.g., the callback used more gas than the limit), anyone can retry: * * * [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#reference) Reference ---------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#full-interface) Full interface [PreviousFlap Candy Box](https://docs.flap.sh/flap/developers/preview/flap-candy-box) [NextWorld Cup Resolver](https://docs.flap.sh/flap/developers/preview/worldcup-resolver) Last updated 2 months ago * [Overview](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#overview) * [How it works](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#how-it-works) * [Timing guarantees](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#timing-guarantees) * [Trigger pricing](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#trigger-pricing) * [How to integrate](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#how-to-integrate) * [Step 1 — Import the interfaces](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#step-1-import-the-interfaces) * [Step 2 — Implement ITriggerReceiver](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#step-2-implement-itriggerreceiver) * [Step 3 — Schedule a trigger](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#step-3-schedule-a-trigger) * [Pseudo-code example](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#pseudo-code-example) * [Scheduling for immediate execution](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#scheduling-for-immediate-execution) * [Querying request status](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#querying-request-status) * [Retrying a failed trigger](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#retrying-a-failed-trigger) * [Reference](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#reference) * [Full interface](https://docs.flap.sh/flap/developers/preview/flap-trigger-service#full-interface) Copy uint256 fee = IFlapTriggerService(triggerService).getFee(); Copy uint256 maxGas = IFlapTriggerService(triggerService).getMaxCallbackGas(); Copy import { IFlapTriggerService } from "src/misc/IFlapTriggerService.sol"; import { ITriggerReceiver } from "src/misc/IFlapTriggerService.sol"; Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import { IFlapTriggerService, ITriggerReceiver } from "src/misc/IFlapTriggerService.sol"; import { ReentrancyGuard } from "@openzeppelin/utils/ReentrancyGuard.sol"; contract MyScheduledVault is ITriggerReceiver, ReentrancyGuard { IFlapTriggerService public immutable triggerService; struct PendingAction { address recipient; uint256 amount; } mapping(uint256 => PendingAction) private pendingActions; constructor(address _triggerService) { triggerService = IFlapTriggerService(_triggerService); } /// @notice Schedule a delayed distribution 1 day from now. function scheduleDistribution(address recipient, uint256 amount) external payable { // 1. Get the required fee uint256 fee = triggerService.getFee(); // 2. Request the trigger (executeAfter = 1 day from now) uint256 requestId = triggerService.requestTrigger{value: fee}( uint64(block.timestamp + 1 days) ); // 3. Store the action data keyed by requestId pendingActions[requestId] = PendingAction({ recipient: recipient, amount: amount }); } /// @notice Called back by FlapTriggerService after executeAfter has passed. function trigger(uint256 requestId) external nonReentrant override { // SECURITY: only the trigger service can call this require(msg.sender == address(triggerService), "Only trigger service"); PendingAction memory action = pendingActions[requestId]; require(action.recipient != address(0), "Unknown requestId"); // Clean up before external call (checks-effects-interactions) delete pendingActions[requestId]; // Execute the intended operation // NOTE: Execution may happen well after the scheduled time — design accordingly _distribute(action.recipient, action.amount); } function _distribute(address recipient, uint256 amount) internal { // ... distribution logic ... } } Copy uint256 requestId = triggerService.requestTrigger{value: fee}(0); Copy // Get details about a specific request IFlapTriggerService.TriggerRequest memory req = triggerService.getRequest(requestId); // req.status: PENDING (0), EXECUTED (1), FAILED (2) // req.executeAfter: scheduled time // req.feePaid: fee paid (wei) // Check if a request is ready to execute right now bool ready = triggerService.isRequestReady(requestId); // Get all requests by your contract (paginated, newest first) (IFlapTriggerService.TriggerRequest[] memory page, uint256 total) = triggerService.getRequestsByRequesterPaginated(address(this), 0, 20); Copy triggerService.retryTrigger(requestId); Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; interface IFlapTriggerService { // ── Enums ──────────────────────────────────────────────────────────────── enum TriggerStatus { PENDING, // 0 — Request created, waiting to be executed EXECUTED, // 1 — Successfully executed by the backend FAILED // 2 — Execution attempted but the callback reverted } // ── Structs ────────────────────────────────────────────────────────────── /// @notice Packed into two 32-byte storage slots. /// Slot 0: status (8 bits) | executeAfter (64 bits) | requester (160 bits) | 24 bits reserved /// Slot 1: feePaid (128 bits) struct TriggerRequest { address requester; // 160 bits — address that requested the trigger uint64 executeAfter; // 64 bits — Unix timestamp lower bound for execution TriggerStatus status; // 8 bits — current lifecycle status uint128 feePaid; // 128 bits — native fee paid in wei (slot 1) } // ── Events ─────────────────────────────────────────────────────────────── /// @notice Emitted when a new trigger request is created. event FlapTriggerRequested( uint256 requestId, address indexed requester, uint64 executeAfter, uint256 gasFeesPaid ); /// @notice Emitted when a trigger execution is attempted (success or failure). event FlapTriggerExecuted(uint256 requestId, bool success, bytes data); /// @notice Emitted when a trigger is skipped (invalid ID, wrong status, or not yet due). event FlapTriggerSkipped(uint256 requestId, string reason); /// @notice Emitted when the required gas fee is updated by admin. event FlapTriggerGasFeeUpdated(uint256 oldFee, uint256 newFee); /// @notice Emitted when the maximum callback gas limit is updated by admin. event FlapTriggerMaxCallbackGasUpdated(uint256 oldLimit, uint256 newLimit); // ── Errors ─────────────────────────────────────────────────────────────── /// @notice msg.value is below the required fee. error InsufficientGasFee(uint256 required, uint256 provided); /// @notice Request is not in PENDING status. error InvalidRequestStatus(uint256 requestId, TriggerStatus currentStatus); /// @notice block.timestamp is before executeAfter. error TooEarly(uint256 requestId, uint64 executeAfter, uint256 currentTime); /// @notice The provided requestId does not exist. error InvalidRequestId(uint256 requestId); /// @notice Caller does not have TRIGGER_ROLE. error OnlyTriggerRole(); /// @notice Admin tried to set an invalid gas fee (e.g., zero). error InvalidGasFee(); /// @notice Admin tried to set an invalid gas limit (e.g., zero or too high). error InvalidGasLimit(); /// @notice Fee receiver address is invalid (e.g., zero address). error InvalidFeeReceiver(); /// @notice A retried trigger callback reverted. error RetryFailed(uint256 requestId, bytes data); /// @notice msg.value overflows uint128 and cannot be stored as feePaid. error FeePaidOverflow(uint256 provided); // ── Write methods ──────────────────────────────────────────────────────── /// @notice Request a trigger callback at or after a specified time. /// @param executeAfter Unix timestamp after which execution may happen. Pass 0 for immediate. /// @return requestId Unique ID for this trigger request. /// Requirements: msg.value >= getFee(); if executeAfter > 0, must be >= block.timestamp. function requestTrigger(uint64 executeAfter) external payable returns (uint256 requestId); /// @notice Execute a single pending trigger (backend only, requires TRIGGER_ROLE). /// @param requestId The ID of the request to execute. function trigger(uint256 requestId) external; /// @notice Execute multiple pending triggers in one transaction (backend only, requires TRIGGER_ROLE). /// @param requestIds Array of request IDs to execute. Invalid or already-done IDs are skipped. function triggerMultiple(uint256[] calldata requestIds) external; /// @notice Retry a previously failed trigger request. Callable by anyone. /// Forwards all available gas; reverts with RetryFailed on failure. /// @param requestId The ID of the FAILED request to retry. function retryTrigger(uint256 requestId) external; // ── View methods ───────────────────────────────────────────────────────── /// @notice The native-currency fee (wei) required as msg.value for requestTrigger(). function getFee() external view returns (uint256 gasFee); /// @notice Maximum gas forwarded to a requester's callback. Callbacks must fit within this. function getMaxCallbackGas() external view returns (uint256 maxGas); /// @notice Get details about a single trigger request. Reverts for unknown IDs. function getRequest(uint256 requestId) external view returns (TriggerRequest memory request); /// @notice Total number of trigger requests ever created (= next request ID). function getRequestCount() external view returns (uint256 count); /// @notice True if the request exists, is PENDING, and block.timestamp >= executeAfter. function isRequestReady(uint256 requestId) external view returns (bool ready); /// @notice Fetch multiple requests by ID in one call. Unknown IDs return zero-initialised structs. function getRequests(uint256[] calldata requestIds) external view returns (TriggerRequest[] memory requests); /// @notice Paginated list of all requests, newest first (descending by ID). /// @param offset Requests to skip from the newest. /// @param limit Maximum number to return. function getRequestsPaginated(uint256 offset, uint256 limit) external view returns (TriggerRequest[] memory requests, uint256 total); /// @notice Paginated list of requests for a specific requester address, newest first. /// @param requester Address whose requests to query. /// @param offset Requests to skip from the newest. /// @param limit Maximum number to return. function getRequestsByRequesterPaginated(address requester, uint256 offset, uint256 limit) external view returns (TriggerRequest[] memory requests, uint256 total); } /// @notice Interface that requester contracts must implement to receive trigger callbacks. interface ITriggerReceiver { /// @notice Called by FlapTriggerService when a scheduled trigger executes. /// @dev MUST validate msg.sender == triggerService address. /// SHOULD implement reentrancy guard. /// MUST complete within getMaxCallbackGas(). /// MUST NOT assume execution at exactly executeAfter — delays are possible. /// @param requestId ID of the trigger request being executed. function trigger(uint256 requestId) external; } --- # Gift Vault V2 | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2.md) . [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#overview) Overview ---------------------------------------------------------------------------------------------- Gift Vault V2 allows you to assign an X (Twitter) account as the **Gift Owner** for a token launched through `VaultPortal`. The gift owner can prove control of that X account and direct the vault's fee flow to any EVM address of their choice. The core mechanism is: 1. **Assign Gift Owner**: The X account you specify becomes the gift owner for the vault. 2. **Accumulating Phase**: After launch, the vault first stays in `ACCUMULATING` mode and holds incoming native BNB fees. 3. **Flexible Beneficiary**: Once the gift owner proves control, the vault enters `STREAMING` mode and routes fees to the chosen EVM address. 4. **7-Day Grace Period**: If the gift owner does not take over within the grace period, the gift flow is closed. 5. **Fallback: Holder Dividends**: After timeout, the vault enters `DIVIDEND` mode and routes value to token holders. Compared with the legacy [Gift Vault (FlapXVault)](https://docs.flap.sh/flap/developers/vault-developers/gift-vault) , Gift Vault V2 mainly brings: * two V2 product options * a new vault lifecycle: `ACCUMULATING -> STREAMING -> DIVIDEND` * new launch constraints for the V2 product line * holder-dividend fallback instead of buyback & burn > Important: `VaultPortal` here is **not** the same contract as the Flap Bonding Curve Protocol `Portal`. [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#legacy-gift-vault-vs-gift-vault-v2) Legacy Gift Vault vs Gift Vault V2 -------------------------------------------------------------------------------------------------------------------------------------------------- The old `gift-vault.md` document is for the legacy Gift Vault flow. This document is for the new Gift Vault V2 product line. For integrators, the practical differences are: Topic Gift Vault V1 Gift Vault V2 Product options one legacy Gift Vault flow two product options: `GiftV4VaultFactoryV2` and `GiftTaxVaultFactoryV2` API integration existing API same API, request format, and response format as V1 Fee routing lifecycle legacy gift flow `ACCUMULATING -> STREAMING -> DIVIDEND` Timeout fallback buyback & burn holder dividends What you need to update legacy integration mainly factory selection and launch-parameter validation So if you already support Gift Vault V1, the main work for V2 is not rewriting your API integration. The main work is choosing the correct V2 factory and respecting the V2 launch constraints. [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#network-details) Network Details ------------------------------------------------------------------------------------------------------------ ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#bnb-smart-chain-mainnet) BNB Smart Chain Mainnet * `GiftV4VaultFactoryV2`: `0x6909aD1822Ece349CDDAb98E6F62EeeD9fAa2e10` * `GiftTaxVaultFactoryV2`: `0xFb7ccc4Fd09Da5b7016A18d51e227Af4ABE53f44` ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#bnb-smart-chain-testnet) BNB Smart Chain Testnet * `GiftV4VaultFactoryV2`: `0xaa00b4CeFeE8b4BF7D2E23C21ae82cfC9AFC9D67` * `GiftTaxVaultFactoryV2`: `0x2eDD880AB36b07bD030BDa28A13c3E9148C11622` [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#product-matrix) Product matrix ---------------------------------------------------------------------------------------------------------- Gift Vault V2 has two product options. The difference is mainly the launch constraints of the selected factory. ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#giftv4vaultfactoryv2) `GiftV4VaultFactoryV2` Use this factory for the zero-tax Gift V4 product. Launch constraints: * `tokenVersion == TOKEN_V3_PERMIT` * `quoteToken == address(0)` * `buyTaxRate == 0` * `sellTaxRate == 0` * `dividendBps == 0` * `dividendToken == address(0)` ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#gifttaxvaultfactoryv2) `GiftTaxVaultFactoryV2` Use this factory for the taxed Gift product. Launch constraints: * `tokenVersion == TOKEN_TAXED_V3` * `quoteToken == address(0)` * `buyTaxRate == sellTaxRate` * `buyTaxRate / sellTaxRate` must both be one of: * `100` (1%) * `200` (2%) * `300` (3%) * `dividendBps == 0` * `dividendToken == address(0)` ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#shared-launch-time-vault-data) Shared launch-time vault data For both Gift Vault V2 product options, the only launch-time vault parameter is the gift owner's X handle. [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#lifecycle) Lifecycle ------------------------------------------------------------------------------------------------ Gift Vault V2 has three states: ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#id-1.-accumulating) 1\. `ACCUMULATING` * initial state after deployment * vault accepts native BNB fees from the token * funds stay inside the vault * waiting for a valid X proof to assign the streaming recipient ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#id-2.-streaming) 2\. `STREAMING` * entered after a valid `manageByProof()` call * all accumulated BNB is flushed immediately to `streamingTarget` * future BNB is forwarded to the same target on receipt * the target can be updated again by a newer valid proof ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#id-3.-dividend) 3\. `DIVIDEND` * entered after `createdAt + timeoutPeriod` if streaming was never activated * sticky once entered * vault wraps native BNB into the dividend token (normally WBNB) * vault deposits the wrapped balance into the token's Dividend contract for holders Default timeout for newly created vaults is `7 days`, configurable at the factory level for future vaults. [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#api-compatibility-with-gift-vault-v1) API compatibility with Gift Vault V1 ------------------------------------------------------------------------------------------------------------------------------------------------------ `gift vault v1` and `gift vault v2` use the same API interface. This means: * the API endpoints are the same * the request format is the same * the response format is the same * existing V1 API integration code can be reused for V2 directly So if you have already integrated the API in [Gift Vault (FlapXVault)](https://docs.flap.sh/flap/developers/vault-developers/gift-vault) , you do not need to change your request / response parsing logic for V2. [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#what-is-different-in-v2) What is different in V2 ---------------------------------------------------------------------------------------------------------------------------- For API integrators, the main V2 differences are: * the factory addresses are different * there are now two V2 product options * launch constraints are different depending on which product you choose * the vault lifecycle is now `ACCUMULATING -> STREAMING -> DIVIDEND` * the fallback behavior is holder dividends instead of buyback & burn [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#integration-notes) Integration notes ---------------------------------------------------------------------------------------------------------------- When integrating Gift Vault V2, the main things you need to do are: 1. choose the correct Gift V2 factory address 2. launch through `VaultPortal` 3. pass the gift owner's `xHandle` in `vaultData` 4. keep using the same API request / response handling as V1 5. update your product selection logic to match the V2 launch constraints ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#launch-data) Launch data For Gift Vault V2, the only launch-time vault parameter is the gift owner's X handle. ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#product-selection) Product selection * use `GiftV4VaultFactoryV2` for the zero-tax Gift V4 product * use `GiftTaxVaultFactoryV2` for the taxed Gift product For `GiftTaxVaultFactoryV2`, buy tax and sell tax should use the same preset, and the supported presets are `1%`, `2%`, or `3%`. [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#summary) Summary -------------------------------------------------------------------------------------------- If you already support Gift Vault V1 API integration, then Gift Vault V2 is mainly a product / factory upgrade, not an API upgrade. In most cases, you only need to: * switch to the correct V2 factory address * choose the correct V2 product type * respect the new launch constraints * keep the existing API request / response integration unchanged [PreviousGift Vault (FlapXVault)](https://docs.flap.sh/flap/developers/vault-developers/gift-vault) [NextBuilding Upgradeable Vaults — Beacon Proxy Pattern](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault) Last updated 1 month ago * [Overview](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#overview) * [Legacy Gift Vault vs Gift Vault V2](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#legacy-gift-vault-vs-gift-vault-v2) * [Network Details](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#network-details) * [BNB Smart Chain Mainnet](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#bnb-smart-chain-mainnet) * [BNB Smart Chain Testnet](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#bnb-smart-chain-testnet) * [Product matrix](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#product-matrix) * [GiftV4VaultFactoryV2](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#giftv4vaultfactoryv2) * [GiftTaxVaultFactoryV2](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#gifttaxvaultfactoryv2) * [Shared launch-time vault data](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#shared-launch-time-vault-data) * [Lifecycle](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#lifecycle) * [1\. ACCUMULATING](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#id-1.-accumulating) * [2\. STREAMING](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#id-2.-streaming) * [3\. DIVIDEND](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#id-3.-dividend) * [API compatibility with Gift Vault V1](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#api-compatibility-with-gift-vault-v1) * [What is different in V2](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#what-is-different-in-v2) * [Integration notes](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#integration-notes) * [Launch data](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#launch-data) * [Product selection](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#product-selection) * [Summary](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2#summary) --- # Flap Candy Box | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/preview/flap-candy-box.md) . [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#overview) Overview -------------------------------------------------------------------------------------- Flap Candy Box is an on-chain data oracle that delivers paginated, addressable lists to consumer contracts via a request-response callback pattern. It serves as an infrastructure layer for programmable, transparent, and automated reward distribution — vaults and other smart contracts can access dynamic CandyBox lists on-chain and act on them automatically, without maintaining the lists themselves. Each subscription ID represents a list — it could be a static snapshot, a dynamically refreshed dataset, or any curated set of addresses. The first subscription is provided by Flap and refreshed every 24 hours, featuring the most active users from the previous day's trading activity on Flap. More subscriptions will be added over time. > Want to build your own custom list? [Reach out to us](https://flap.sh/) > — we'd love to explore it with you. **Deployed addresses:** Network Address Max Callback Gas BSC Mainnet `0x6255fbd731272a517022e99f6CaCf6A5De9414Ee` 2,000,000 BSC Testnet `0x8e6C16Bf07022a7Da9398B543f38846E9355Bd70` 2,000,000 **Max Callback Gas:** The maximum gas limit supported for an `onDataReceived` callback. ### [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#how-it-works) How it works **Step by step:** 1. The consumer contract calls `requestData()`, paying the subscription fee and specifying the page (`offset`, `limit`) to fetch. 2. The oracle records the request and emits `FlapDataRequested`. 3. The backend queries the previous day's trade data and encodes it as a `ResponseData` struct. 4. The backend calls `fulfillData(requestId, encodedData)` on the oracle. 5. The oracle forwards the data to the consumer via `onDataReceived()` with a bounded gas limit. 6. The oracle emits `FlapDataFulfilled` with the callback outcome. ### [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#request-status-lifecycle) Request status lifecycle If the consumer's `onDataReceived()` reverts, the request status is set to `FAILED`. The oracle operator can retry delivery via `retryFulfillData()` once the consumer issue is resolved. * * * [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#subscriptions) Subscriptions ------------------------------------------------------------------------------------------------ Each subscription defines a dataset, its pricing, and the maximum page size. Field Type Description `subscriptionId` `bytes32` Pass to `requestData()` `description` `string` Human-readable rule `responseStruct` `string` Struct name for decoding `data` in `onDataReceived()` `fee` `uint256` Required `msg.value` per request (wei) `maxLimit` `uint32` Maximum records per page > **Current limit cap:** `maxLimit` is **145** per request. This accounts for consumer contracts that store the returned records on-chain — larger payloads may exceed the callback gas budget. Use pagination for more records. ### [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#available-subscriptions) Available subscriptions Constant `subscriptionId` Description `responseStruct` `FLAP_TRADE_BNB_24H` `keccak256("FLAP_TRADE_BNB_24H")` Addresses based on their trading activity in the previous 24 hours — across both Bonding Curve and DEX — on tokens launched from Flap `ResponseData` Fees may change. Always call `getFee(subscriptionId)` on-chain immediately before sending `requestData()`. * * * [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#how-to-integrate) How to integrate ------------------------------------------------------------------------------------------------------ ### [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#step-1-query-the-fee) Step 1 — Query the fee ### [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#step-2-call-requestdata) Step 2 — Call `requestData()` > Pass `block.timestamp` directly — do not manually compute `startTime`/`endTime`. The backend derives the previous UTC day window from the request timestamp. **Pagination example** — fetching 3000 addresses in pages of 145: ### [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#step-3-implement-the-consumer) Step 3 — Implement the consumer The recommended approach is to inherit `FlapCandyBoxConsumerBase`, which provides the `onlyFlapCandyBox` modifier and hardcoded address resolution automatically. Alternatively, implement `IFlapCandyBoxConsumer` directly and validate `msg.sender` yourself: * * * [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#responsedata-struct) ResponseData struct ------------------------------------------------------------------------------------------------------------ The `data` payload in `onDataReceived()` is ABI-encoded as `ResponseData`. Use `getSubscription(subscriptionId).responseStruct` to confirm the struct name for each subscription. > Use `totalSize` (not `returnedCount`) for proportional distribution calculations across pages. * * * [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#security-checklist) Security checklist ---------------------------------------------------------------------------------------------------------- Item Detail Validate `msg.sender` Use `FlapCandyBoxConsumerBase` (recommended) or `require(msg.sender == address(oracle))` Guard double-delivery Track a `fulfilled` flag per `requestId` and revert on repeat Reentrancy Mark fulfilled **before** any external calls or token transfers Callback gas Keep `onDataReceived()` within `getMaxCallbackGas()`; check with `oracle.getMaxCallbackGas()` Fee freshness Call `getFee()` immediately before sending `requestData()` — fees may change Page size Use `getMaxLimit(subscriptionId)` to check the current cap before submitting * * * [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#reference) Reference ---------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/preview/flap-candy-box#full-interface) Full interface [PreviousFlap AI Oracle](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle) [NextFlap Trigger Service](https://docs.flap.sh/flap/developers/preview/flap-trigger-service) Last updated 6 days ago * [Overview](https://docs.flap.sh/flap/developers/preview/flap-candy-box#overview) * [How it works](https://docs.flap.sh/flap/developers/preview/flap-candy-box#how-it-works) * [Request status lifecycle](https://docs.flap.sh/flap/developers/preview/flap-candy-box#request-status-lifecycle) * [Subscriptions](https://docs.flap.sh/flap/developers/preview/flap-candy-box#subscriptions) * [Available subscriptions](https://docs.flap.sh/flap/developers/preview/flap-candy-box#available-subscriptions) * [How to integrate](https://docs.flap.sh/flap/developers/preview/flap-candy-box#how-to-integrate) * [Step 1 — Query the fee](https://docs.flap.sh/flap/developers/preview/flap-candy-box#step-1-query-the-fee) * [Step 2 — Call requestData()](https://docs.flap.sh/flap/developers/preview/flap-candy-box#step-2-call-requestdata) * [Step 3 — Implement the consumer](https://docs.flap.sh/flap/developers/preview/flap-candy-box#step-3-implement-the-consumer) * [ResponseData struct](https://docs.flap.sh/flap/developers/preview/flap-candy-box#responsedata-struct) * [Security checklist](https://docs.flap.sh/flap/developers/preview/flap-candy-box#security-checklist) * [Reference](https://docs.flap.sh/flap/developers/preview/flap-candy-box#reference) * [Full interface](https://docs.flap.sh/flap/developers/preview/flap-candy-box#full-interface) Copy PENDING → FULFILLED (callback succeeded) → FAILED (callback reverted — eligible for retry by operator) Copy IFlapCandyBox.Subscription[] memory subs = IFlapCandyBox(oracleAddress).getSubscriptions(); Copy uint256 fee = IFlapCandyBox(oracleAddress).getFee(subscriptionId); Copy bytes32 requestId = IFlapCandyBox(oracleAddress).requestData{value: fee}( subscriptionId, uint64(block.timestamp), // backend derives previous day's window from this offset, // zero-based page offset limit, // records per page, must be <= maxLimit "" // extraParams: leave empty ); Copy // Page 0: offset=0, limit=145 // Page 1: offset=145, limit=145 // Page 2: offset=290, limit=145 // ... // Use ResponseData.totalSize from the first callback to calculate how many pages are needed. Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {IFlapCandyBox, FlapCandyBoxConsumerBase} from "src/interfaces/IFlapCandyBox.sol"; contract MyVault is FlapCandyBoxConsumerBase { bytes32 public constant SUBSCRIPTION_BNB = keccak256("FLAP_TRADE_BNB_24H"); mapping(bytes32 => bool) private _fulfilled; function requestStats(uint32 offset, uint32 limit) external payable returns (bytes32 requestId) { IFlapCandyBox oracle = IFlapCandyBox(_getFlapCandyBox()); uint256 fee = oracle.getFee(SUBSCRIPTION_BNB); requestId = oracle.requestData{value: fee}( SUBSCRIPTION_BNB, uint64(block.timestamp), offset, limit, "" ); } // Called by FlapCandyBox — protected by onlyFlapCandyBox modifier from base contract function _onDataReceived( bytes32 subscriptionId, bytes32 requestId, bytes calldata data ) internal override { require(!_fulfilled[requestId], "already fulfilled"); _fulfilled[requestId] = true; // mark before any external calls if (subscriptionId == SUBSCRIPTION_BNB) { IFlapCandyBox.ResponseData memory resp = abi.decode(data, (IFlapCandyBox.ResponseData)); // resp.totalSize — total qualifying addresses across all pages // resp.totalAmount — sum of tradeAmount across ALL matching records (not just this page) // resp.records — address-level records for this page // resp.startTime / resp.endTime — actual data window _handleDistribution(resp); } } function _handleDistribution(IFlapCandyBox.ResponseData memory resp) internal { for (uint256 i = 0; i < resp.records.length; i++) { // distribute proportionally using resp.records[i].wallet and resp.records[i].tradeAmount } } } Copy function onDataReceived(bytes32 subscriptionId, bytes32 requestId, bytes calldata data) external override { require(msg.sender == address(oracle), "Only FlapCandyBox"); // ... } Copy struct AddressRecord { address wallet; uint256 tradeAmount; // 1e18-scaled; BNB subscription → wei, USD subscription → 1e18-scaled USD } struct ResponseData { uint256 totalSize; // total records matching the query across all pages uint256 offset; // zero-based index of the first record in this page uint256 returnedCount; // actual number of records in this page (may be < limit on last page) bool isDesc; // true — records are sorted by tradeAmount descending uint64 startTime; // query window start (Unix timestamp, UTC 00:00:00) uint64 endTime; // query window end (Unix timestamp, UTC 23:59:59) uint256 totalAmount; // sum of tradeAmount across ALL matching records (not just this page) AddressRecord[] records; } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; interface IFlapCandyBox { // ── Enums ──────────────────────────────────────────────────────────────── enum RequestStatus { PENDING, // 0 — created, waiting for fulfiller FULFILLED, // 1 — data delivered and consumer callback succeeded FAILED // 2 — data delivered but consumer callback reverted; eligible for retry } // ── Structs ────────────────────────────────────────────────────────────── struct Subscription { bytes32 subscriptionId; string description; string responseStruct; // name of the ResponseData struct, e.g. "ResponseData" uint256 fee; // required msg.value per requestData() call (wei) uint32 maxLimit; // maximum allowed limit per request bool active; } struct OracleRequest { address consumer; bytes32 subscriptionId; uint64 timestamp; // caller's block.timestamp; backend derives previous day's window uint32 offset; uint32 limit; bytes extraParams; uint128 feePaid; RequestStatus status; } struct AddressRecord { address wallet; uint256 tradeAmount; } struct ResponseData { uint256 totalSize; // total records matching the query across all pages uint256 offset; // zero-based index of the first record in this page uint256 returnedCount; // actual records in this page bool isDesc; // true if sorted by tradeAmount descending uint64 startTime; // query window start (Unix timestamp) uint64 endTime; // query window end (Unix timestamp) uint256 totalAmount; // sum of tradeAmount across ALL matching records (not just this page) AddressRecord[] records; } // ── Events ─────────────────────────────────────────────────────────────── event FlapSubscriptionRegistered(bytes32 indexed subscriptionId, string description, uint256 fee); event FlapSubscriptionFeeUpdated(bytes32 indexed subscriptionId, uint256 oldFee, uint256 newFee); event FlapSubscriptionLimitUpdated(bytes32 indexed subscriptionId, uint32 oldLimit, uint32 newLimit); event FlapDataRequested( bytes32 indexed subscriptionId, bytes32 indexed requestId, address indexed consumer, uint64 timestamp, uint32 offset, uint32 limit, uint256 feePaid, bytes extraParams ); event FlapDataFulfilled(bytes32 indexed subscriptionId, bytes32 indexed requestId, bool success, bytes returnData); event FlapFulfillerUpdated(address indexed fulfiller, bool authorized); // ── Errors ─────────────────────────────────────────────────────────────── error InvalidSubscription(bytes32 subscriptionId); error SubscriptionAlreadyExists(bytes32 subscriptionId); error InsufficientFee(uint256 required, uint256 provided); error LimitExceeded(uint32 requested, uint32 max); error AlreadyFulfilled(bytes32 requestId); error InvalidRequestId(bytes32 requestId); error OnlyFulfiller(); error RequestNotFailed(bytes32 requestId, RequestStatus status); error RetryFailed(bytes32 requestId, bytes returnData); // ── Write ──────────────────────────────────────────────────────────────── /// @notice Submit a data request. Emits FlapDataRequested for the backend to pick up. /// @param subscriptionId Identifies which dataset and pricing rule to query. /// @param timestamp Pass block.timestamp. Backend derives previous day's window from this. /// @param offset Zero-based page offset (first page = 0). /// @param limit Records per page. Must be <= getMaxLimit(subscriptionId). /// @param extraParams Reserved. Pass "". /// @return requestId Store this to correlate the callback. function requestData( bytes32 subscriptionId, uint64 timestamp, uint32 offset, uint32 limit, bytes calldata extraParams ) external payable returns (bytes32 requestId); // ── View ───────────────────────────────────────────────────────────────── function getSubscription(bytes32 subscriptionId) external view returns (Subscription memory); function getSubscriptions() external view returns (Subscription[] memory); function getFee(bytes32 subscriptionId) external view returns (uint256); function getMaxLimit(bytes32 subscriptionId) external view returns (uint32); function getRequest(bytes32 requestId) external view returns (OracleRequest memory); function getMaxCallbackGas() external view returns (uint256); } /// @notice Base contract for FlapCandyBox consumers. /// @dev Inherit this to get address resolution and the onlyFlapCandyBox modifier for free. abstract contract FlapCandyBoxConsumerBase { error FlapCandyBoxConsumerOnlyBox(); error FlapCandyBoxConsumerUnsupportedChain(uint256 chainId); modifier onlyFlapCandyBox() { if (msg.sender != _getFlapCandyBox()) revert FlapCandyBoxConsumerOnlyBox(); _; } /// @notice Returns the FlapCandyBox proxy address for the current chain. function _getFlapCandyBox() internal view virtual returns (address) { uint256 id = block.chainid; if (id == 56) return 0x6255fbd731272a517022e99f6CaCf6A5De9414Ee; // BSC Mainnet if (id == 97) return 0x8e6C16Bf07022a7Da9398B543f38846E9355Bd70; // BSC Testnet revert FlapCandyBoxConsumerUnsupportedChain(id); } function onDataReceived(bytes32 subscriptionId, bytes32 requestId, bytes calldata data) external virtual onlyFlapCandyBox { _onDataReceived(subscriptionId, requestId, data); } function _onDataReceived(bytes32 subscriptionId, bytes32 requestId, bytes calldata data) internal virtual; } /// @notice Minimal interface for consumers that prefer not to use the base contract. interface IFlapCandyBoxConsumer { function onDataReceived(bytes32 subscriptionId, bytes32 requestId, bytes calldata data) external; } --- # Trade Tokens | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens.md) . [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#get-a-quote) Get A Quote ------------------------------------------------------------------------------------------------------------------------- If a token is already migrated, you can still get a quote using the following method. However, we will always use the pool that the token has migrated to, this may not be the best quote. You can also do an off-chain quote to save the RPC calls. Check the end of this document to learn more about the off-chain quote. To get a quote , we call the `quoteExactInput` function: Copy /// @notice Parameters for quoting the output amount for a given input struct QuoteExactInputParams { /// @notice The address of the input token (use address(0) for native asset) address inputToken; /// @notice The address of the output token (use address(0) for native asset) address outputToken; /// @notice The amount of input token to swap (in input token decimals) uint256 inputAmount; } /// @notice Quote the output amount for a given input /// @param params The quote parameters /// @return outputAmount The quoted output amount /// @dev refer to the swapExactInput method for the scenarios function quoteExactInput(QuoteExactInputParams calldata params) external returns (uint256 outputAmount); Note: the `quoteExactInput` method is not a view function, but we don’t need to send a transaction to get the quote (an `eth_call` or the simulation in viem will do the work) . Here are the possible scenarios: * **When the quote token is the native gas token (BNB or ETH):** * buy a token: * `inputToken` is zero address, representing the gas token * `outputToken` is the token you wanna buy * sell a token: * `inputToken` is the token you wanna sell * `outputToken` is zero address, representing the gas token * **When the quote token is not the native gas token (taking USD1 as an example)** : * buy a token with USD1: * `inputToken` is USD1 address * `outputToken` is the token you wanna buy * **buy with native gas token** (only when the token’s `nativeToQuoteSwap` is enabled , check below to inspect if a token’s quote token supports `nativeToQuoteSwap` ): * `inputToken` is zero address, representing the gas token * `outputToken` is the token you wanna buy * under the hood, we will help you swap BNB for for USD1 on PancakeSwap as an intermediate step in the contract. * sell a token: * `inputToken` is the token you wanna sell * `outputToken` is USD1 * Sell directly from token to BNB is also supported, but we will not support this in our UI. [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#swap) Swap ----------------------------------------------------------------------------------------------------------- To swap we call the `swapExactInput` method: This is quite straightforward after getting a quote. #### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#how-to-construct-the-permitdata-in-exactinputparams) How to construct the permitData in ExactInputParams ? When selling the tokens, you can use our permitData field to save an approve transaction. The permitData field is the abi encoding of each field from the Permit struct plus the EIP712 signature. Check the `genPermitData` below : [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#events) Events --------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#tokenbought) `TokenBought` Emitted when a user buys tokens through the Portal. **Parameters:** * `ts`: Timestamp of the trade. * `token`: Address of the token bought. * `buyer`: Address of the buyer. * `amount`: Amount of tokens bought. * `eth`: Amount of ETH (or quote token) spent. * `fee`: Amount of ETH (or quote token) spent as a fee. * `postPrice`: Price of the token after this trade. **When emitted:** Whenever a user successfully buys tokens via the Portal. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#tokensold) `TokenSold` Emitted when a user sells tokens through the Portal. **Parameters:** * `ts`: Timestamp of the trade. * `token`: Address of the token sold. * `seller`: Address of the seller. * `amount`: Amount of tokens sold. * `eth`: Amount of ETH (or quote token) received. * `fee`: Amount of ETH (or quote token) deducted as a fee. * `postPrice`: Price of the token after this trade. **When emitted:** Whenever a user successfully sells tokens via the Portal. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#launchedtodex) `LaunchedToDEX` Emitted when a token finishes its bonding-curve phase and is launched to the target DEX. **Parameters:** * `token`: Address of the token being migrated. * `pool`: Compatibility field for the DEX target. * `amount`: Amount of token liquidity added. * `eth`: Amount of quote-token liquidity added. For classic `V2_MIGRATOR` and `V3_MIGRATOR` launches, `pool` is the actual pool address. For `PCS_INFINITY_CL_MIGRATOR`, `pool` is the PancakeSwap Infinity vault address. For `V4_UNI_MIGRATOR`, `pool` is the Uniswap PoolManager address. This is kept mainly for compatibility with older indexers. For these two CL-style migrators, the actual pool identifier should be read from `FlapTokenCLPoolCreated`. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#flaptokenclpoolcreated) `FlapTokenCLPoolCreated` This event is only relevant for the concentrated-liquidity launch paths: **PancakeSwap Infinity CL** and **Uniswap v4**. **Parameters:** * `token`: Address of the launched token. * `poolId`: The CL pool ID. For `PCS_INFINITY_CL_MIGRATOR` and `V4_UNI_MIGRATOR`, this is the value you should index and store as the real pool identifier. * `sqrtPriceX96`: The initial sqrt price used to initialize the CL pool. **When emitted:** Emitted when the token is launched through `PCS_INFINITY_CL_MIGRATOR` or `V4_UNI_MIGRATOR` and the CL pool is initialized. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#flaptokenprogresschanged) FlapTokenProgressChanged This event is available since v4.12.1 Whenever a token's progress changes, the `FlapTokenProgressChanged` event will be emitted. Note that the `newProgress` is in `wad` (i.e, with 18 decimals, 1 ether = 100%). [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#how-to-do-an-off-chain-quote) How To Do An Off-Chain Quote? ------------------------------------------------------------------------------------------------------------------------------------------------------------ ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#off-chain-quote-1.-the-preliminary) \[Off-Chain Quote\] 1. The Preliminary The preliminary steps to quote off-chain is to get the info of a token first. You can either use the methods provided by our smart contract (i.e: [getTokenV6 or getTokenV7](https://github.com/flap-sh/gitbook/blob/main/developers/wallet-and-terminal-and-bot-developers/inspect-a-token/README.md) ), or you can build your indexer by indexing the following events: * `TokenCreated`: This event gives us the basic info of the token * `TokenCurveSet` and `TokenCurveSetV2`: We use these events to determine our bonding curve parameters. * `TokenDexSupplyThreshSet`: this event gives us the circulating supply threshold for the token be migrated. * `FlapTokenCirculatingSupplyChanged`: This event gives us the current circulating supply of the token. * `LaunchedToDEX` : This event indicates that the token has been launched to a decentralized exchange (DEX), and the bonding curve equation ceases to be valid. For `PCS_INFINITY_CL_MIGRATOR` and `V4_UNI_MIGRATOR`, note that the `pool` field is a compatibility address (Infinity vault / PoolManager) rather than the final CL pool ID. * `FlapTokenTaxSet` : (optional), if a token does not emit this event, the tax is 0 or it is not a tax token. * `FlapTokenProgressChanged` : emitted when the bonding curve progress of a token changes * `FlapTokenCLPoolCreated` : For `PCS_INFINITY_CL_MIGRATOR` and `V4_UNI_MIGRATOR`, use this event's `poolId` as the real CL pool identifier. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#off-chain-quote-2.-the-curve-library) \[Off-Chain Quote\] 2. The Curve Library With the bonding curve parameters (either a single `r` from `TokenCurveSet` or the full set (`r`, `h`, `k`) from `TokenCurveSetV2`), you can construct a curve instance. Here are the reference typescript and solidity code. You can always feed the code to your AI (Claude or others), and ask them to help you to convert to your preferred programming language: * The legacy curve is a special case of the new curve where h = 0. For constructing a legacy curve, we can pass only `r` as the parameter. * For latest curve, we should pass all the parameters (`r`, `h`, `k`). The solidity version of the curve lib: Note that the curve has the following methods: * `estimateSupply(reserve: string)`: Estimates the token circulating supply given the reserve amount. * `estimateReserve(amount: string)`: Estimates the reserve amount given the token’s circulating supply. ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#off-chain-quote-3.-quote-with-the-curve-instance) \[Off-Chain Quote\] 3. Quote With The Curve Instance We will use the typescript pseudo code to demonstrate how to quote with the curve instance. **Case1: Buy 1 BNB Value of Token** **Case 2: Sell 1M token for bnb** ### [](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#off-chain-quote-4.-quote-for-tax-tokens) \[Off-Chain Quote\] 4. Quote for tax tokens If a token has tax, we also charge the tax on the bonding curve. For off-chain quotes, this means you should **add the tax rate to the protocol fee**. * Effective fee = protocol fee (1%) + token tax rate * Tax rate can be read from `getTokenV6` / `getTokenV7`, or indexed from the `FlapTokenTaxSet` event Below are the same examples as above, updated for tax tokens. You can also read the dedicated tax guide at [developers/basic-and-mechanism/flap-tax-token/prebond-tax.md](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax) . **Case 1: Buy 1 BNB value of token (tax token)** **Case 2: Sell 1M token for BNB (tax token)** [PreviousInspect A Tax Token](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token) [NextToken Migration](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-migration) Last updated 1 month ago * [Get A Quote](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#get-a-quote) * [Swap](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#swap) * [Events](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#events) * [TokenBought](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#tokenbought) * [TokenSold](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#tokensold) * [LaunchedToDEX](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#launchedtodex) * [FlapTokenCLPoolCreated](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#flaptokenclpoolcreated) * [FlapTokenProgressChanged](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#flaptokenprogresschanged) * [How To Do An Off-Chain Quote?](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#how-to-do-an-off-chain-quote) * [\[Off-Chain Quote\] 1. The Preliminary](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#off-chain-quote-1.-the-preliminary) * [\[Off-Chain Quote\] 2. The Curve Library](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#off-chain-quote-2.-the-curve-library) * [\[Off-Chain Quote\] 3. Quote With The Curve Instance](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#off-chain-quote-3.-quote-with-the-curve-instance) * [\[Off-Chain Quote\] 4. Quote for tax tokens](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens#off-chain-quote-4.-quote-for-tax-tokens) Copy /// @notice Parameters for swapping exact input amount for output token struct ExactInputParams { /// @notice The address of the input token (use address(0) for native asset) address inputToken; /// @notice The address of the output token (use address(0) for native asset) address outputToken; /// @notice The amount of input token to swap (in input token decimals) uint256 inputAmount; /// @notice The minimum amount of output token to receive uint256 minOutputAmount; /// @notice Optional permit data for the input token (can be empty) bytes permitData; } /// @notice Swap exact input amount for output token /// @param params The swap parameters /// @return outputAmount The amount of output token received /// @dev Here are some possible scenarios: /// If the token's reserve is BNB or ETH (i.e: the quote token is the native gas token): /// - BUY: input token is address(0), output token is the token address /// - SELL: input token is the token address, output token is address(0) /// If the token's reserve is another ERC20 token (eg. USD*, i.e, the quote token is an ERC20 token): /// - BUY with USD*: input token is the USD* address, output token is the token address /// - SELL for USD*: input token is the token address, output token is the USD* address /// - BUY with BNB or ETH: input token is address(0), output token is the token address. /// (Note: this requires an internal swap to convert BNB/ETH to USD*, nativeToQuoteSwap must be anabled for this quote token) /// Note: Currently, this method only supports trading tokens that are still in the bonding curve state. /// However, in the future, we may also support trading tokens that are already in DEX state. function swapExactInput(ExactInputParams calldata params) external payable returns (uint256 outputAmount); Copy import { Account, Address, createWalletClient, encodeAbiParameters, encodeDeployData, encodeFunctionData, formatEther, getContract, getContractAddress, HDAccount, Hex, hexToBigInt, http, keccak256, parseEther, parseGwei, parseSignature, parseTransaction, parseUnits, PublicClient, serializeTransaction, toBytes, toHex } from 'viem'; // generate ERC20 permit data for token (returns all fields, not abi encoded) async function genPermitDataNoPack(account: Account, token: `0x${string}`, spender: `0x${string}`, amount: bigint, client: PublicClient) { const tokenInst = getContract({ abi: [\ {\ type: "function",\ name: "name",\ inputs: [],\ outputs: [{ name: "", type: "string" }],\ stateMutability: "view"\ },\ {\ type: "function",\ name: "nonces",\ inputs: [{ name: "", type: "address" }],\ outputs: [{ name: "", type: "uint256" }],\ stateMutability: "view"\ },\ {\ type: "function",\ name: "permit",\ inputs: [\ { name: "owner", type: "address" },\ { name: "spender", type: "address" },\ { name: "value", type: "uint256" },\ { name: "deadline", type: "uint256" },\ { name: "v", type: "uint8" },\ { name: "r", type: "bytes32" },\ { name: "s", type: "bytes32" }\ ],\ outputs: [],\ stateMutability: "nonpayable"\ }\ ], address: token, client, }); const nonce = await tokenInst.read.nonces([account.address]) as bigint; const name = await tokenInst.read.name() as string; const deadline = BigInt(Date.now() + 10 * 60 * 1000); // 10 minutes ttl const sig = await (account as HDAccount).signTypedData({ domain: { name, version: "1", chainId: bsc.id, // or bsc.id for mainnet verifyingContract: token as `0x${string}` }, types: { Permit: [\ { name: "owner", type: "address" },\ { name: "spender", type: "address" },\ { name: "value", type: "uint256" },\ { name: "nonce", type: "uint256" },\ { name: "deadline", type: "uint256" }\ ] }, primaryType: "Permit", message: { owner: account.address, spender: spender, value: amount, nonce, deadline, } }); const { r, s, v } = parseSignature(sig) as { r: `0x${string}`, s: `0x${string}`, v: bigint, yParity: number }; return { owner: account.address, spender, value: amount, nonce, deadline, v: Number(v), r, s }; } // generate ERC20 permit data for token (returns abi encoded data) async function genPermitData(account: Account, token: `0x${string}`, amount: bigint, client: PublicClient) { const permitFields = await genPermitDataNoPack(account, token, flapConfig.portal, amount, client); const data = encodeAbiParameters([\ { name: "owner", type: "address" },\ { name: "spender", type: "address" },\ { name: "value", type: "uint256" },\ { name: "deadline", type: "uint256" },\ { name: "v", type: "uint8" },\ { name: "r", type: "bytes32" },\ { name: "s", type: "bytes32" }\ ], [permitFields.owner, permitFields.spender, permitFields.value, permitFields.deadline, permitFields.v, permitFields.r, permitFields.s]); return data; } Copy event TokenBought( uint256 ts, address token, address buyer, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice ); Copy event TokenSold( uint256 ts, address token, address seller, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice ); Copy event LaunchedToDEX(address token, address pool, uint256 amount, uint256 eth); Copy /// @notice emitted when a new CL pool is created for a token /// @param token The address of the token /// @param poolId The ID of the created CL pool /// @param sqrtPriceX96 The initial sqrt price used to initialize the CL pool event FlapTokenCLPoolCreated(address token, bytes32 poolId, uint160 sqrtPriceX96); Copy /// @notice emitted when the progress of a token changes /// @param token The address of the token /// @param newProgress The new progress value in Wad event FlapTokenProgressChanged(address token, uint256 newProgress); Copy import { Decimal } from "decimal.js"; const BILLION: Decimal = new Decimal("1000000000"); // The latest curve is CDPV2 export class CDPV2 { // the initial virtual reserve private r: number; private h: number; private k: number; static defaultDexSupplyThreshold() { return new Decimal(8e8); } static getCurve(r: number, h?: number, k?: number): CDPV2 { if (h == null) { return new CDPV2(r, 0, 1e9 * r); } return new CDPV2(r, h, k); } constructor(r: number, h: number = 0, k: number = 0) { this.r = r; this.h = h; this.k = k; } estimateSupply(reserve: string): Decimal { // s = 1e9 + h - k/(r + eth) if (!reserve) return new Decimal(0); return new Decimal(BILLION).add(this.h).sub( new Decimal(this.k).div(new Decimal(reserve).add(this.r)) ); } estimateReserve(amount: string): Decimal { // eth = k/(h + 1e9 - s) - r if (!amount) return new Decimal(0); return new Decimal(this.k) .div(new Decimal(BILLION).add(this.h).sub(new Decimal(amount))) .sub(this.r); } mc(reserve: string): Decimal { return this.fdv(this.totalSupply(reserve).toString()); } price(supply: string): Decimal { // Price: k/(h + 1e9 - s)^2 const denominator = new Decimal(BILLION).add(this.h).sub(new Decimal(supply || 0)); return new Decimal(this.k).div(denominator.pow(2)); } fdv(supply: string): Decimal { return this.price(supply).mul(new Decimal(BILLION)); } } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol"; /// @title constant product bonding curve /// @author The Flap Team /// @dev v2 /// /// Spec: /// - max supply: 1 Billion tokens /// - The constant product equation is : /// (1e9 + h - s) * (eth + r) = k /// - s is the current circulating supply of the token /// - eth is the current eth reserve /// - special case: When h = 0, k = r * 1e9 /// - estimateSupply(estimate s given eth): s = 1e9 + h - k/(r + eth) /// - estimateReserve(estimate eth given s): eth = k/(h + 1e9 - s) - r /// - price estimation: k/(h + 1e9 - s)**2 library LibCurve { /// @notice The curve type is represented by a struct /// refer to Uniswap V3 white paper. struct Curve { uint256 r; // Virtual ETH reserve uint256 h; // Virtual token reserve uint256 k; // The square of the virtual Liquidity } // The total supply of the token uint256 public constant TOTAL_SUPPLY = 1_000_000_000 ether; // custom error type /// @notice error if the new supply is greater than the total supply error SupplyExceedsTotalSupply(uint256 newSupply); /// @notice error if reserve is greater than the max reserve error ReserveExceedsMaxReserve(uint256 reserve); // @notice Return the estimate supply given the reserve amount /// @param reserve The reserve amount /// @dev The resulting supply is rounded down and may even subtract small amount /// /// This function is used when a user wants to buy tokens, /// a rounded down value is more favorable to the protocol. function estimateSupply(Curve memory curve, uint256 reserve) internal pure returns (uint256 supply) { // s = 1e9 + h - k/(r + eth) // Round down for protocol safety when buying supply = TOTAL_SUPPLY + curve.h - FixedPointMathLib.divWadUp(curve.k, curve.r + reserve); } /// @notice estimate the reserve given the supply /// @dev This function returns a roundup value, because we want the following invariant to hold: /// currReserve >= estimateReserve_without_roudup(currSupply) /// /// This function is used when a user wants to sell tokens, a rounded up value /// is more favorable to the protocol. function estimateReserve(Curve memory curve, uint256 supply) internal pure returns (uint256 reserve) { if (supply > TOTAL_SUPPLY) { revert SupplyExceedsTotalSupply(supply); } // eth = k/(h + 1e9 - s) - r // Round up for protocol safety when selling reserve = FixedPointMathLib.divWadUp(curve.k, TOTAL_SUPPLY + curve.h - supply) - curve.r; } /// @notice price (wei) of a token (1e18) if you buy/sell inifinitesimal amount at current supply function price(Curve memory curve, uint256 supply) internal pure returns (uint256) { // Price: k/(h + 1e9 - s)^2 uint256 denominator = TOTAL_SUPPLY + curve.h - supply; if (denominator < 1e9 + 1) { return type(uint256).max; } // Calculate (h + 1e9 - s)^2 using mulWad for precision uint256 denominator_squared = FixedPointMathLib.mulWad(denominator, denominator); return FixedPointMathLib.divWad(curve.k, denominator_squared); } // helper from r to curve function fromR(uint256 r) internal pure returns (Curve memory) { // legacy curve: h = 0 and k = r * TOTAL_SUPPLY return Curve({r: r, h: 0, k: FixedPointMathLib.mulWad(r, TOTAL_SUPPLY)}); } // helper from r,h,k to curve function fromRHK(uint256 r, uint256 h, uint256 k) internal pure returns (Curve memory) { return Curve({r: r, h: h, k: k}); } /// @notice Return the estimate supply given the reserve amount with support for different reserve token decimals /// @param curve The curve parameters /// @param reserve The reserve amount /// @param reserveDecimals The number of decimals of the reserve token (must be <= 18) /// @dev The resulting supply is rounded down, same as estimateSupply /// This function is used when a user wants to buy tokens, /// a rounded down value is more favorable to the protocol. function estimateSupplyV2(Curve memory curve, uint256 reserve, uint8 reserveDecimals) internal pure returns (uint256 supply) { if (reserveDecimals > 18) { revert("Reserve decimals must be <= 18"); } if (reserveDecimals == 18) { // If reserve token has 18 decimals, use the original estimateSupply function return estimateSupply(curve, reserve); } // Adjust the reserve to 18 decimals uint256 scaleFactor = 10 ** (18 - reserveDecimals); uint256 scaledReserve = reserve * scaleFactor; // Use the adjusted reserve amount supply = TOTAL_SUPPLY + curve.h - FixedPointMathLib.divWadUp(curve.k, curve.r + scaledReserve); } /// @notice Estimate the reserve given the supply with support for different reserve token decimals /// @param curve The curve parameters /// @param supply The token supply /// @param reserveDecimals The number of decimals of the reserve token (must be <= 18) /// @dev This function returns a roundup value, same as estimateReserve /// This function is used when a user wants to sell tokens, a rounded up value /// is more favorable to the protocol. function estimateReserveV2(Curve memory curve, uint256 supply, uint8 reserveDecimals) internal pure returns (uint256 reserve) { if (reserveDecimals > 18) { revert("Reserve decimals must be <= 18"); } if (supply > TOTAL_SUPPLY) { revert SupplyExceedsTotalSupply(supply); } if (reserveDecimals == 18) { // If reserve token has 18 decimals, use the original estimateReserve function return estimateReserve(curve, supply); } // Calculate the reserve in 18 decimals uint256 scaledReserve = FixedPointMathLib.divWadUp(curve.k, TOTAL_SUPPLY + curve.h - supply) - curve.r; // Convert the reserve from 18 decimals to the actual reserve decimals uint256 scaleFactor = 10 ** (18 - reserveDecimals); // Divide scaled reserve by scaleFactor (rounding up) // For rounding up division: (a + b - 1) / b reserve = (scaledReserve + scaleFactor - 1) / scaleFactor; } } Copy //// Values From your Indexer or getTokenV6/V7 // current circulating supply // This can be obtained from getTokenV6 or the FlapTokenCirculatingSupplyChanged event. let curr_circulating_supply; // The circulating supply threshold for the token to be migrated to DEX // Typicially, this is 800M // This can be obtained from getTokenV6 or the TokenDexSupplyThreshSet event. let dex_supply_threshold = 800_000_000; // The input bnb amount let input_bnb = 1; //// Compute the Quote // We cannot buy more than the dex_supply_threshold on the bonding curve // The max_reserve is the cap of the reserve let max_reserve = curve.estimateReserve(dex_supply_threshold); // We estimate the current reserve from the circulating supply let curr_reserve = curve.estimateReserve(curr_circulating_supply); // protocol fee on BNB chain is 1% let fee = 0.01; // 1% fee // Charge a 1% fee on the input bnb. let input_after_fee = input_bnb * (0.99); // If the user pays more than required, we will automatically refund the user let new_reserve = curr_reserve + min(input_after_fee, max_reserve - curr_reserve); // estimate new circulating supply from new reserve let new_circulating_supply = curve.estimateSupply(new_reserve); // The diff of circulating supply is the output token amount let output_amt = new_circulating_supply - curr_circulating_supply; Copy //// From Indexer Or getTokenV6/V7 // current circulating supply let curr_circulating_supply; // The circulating supply threshold for the token to be migrated to DEX // Typicially, this is 800M // This can be obtained from getTokenV6/V7 or the TokenDexSupplyThreshSet event. let dex_supply_threshold; // BNB fee let fee = 0.01; // 1% fee // The input token amount let input_token_amt = 1_000_000; // estimate the current reserve let curr_reserve = curve.estimateReserve(curr_circulating_supply); // estimate the new resereve let new_reserve = curve.estimateReserve(curr_circulating_supply - input_token_amt); // The diff is the output amount without deducting the fee let output_eth_before_fee = curr_reserve - new_reserve; // we take 1% fee from the quote token let output_eth = output_eth_before_fee * (0.99); Copy //// Values From your Indexer or getTokenV6/V7 // current circulating supply // This can be obtained from getTokenV6 or the FlapTokenCirculatingSupplyChanged event. let curr_circulating_supply; // The circulating supply threshold for the token to be migrated to DEX // Typicially, this is 800M // This can be obtained from getTokenV6 or the TokenDexSupplyThreshSet event. let dex_supply_threshold = 800_000_000; // The token's tax rate // This can be obtained from getTokenV6 or the FlapTokenTaxSet event from the Portal let tax = 0; // The input bnb amount let input_bnb = 1; //// Compute the Quote // We cannot buy more than the dex_supply_threshold on the bonding curve // The max_reserve is the cap of the reserve let max_reserve = curve.estimateReserve(dex_supply_threshold); // We estimate the current reserve from the circulating supply let curr_reserve = curve.estimateReserve(curr_circulating_supply); // Effective fee = 1% protocol fee + tax let fee = 0.01 + tax; // Charge the fee on the input bnb. let input_after_fee = input_bnb * (1 - fee); // If the user pays more than required, we will automatically refund the user let new_reserve = curr_reserve + min(input_after_fee, max_reserve - curr_reserve); // estimate new circulating supply from new reserve let new_circulating_supply = curve.estimateSupply(new_reserve); // The diff of circulating supply is the output token amount let output_amt = new_circulating_supply - curr_circulating_supply; Copy //// From Indexer Or getTokenV6/V7 // current circulating supply let curr_circulating_supply; // The circulating supply threshold for the token to be migrated to DEX // Typicially, this is 800M // This can be obtained from getTokenV6/V7 or the TokenDexSupplyThreshSet event. let dex_supply_threshold; // The token's tax rate // This can be obtained from getTokenV6 or the FlapTokenTaxSet event from the Portal let tax = 0.03; // BNB fee // Effective fee: 1% protocol + tax let fee = 0.01 + tax; // The input token amount let input_token_amt = 1_000_000; // estimate the current reserve let curr_reserve = curve.estimateReserve(curr_circulating_supply); // estimate the new reserve let new_reserve = curve.estimateReserve(curr_circulating_supply - input_token_amt); // The diff is the output amount without deducting the fee let output_eth_before_fee = curr_reserve - new_reserve; // apply the fee on the quote token let output_eth = output_eth_before_fee * (1 - fee); --- # World Cup Resolver | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/preview/worldcup-resolver.md) . This is an older solution and can still be used. For new integrations, prefer [worldcup-viewer.md](https://docs.flap.sh/flap/developers/preview/worldcup-viewer) . [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#overview) Overview ----------------------------------------------------------------------------------------- `WorldCupResolver` is a read-only smart contract deployed on BSC mainnet that reads **2026 FIFA World Cup** match outcomes from the on-chain oracle system and exposes the results to downstream consumers. **Deployed address (BSC mainnet):** `0x134C6b9562E226096947e018ddEe4804c9146921` The contract covers 43 entries: the 32 tournament teams, a selection of additional national teams with odds, and an "Other" catch-all. Each team maps to a unique oracle question: _"Will \[Team\] win the 2026 FIFA World Cup?"_ The underlying oracle resolves each question as YES or NO on-chain. No result state is stored inside `WorldCupResolver`. Every call to `resolveWinner()` reads the oracle live, so the returned snapshot always reflects the latest state. * * * [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#how-it-works) How It Works ------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#oracle-questions-and-team-resolution) Oracle questions and team resolution For every team, there is exactly one oracle question: _"Will this team win the World Cup?"_ Oracle field Meaning `reportedAt > 0` The oracle has published a result for this team `results == true` The result is **YES** — this team won `results == false` The result is **NO** — this team did **not** win (eliminated) `flaggedAt > 0` The result is currently under dispute; treat as unresolved A team is confirmed as the **final winner** only when all three conditions hold simultaneously: 1. `reportedAt > 0` — oracle has reported. 2. `results == true` — result is YES. 3. `flaggedAt == 0` — result is **not** flagged/disputed. ### [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#resolving-the-final-winner) Resolving the final winner Call `resolveWinner()`. It scans all 43 entries and returns a `WinnerResult` struct: * `isResolved = true` → a confirmed winning team was found; read `winnerIndex` and `winnerName`. * `isResolved = false` → no confirmed winner yet; inspect `statuses[]` for per-team progress. ### [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#resolving-a-single-match-team-elimination) Resolving a single match (team elimination) **Limitation — teams grouped under "Other" (index 42):** `WorldCupResolver` does not run its own oracle. It reads data from an external UMA-based oracle, and the resolution data provided by that oracle only covers 42 individually named teams. All remaining qualified national teams that were not given their own oracle question are grouped into a single `Other` entry (index 42). As a result, if one of those unlisted teams is eliminated in a match, their elimination **cannot** be detected individually — the `Other` question stays unresolved until one of those teams wins the entire World Cup (at which point `Other` resolves YES). Per-team match resolution is only supported for the 42 named teams. Because every oracle question is about the **overall World Cup winner**, an answer of NO for a team means that team has been **definitively eliminated** — they can never win the tournament. This makes it possible to infer match outcomes incrementally, well before the final is played: **Example — Group stage match: Team A vs Team B** 1. Call `resolveWinner()` and read `statuses[]`. 2. Check Team A's status: `isReported = true`, `result = false`, `isFlagged = false` → **Team A is eliminated** (they lost and are out of the tournament). 3. Check Team B's status: `isReported = false` → **Team B is still in contention** (their question has not yet been resolved). In this scenario Team A's result is fully settled — it can be used to pay out prediction positions — while Team B remains pending until a later match resolves their fate. Only treat `isReported = true` **and** `isFlagged = false` as a settled result. A flagged result is under oracle dispute and must not be acted upon. * * * [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#interface) Interface ------------------------------------------------------------------------------------------- * * * [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#supported-teams) Supported Teams ------------------------------------------------------------------------------------------------------- `getSupportedTeams()` returns 43 entries. The index is stable and matches the `TeamStatus.index` field returned by `resolveWinner()`. Index Team Index Team 0 Spain 22 Canada 1 France 23 Scotland 2 England 24 South Korea 3 Argentina 25 Paraguay 4 Brazil 26 Ivory Coast 5 Portugal 27 Egypt 6 Germany 28 Iran 7 Netherlands 29 Ghana 8 Norway 30 Algeria 9 Italy 31 Tunisia 10 Belgium 32 Austria 11 USA 33 New Zealand 12 Morocco 34 Haiti 13 Colombia 35 Jordan 14 Japan 36 Curacao 15 Uruguay 37 Uzbekistan 16 Croatia 38 South Africa 17 Mexico 39 Cape Verde 18 Switzerland 40 Qatar 19 Ecuador 41 Saudi Arabia 20 Senegal 42 Other 21 Australia * * * [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#integration-examples) Integration Examples ----------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#check-for-the-final-winner-solidity) Check for the final winner (Solidity) ### [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#check-whether-a-specific-team-has-been-eliminated-solidity) Check whether a specific team has been eliminated (Solidity) ### [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#read-all-statuses-off-chain-ethers.js) Read all statuses off-chain (ethers.js) * * * [](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#deployment-details) Deployment Details ------------------------------------------------------------------------------------------------------------- Field Value Network BSC Mainnet Contract address `0x134C6b9562E226096947e018ddEe4804c9146921` [PreviousFlap Trigger Service](https://docs.flap.sh/flap/developers/preview/flap-trigger-service) [NextWorld Cup Viewer](https://docs.flap.sh/flap/developers/preview/worldcup-viewer) Last updated 1 month ago * [Overview](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#overview) * [How It Works](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#how-it-works) * [Oracle questions and team resolution](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#oracle-questions-and-team-resolution) * [Resolving the final winner](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#resolving-the-final-winner) * [Resolving a single match (team elimination)](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#resolving-a-single-match-team-elimination) * [Interface](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#interface) * [Supported Teams](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#supported-teams) * [Integration Examples](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#integration-examples) * [Check for the final winner (Solidity)](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#check-for-the-final-winner-solidity) * [Check whether a specific team has been eliminated (Solidity)](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#check-whether-a-specific-team-has-been-eliminated-solidity) * [Read all statuses off-chain (ethers.js)](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#read-all-statuses-off-chain-ethers.js) * [Deployment Details](https://docs.flap.sh/flap/developers/preview/worldcup-resolver#deployment-details) Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @notice Per-team oracle status, returned by read-only query methods. struct TeamStatus { uint8 index; // 0-based team index (same order as getSupportedTeams()) string name; // human-readable team name bool isReported; // true when reportedAt > 0 on the oracle bool result; // true = YES (winner), false = NO or not yet reported bool isFlagged; // true when flaggedAt > 0 — result is disputed } /// @notice Return value of resolveWinner(). struct WinnerResult { bool isResolved; // true if a definitive YES answer was found uint8 winnerIndex; // set only when isResolved = true string winnerName; // set only when isResolved = true TeamStatus[] statuses; // full per-team snapshot at time of call } interface IWorldCupResolver { /// @notice Returns the full list of 43 supported team names in index order. /// names[0] = "Spain", names[42] = "Other". /// Safe to call at any time, including while the contract is paused. function getSupportedTeams() external view returns (string[] memory names); /// @notice Reads the oracle live and computes the World Cup winner. /// /// - isResolved = true → winnerIndex / winnerName identify the champion. /// - isResolved = false → no winner yet; inspect statuses[] for per-team progress. /// /// A team counts as winner only when: /// 1. reportedAt > 0 (oracle has reported) /// 2. results == true (oracle result is YES) /// 3. flaggedAt == 0 (result is not disputed) function resolveWinner() external view returns (WinnerResult memory result); } Copy IWorldCupResolver resolver = IWorldCupResolver(0x134C6b9562E226096947e018ddEe4804c9146921); WinnerResult memory r = resolver.resolveWinner(); if (r.isResolved) { // r.winnerName → e.g. "Brazil" // r.winnerIndex → e.g. 4 } Copy WinnerResult memory r = resolver.resolveWinner(); // Example: check if Spain (index 0) is eliminated TeamStatus memory spain = r.statuses[0]; if (spain.isReported && !spain.result && !spain.isFlagged) { // Spain has been definitively eliminated — safe to settle Spain positions } Copy const resolver = new ethers.Contract( "0x134C6b9562E226096947e018ddEe4804c9146921", IWorldCupResolverABI, provider ); const result = await resolver.resolveWinner(); console.log("Is resolved:", result.isResolved); if (result.isResolved) { console.log("Winner:", result.winnerName, "(index", result.winnerIndex, ")"); } for (const status of result.statuses) { if (status.isReported && !status.isFlagged) { const outcome = status.result ? "WINNER" : "ELIMINATED"; console.log(`${status.name}: ${outcome}`); } else { console.log(`${status.name}: pending`); } } --- # Using an LP Token or Child Token as the Dividend Token | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend.md) . **Preview.** This document has not been reviewed yet. Content may be incomplete or inaccurate. This tutorial walks you through implementing a vault factory that uses a **computed dividend token** — a token whose address cannot be known before the tax token is deployed, such as a Uniswap/PancakeSwap LP pair or a child token deployed deterministically from the tax token address. This feature was introduced in **factory spec v2.3**. * * * [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#the-problem) The Problem --------------------------------------------------------------------------------------------------------------------- When launching a token through VaultPortal, the caller must supply a `dividendToken` address — the ERC-20 token that vault holders receive as dividends. Most of the time this is straightforward (e.g. use WBNB, or USDT). But some vault designs require a dividend token whose address **depends on the tax token address itself**: * **LP pair tokens** — the PancakeSwap pair for `(taxToken, WBNB)` has an address derived from `taxToken`. * **Child tokens / receipt tokens** — a token deployed via CREATE2 keyed on `taxToken`. At launch time the tax token does not exist yet — VaultPortal predicts its address via CREATE2, but the actual deployment happens inside Portal. The `dividendToken` field is needed _before_ that deployment, so there is no way to supply the real LP/child address upfront. * * * [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#the-solution-magic_dividend_computed) The Solution: `MAGIC_DIVIDEND_COMPUTED` -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- VaultPortal supports a special sentinel value for `dividendToken`: Copy // IPortal.sol address constant MAGIC_DIVIDEND_COMPUTED = address(0xC0Dec0dec0DeC0Dec0dEc0DEC0DEC0DEC0DEC0dE); When the launcher passes `MAGIC_DIVIDEND_COMPUTED` as `dividendToken`, VaultPortal will: 1. Compute the CREATE2-predicted tax token address. 2. Check that the selected vault factory implements **spec v2.3** (`factorySpecVersion()` returns `"v2.3"` or higher). 3. Call `resolveDividendToken(predictedToken, launchVersion, launchParams)` on the factory. 4. Use the returned address as the actual `dividendToken` forwarded to Portal. If the factory does not support v2.3, the launch reverts with `FactoryDoesNotSupportComputedDividend()`. * * * [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#implementation-guide) Implementation Guide --------------------------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#step-1-declare-spec-v2.3-in-your-factory) Step 1 — Declare spec v2.3 in your factory Your factory must extend `VaultFactoryBaseV2` and override `factorySpecVersion()` to return `"v2.3"` (or higher): You **must** also implement `IVaultFactoryDividendV23`. Declaring `"v2.3"` in `factorySpecVersion()` but not implementing `resolveDividendToken` will cause every `MAGIC_DIVIDEND_COMPUTED` launch to revert. * * * ### [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#step-2-implement-resolvedividendtoken) Step 2 — Implement `resolveDividendToken` `IVaultFactoryDividendV23` requires a single function: Parameter Description `predictedToken` The CREATE2-predicted address of the tax token (not yet deployed) `launchVersion` `DIVIDEND_TOKEN_LAUNCH_VERSION_V6` or `DIVIDEND_TOKEN_LAUNCH_VERSION_V7` depending on the launch wrapper used `launchParams` ABI-encoded `NewTokenV6WithVaultParams` or `NewTokenV7WithVaultParams` VaultPortal calls this as a `**staticcall**`, so the function must be `view` (no state writes). #### [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#example-lp-pair-dividend) Example — LP pair dividend This example resolves the PancakeSwap V2 LP pair of `(predictedToken, WBNB)` as the dividend token: #### [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#example-child-token-dividend) Example — Child token dividend If your vault deploys a child token deterministically from the tax token address: * * * ### [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#step-3-declare-a-policy-so-the-ui-pre-fills-the-sentinel) Step 3 — Declare a policy so the UI pre-fills the sentinel `tokenCreationPolicies()` lets the UI know that `dividendToken` **must** equal `MAGIC_DIVIDEND_COMPUTED`. The UI reads this policy and pre-fills (or locks) the field before the user submits the launch form. Policies are **advisory hints for the UI only** — they do not enforce anything on-chain. Always pair a policy with the corresponding `_validateBeforeLaunch` check (Step 4) to enforce the rule on-chain. * * * ### [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#step-4-enforce-the-sentinel-in-_validatebeforelaunch) Step 4 — Enforce the sentinel in `_validateBeforeLaunch` Override `_validateBeforeLaunch` to reject any launch that does not carry `MAGIC_DIVIDEND_COMPUTED`. This is the on-chain enforcement that backs the UI policy declared above. `VaultPortal` calls `onBeforeLaunch(bytes)` (which decodes into `_validateBeforeLaunch`) **before** it calls `resolveDividendToken`. This means the hook sees `dividendToken == MAGIC_DIVIDEND_COMPUTED` — the raw sentinel, not yet the resolved LP address. That is the correct value to check against. Your factory must return `"v2.3"` from `factorySpecVersion()` for `onBeforeLaunch` to be reached. If you return `"v2.1"` or lower, `VaultPortal` will use the legacy `onBeforeNewTokenV6WithVault` path instead. * * * ### [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#step-5-launch-with-magic_dividend_computed) Step 5 — Launch with `MAGIC_DIVIDEND_COMPUTED` On the caller side, set `dividendToken` to the sentinel when building the launch params: VaultPortal will call `myLPVaultFactory.resolveDividendToken(...)` and substitute the result before forwarding the launch to Portal. * * * [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#what-happens-on-chain-flow-summary) What Happens On-Chain (Flow Summary) --------------------------------------------------------------------------------------------------------------------------------------------------------------------- * * * [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#version-gating) Version Gating --------------------------------------------------------------------------------------------------------------------------- `factorySpecVersion()` `MAGIC_DIVIDEND_COMPUTED` supported? `""` (legacy / V1) ❌ reverts with `FactoryDoesNotSupportComputedDividend` `"v2.1"`, `"v2.2"` ❌ reverts `"v2.3"` or higher ✅ `resolveDividendToken` is called Factories that do not implement v2.3 are completely unaffected — the new sentinel only triggers for launches that explicitly pass `MAGIC_DIVIDEND_COMPUTED`. * * * [](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#checklist) Checklist ----------------------------------------------------------------------------------------------------------------- * Factory extends `VaultFactoryBaseV2` and implements `IVaultFactoryDividendV23` * `factorySpecVersion()` returns `"v2.3"` or higher * `resolveDividendToken` is `view` and always returns a non-zero address * `tokenCreationPolicies()` declares `dividendToken eq MAGIC_DIVIDEND_COMPUTED` * `_validateBeforeLaunch` rejects any launch where `dividendToken != MAGIC_DIVIDEND_COMPUTED` * Launcher passes `MAGIC_DIVIDEND_COMPUTED` as `dividendToken` * Launcher selects the v2.3 factory in the launch params [PreviousTutorials](https://docs.flap.sh/flap/developers/vault-developers/tutorials) [NextPreview](https://docs.flap.sh/flap/developers/preview) Last updated 23 days ago * [The Problem](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#the-problem) * [The Solution: MAGIC\_DIVIDEND\_COMPUTED](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#the-solution-magic_dividend_computed) * [Implementation Guide](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#implementation-guide) * [Step 1 — Declare spec v2.3 in your factory](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#step-1-declare-spec-v2.3-in-your-factory) * [Step 2 — Implement resolveDividendToken](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#step-2-implement-resolvedividendtoken) * [Step 3 — Declare a policy so the UI pre-fills the sentinel](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#step-3-declare-a-policy-so-the-ui-pre-fills-the-sentinel) * [Step 4 — Enforce the sentinel in \_validateBeforeLaunch](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#step-4-enforce-the-sentinel-in-_validatebeforelaunch) * [Step 5 — Launch with MAGIC\_DIVIDEND\_COMPUTED](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#step-5-launch-with-magic_dividend_computed) * [What Happens On-Chain (Flow Summary)](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#what-happens-on-chain-flow-summary) * [Version Gating](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#version-gating) * [Checklist](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend#checklist) Copy import {VaultFactoryBaseV2} from "flap-sh/FlapTaxVaults/src/interfaces/VaultFactoryBaseV2.sol"; import {IVaultFactoryDividendV23} from "flap-sh/FlapTaxVaults/src/interfaces/IVaultFactory.sol"; contract MyLPVaultFactory is VaultFactoryBaseV2, IVaultFactoryDividendV23 { function factorySpecVersion() external pure override returns (string memory) { return "v2.3"; } // ... rest of factory } Copy interface IVaultFactoryDividendV23 { function resolveDividendToken( address predictedToken, uint8 launchVersion, bytes calldata launchParams ) external view returns (address dividendToken); } Copy import {IUniswapV2Factory} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; address constant PANCAKE_FACTORY = 0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73; // BSC mainnet address constant WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; function resolveDividendToken( address predictedToken, uint8 /* launchVersion */, bytes calldata /* launchParams */ ) external view override returns (address dividendToken) { // The LP pair is deterministically computable from the two token addresses. // PancakeSwap V2 uses the same CREATE2 formula as Uniswap V2. dividendToken = IUniswapV2Factory(PANCAKE_FACTORY).getPair(predictedToken, WBNB); // If the pair does not exist yet, compute it via CREATE2. if (dividendToken == address(0)) { dividendToken = _computePairAddress(predictedToken, WBNB); } } function _computePairAddress(address tokenA, address tokenB) internal pure returns (address) { (address t0, address t1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); bytes32 salt = keccak256(abi.encodePacked(t0, t1)); // PancakeSwap V2 init code hash on BSC bytes32 initCodeHash = 0x00fb7f630766e6a796048ea87d01acd3068e8ff67d078148a3fa3f4a84f69bd5; return address(uint160(uint256(keccak256(abi.encodePacked( hex"ff", PANCAKE_FACTORY, salt, initCodeHash ))))); } Copy function resolveDividendToken( address predictedToken, uint8 /* launchVersion */, bytes calldata /* launchParams */ ) external view override returns (address dividendToken) { // Child token is deployed via CREATE2 with `predictedToken` as the salt dividendToken = address(uint160(uint256(keccak256(abi.encodePacked( hex"ff", address(this), // deployer is the factory itself bytes32(uint256(uint160(predictedToken))), CHILD_TOKEN_INIT_HASH ))))); } Copy import {MAGIC_DIVIDEND_COMPUTED} from "./interfaces/IPortal.sol"; import {FactoryPolicy} from "./interfaces/IVaultSchemasV1.sol"; function tokenCreationPolicies() public pure override returns (FactoryPolicy[] memory policies) { policies = new FactoryPolicy[](1); policies[0] = FactoryPolicy({ target: "dividendToken", operator: "eq", value: abi.encode(MAGIC_DIVIDEND_COMPUTED), description: "Dividend token must be set to MAGIC_DIVIDEND_COMPUTED. " "The factory will resolve the real LP token address on-chain." }); } Copy import {LaunchValidationDataV1} from "./interfaces/IVaultFactory.sol"; function _validateBeforeLaunch(LaunchValidationDataV1 memory data) internal pure override returns (bool success, string memory reason) { if (data.dividendToken != MAGIC_DIVIDEND_COMPUTED) { return ( false, "dividendToken must be MAGIC_DIVIDEND_COMPUTED for this vault type." ); } return (true, ""); } Copy import {MAGIC_DIVIDEND_COMPUTED} from "flap-sh/FlapTaxVaults/src/interfaces/IPortal.sol"; IVaultPortal.NewTokenV6WithVaultParams memory params = IVaultPortal.NewTokenV6WithVaultParams({ // ... other fields dividendToken: MAGIC_DIVIDEND_COMPUTED, // <-- the key field factory: address(myLPVaultFactory), vaultData: abi.encode(/* your vault config */), // ... }); vaultPortal.newTokenV6WithVault{value: msg.value}(params); Copy Caller │ dividendToken = MAGIC_DIVIDEND_COMPUTED ▼ VaultPortal.newTokenV6WithVault / newTokenV7WithVault │ 1. Predict tax token address (CREATE2) │ 2. Check factory spec >= v2.3 │ 3. staticcall factory.resolveDividendToken(predictedToken, ...) │ └─ factory returns real LP / child token address │ 4. Replace dividendToken with resolved address │ 5. newVault(predictedToken, quoteToken, creator, vaultData) │ 6. Forward to Portal.newTokenV6WithVault(params with real dividendToken) ▼ Portal deploys tax token, links vault --- # Building Upgradeable Vaults — Beacon Proxy Pattern | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault.md) . This guide explains how to build an upgradeable vault for the Flap protocol. The recommended pattern is **OpenZeppelin** `**BeaconProxy**` **+** `**UpgradeableBeacon**`, with the upgrade authority gated to the **Flap Guardian** for risk-control reasons. The reference implementation lives in the [FlapVaultExample](https://github.com/flap-sh/FlapVaultExample) repository: * Source: [`src/FreeCoinBeacon.sol`](https://github.com/flap-sh/FlapVaultExample/blob/main/src/FreeCoinBeacon.sol) * Beacon-specific tests: [`test/FreeCoinBeacon.mainnet.t.sol`](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoinBeacon.mainnet.t.sol) * Bonding-curve and post-DEX dispatch tests (non-beacon variant, identical protocol surface): [`test/FreeCoin.mainnet.t.sol`](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoin.mainnet.t.sol) * Deployment scripts: [`script/mainnet/bnb/DeployFreeCoinBeacon.s.sol`](https://github.com/flap-sh/FlapVaultExample/blob/main/script/mainnet/bnb/DeployFreeCoinBeacon.s.sol) , [`script/testnet/bnb/DeployFreeCoinBeacon.s.sol`](https://github.com/flap-sh/FlapVaultExample/blob/main/script/testnet/bnb/DeployFreeCoinBeacon.s.sol) [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#overview) Overview -------------------------------------------------------------------------------------------------------------------- For a new vault factory that will deploy more than a handful of vault instances, the recommended setup is: 1. Implement the vault as an **upgradeable implementation contract** with `_disableInitializers()` in the constructor and an `initialize(...)` function gated by the `initializer` modifier. 2. In the factory's constructor, deploy the implementation and an `UpgradeableBeacon` pointing at it. The factory becomes the beacon's owner. 3. In `newVault(...)`, deploy a `BeaconProxy` and pass `abi.encodeCall(YourVault.initialize, (...))` as the proxy's initialization calldata. 4. Expose `upgradeVaultImplementation(address)` and `lockVaultUpgrades()` on the factory, **gated to the Flap Guardian only**. 5. Cover bonding-curve dispatch, post-DEX dispatch, the Guardian-only upgrade path, and the irreversible lock with integration tests. The rest of this guide walks through the rationale and the reference code. [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#why-beacon-proxies) Why beacon proxies ---------------------------------------------------------------------------------------------------------------------------------------- For production vaults, deploy the vault implementation behind an `**UpgradeableBeacon**` and create one `**BeaconProxy**` per token. Compared with deploying many immutable vault instances: 1. **Operational flexibility** — if a bug or protocol change shows up after launch, upgrading a single beacon implementation rolls the fix out to every existing and future vault of this type. No redeployment, no migration ask of token creators. 2. **Cleaner factory design** — the factory ABI-encodes initialization data and hands it to a fresh `BeaconProxy`, rather than duplicating constructor logic into every deployment path. 3. **Consistent multi-vault upgrades** — one beacon coordinates upgrades across every vault instance of the same type. 4. **Smaller audit surface** — auditors and integrators reason about a single implementation contract plus a single beacon authority model. If the vault is a one-instance-per-token, immutable-for-life design, inherit `VaultBaseV2` directly and skip the proxy. The beacon pattern in this guide is the default recommendation for **factories that deploy many vault instances**. [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#why-upgrade-authority-stays-with-the-guardian) Why upgrade authority stays with the Guardian ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Once a vault is upgradeable, the implementation address becomes the highest-risk asset in the setup. Anyone who can swap it can drain the vault, rewrite its accounting, or break dispatch. Gating that authority exclusively to the Flap Guardian is a risk-control requirement: * **Centralized incident response.** If a vulnerability is discovered post-launch — through monitoring, audit follow-up, or an incident on a sibling vault — the Guardian path enables an immediate fix without coordinating with each token creator. * **Fewer privileged keys for vault developers to maintain.** No HSM, multisig, or deployer EOA needs to remain online for the lifetime of the token on the developer's side. The upgrade key cannot be lost or compromised by the vault developer. * **Single trust assumption for users.** Users already trust the Flap protocol. A separate non-Guardian upgrade owner widens the trust surface without a corresponding benefit. * **Optional irreversible immutability.** When the project is ready, the Guardian can call `lockVaultUpgrades()` to renounce beacon ownership permanently — making the immutability commitment on-chain and verifiable. In practice this means: the factory must not retain a separate non-Guardian owner, proxy admin, beacon owner, upgrader role, deployer EOA, or external multisig with equivalent power. The factory should expose exactly two privileged entrypoints — `upgradeVaultImplementation` and `lockVaultUpgrades` — both gated to `_getGuardian()`. A factory that keeps any additional upgrade authority alongside the Guardian will not pass Flap's verification. The risk-control posture described above relies on the Guardian being the only path that can swap the implementation. [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#how-to-build-it) How to build it ---------------------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#the-implementation-contract) The implementation contract The implementation is the logic contract that all `BeaconProxy` instances delegate to. It looks like a normal vault except for three upgradeable-pattern obligations: Three things to check off: * `**_disableInitializers()**` **in the constructor** — locks the bare implementation so nobody can call `initialize` on it directly. Only proxies pointing at it can be initialized. * `**initialize(...)**` **replaces the constructor** — every freshly deployed `BeaconProxy` calls this exactly once with the configuration the factory chose. The `initializer` modifier (from OpenZeppelin's `Initializable`) makes the second call revert. * **Use OpenZeppelin's upgradeable variants** — `ReentrancyGuardUpgradeable` instead of `ReentrancyGuard`, plus the matching `__*_init()` calls inside `initialize`. The rest of the contract (`claim()`, `description()`, `vaultUISchema()`, etc.) is shaped exactly like a non-upgradeable vault. Full source is in [`src/FreeCoinBeacon.sol`](https://github.com/flap-sh/FlapVaultExample/blob/main/src/FreeCoinBeacon.sol) . ### [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#the-beacon-factory) The beacon factory The factory extends `VaultFactoryBaseV2` and owns the `UpgradeableBeacon` for this vault type: What's happening: 1. **Constructor** deploys the implementation and an `UpgradeableBeacon` pointing at it. The factory becomes the beacon's owner — the only account that can later call `upgradeTo` on the underlying `Ownable` beacon. 2. `**newVault(...)**` is invoked by VaultPortal during token launch. It deploys a fresh `BeaconProxy`, passing `abi.encodeCall(initialize, (...))` as the proxy's initialization calldata. The proxy delegates to the beacon's current implementation. 3. **Quote-token whitelist** is enforced by `isQuoteTokenSupported` and the V2.2 validation hook `_validateBeforeLaunch` — this factory accepts native BNB only. ### [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#wiring-the-upgrade-authority-to-the-guardian) Wiring the upgrade authority to the Guardian Although the factory technically owns the beacon, the only externally callable upgrade path goes through Guardian-gated functions: Two details worth highlighting: * After `lockVaultUpgrades()`, the beacon's `owner()` is `address(0)`. From that point forward, `**upgradeTo**` **reverts at the beacon's own** `**onlyOwner**` **check** — even when the Guardian re-enters `upgradeVaultImplementation`, the call bottoms out in `Ownable: caller is not the owner`. The lock is on-chain irreversible. * `isVaultUpgradesLocked()` derives from the beacon's owner, so it stays accurate even if the factory is later replaced or local tooling forgets state. `_getGuardian()` is provided by `VaultFactoryBaseV2` and resolves to the Flap Guardian address per chain — the address does not need to be hardcoded. Any additional permissioned function added to the factory or vault later should be gated the same way. ### [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#deployment) Deployment Both reference scripts construct the factory; the implementation and beacon are deployed inline: Run them with: VaultPortal is permissionless, so the factory does not need to be registered anywhere after deployment. To clear the warning flag on Flap.sh, contact the Flap team to schedule the third-party audit. [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#required-tests) Required tests -------------------------------------------------------------------------------------------------------------------------------- For an upgradeable vault, the test surface is bigger than for a plain vault: alongside the usual logic and dispatch tests, the upgrade-authority model must be asserted directly. The reference test file [`test/FreeCoinBeacon.mainnet.t.sol`](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoinBeacon.mainnet.t.sol) covers the proxy / upgrade / lock surface. The bonding-curve and post-DEX dispatch tests are in [`test/FreeCoin.mainnet.t.sol`](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoin.mainnet.t.sol) and apply unchanged here, because the integration surface is identical (same VaultPortal, same TaxProcessor dispatch). Minimum coverage expected before audit: # Scenario What to assert Reference test 1 **Registration** — factory deploys an initialized proxy when called by VaultPortal `taxToken`, `maxReward`, `cooldown` set; non-VaultPortal callers rejected `test_beaconFactoryDeploysInitializedProxyVault`, `test_factoryRejectsNonVaultPortalCaller` 2 **Initializer is locked** — implementation cannot be re-initialized `initialize()` reverts on both proxy and bare implementation after first call `test_beaconProxyClaimAndInitializerLock` 3 **Bonding-curve buy → dispatch → vault** Buy on BC, call `dispatch()`, assert vault BNB balance increased `test_buyOnBCAndDispatch` (in `FreeCoin.mainnet.t.sol`) 4 **DEX graduation → sell → dispatch → vault** Buy until graduation (`status == 4`), sell on DEX, dispatch, assert vault received BNB `test_graduateAndDispatchPostDEX` (in `FreeCoin.mainnet.t.sol`) 5 **Guardian can upgrade the beacon** Non-Guardian callers revert; Guardian successfully changes `beaconImplementation()` `test_guardianCanUpgradeBeaconImplementation`, `test_nonGuardianCannotUpgradeBeaconImplementation` 6 **Guardian can lock the beacon, and lock is irreversible** After `lockVaultUpgrades()`, `isVaultUpgradesLocked() == true` and any further upgrade reverts at the beacon's `onlyOwner` check `test_guardianCanLockVaultUpgrades`, `test_lockingIsIrreversibleAndIdempotentlyReverts` 7 **Vault logic & gating** Vault's own business rules (claim, cooldown, double-claim, failed transfer rollback, getters) `test_claimCooldownAndDoubleClaimReverts`, `test_claimRevertsWhenRecipientRejectsNativeTransfer`, etc. 8 **Schema sanity** `vaultUISchema()` and `vaultDataSchema()` return the shape the UI expects; `factorySpecVersion()` is `"v2.2"`; `onBeforeLaunch(bytes)` rejects non-native quote tokens with the correct reason `test_vaultUISchemaMetadata`, `test_factoryMetadataAndValidationHelpers` ### [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#why-both-bonding-curve-and-dex-tests-are-required) Why both bonding-curve and DEX tests are required The vault's `receive()` is invoked indirectly through `TaxProcessor.dispatch()`. The dispatch pipeline behaves slightly differently in each phase: * **Bonding curve.** Each buy or sell on Portal applies the buy or sell tax, accumulates BNB on the `TaxProcessor`, and `dispatch()` flushes it to the vault. Tests verify that dispatch reaches the vault and that `receive()` does not revert under that gas budget. * **Post-DEX (graduated).** Once the bonding curve crosses the `FOUR_FIFTHS` threshold the token graduates to DEX. Sell-side tax is then collected through the token's own swap-and-distribute path, accumulated on the `TaxProcessor`, and dispatched. Tests verify that the same vault still receives BNB after graduation, since this is the path that runs for the entire post-launch lifetime of the token. Both phases must be covered. A vault that handles bonding-curve dispatch correctly but reverts after DEX graduation is broken in production, and that bug is invisible without a graduation test. A condensed sketch of the integration tests (full code in [`test/FreeCoin.mainnet.t.sol`](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoin.mainnet.t.sol) ): Always wrap multi-call helpers like `_sell()` and `_buyOnBC()` in `vm.startPrank(...)` / `vm.stopPrank()`. `vm.prank()` only covers the **next** external call, so the second internal call (e.g. `swapExactInput` after `approve`) silently runs as the wrong sender and produces confusing reverts. See the [FlapVaultExample README](https://github.com/flap-sh/FlapVaultExample#prank-convention-in-tests) for the full convention. ### [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#tests-for-the-upgrade-and-lock-authority) Tests for the upgrade and lock authority Excerpts from [`test/FreeCoinBeacon.mainnet.t.sol`](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoinBeacon.mainnet.t.sol) : These three tests are the minimum set demonstrating that the authority model holds: the Guardian is the only path in, non-Guardian callers are rejected, and locking is genuinely irreversible. [](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#pre-audit-checklist) Pre-audit checklist ------------------------------------------------------------------------------------------------------------------------------------------ * Implementation calls `_disableInitializers()` in its constructor. * `initialize(...)` is the only place that sets vault state, and it has the `initializer` modifier. * Factory deploys the implementation and `UpgradeableBeacon` in its own constructor; the beacon owner is the factory itself. * `upgradeVaultImplementation(address)` is gated to `_getGuardian()` — no other caller passes. * `lockVaultUpgrades()` is gated to `_getGuardian()` and renounces beacon ownership. * No other upgrade path exists on the factory (no admin EOA, no proxy admin, no extra owner, no parallel multisig). * Tests assert: registration via VaultPortal, initializer lockdown, bonding-curve dispatch, post-DEX dispatch, Guardian upgrade success, non-Guardian upgrade rejection, lock irreversibility. * All tests pass against a BSC fork (`forge test --fork-url https://bsc-dataseed.bnbchain.org`). * The [`flap-vault-spec-checker`](https://github.com/flap-sh/FlapVaultExample/tree/main/.agents/skills/flap-vault-spec-checker) Copilot skill reports no ❌ findings. Once those are green, contact the Flap team to schedule the third-party audit and clear the warning flag on Flap.sh. See the [Vault & VaultFactory specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#get-your-vault-verified) page for the verification process. [PreviousGift Vault V2](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2) [NextTutorials](https://docs.flap.sh/flap/developers/vault-developers/tutorials) Last updated 27 days ago * [Overview](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#overview) * [Why beacon proxies](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#why-beacon-proxies) * [Why upgrade authority stays with the Guardian](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#why-upgrade-authority-stays-with-the-guardian) * [How to build it](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#how-to-build-it) * [The implementation contract](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#the-implementation-contract) * [The beacon factory](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#the-beacon-factory) * [Wiring the upgrade authority to the Guardian](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#wiring-the-upgrade-authority-to-the-guardian) * [Deployment](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#deployment) * [Required tests](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#required-tests) * [Why both bonding-curve and DEX tests are required](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#why-both-bonding-curve-and-dex-tests-are-required) * [Tests for the upgrade and lock authority](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#tests-for-the-upgrade-and-lock-authority) * [Pre-audit checklist](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault#pre-audit-checklist) Copy contract FreeCoinVaultUpgradeable is Initializable, VaultBaseV2, ReentrancyGuardUpgradeable { address public taxToken; uint256 public maxReward; uint256 public cooldown; // ... /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize(address _taxToken, uint256 _maxReward, uint256 _cooldown) external initializer { __ReentrancyGuard_init(); taxToken = _taxToken; maxReward = _maxReward; cooldown = _cooldown; } Copy contract FreeCoinVaultBeaconFactory is VaultFactoryBaseV2 { address public immutable beacon; constructor() { FreeCoinVaultUpgradeable impl = new FreeCoinVaultUpgradeable(); beacon = address(new UpgradeableBeacon(address(impl))); } function newVault(address taxToken, address /*quoteToken*/, address /*creator*/, bytes calldata vaultData) external override returns (address vault) { require(msg.sender == _getVaultPortal(), unicode"Only VaultPortal / 仅限 VaultPortal 调用"); (uint256 maxReward, uint256 cooldown) = abi.decode(vaultData, (uint256, uint256)); vault = address( new BeaconProxy( beacon, abi.encodeCall(FreeCoinVaultUpgradeable.initialize, (taxToken, maxReward, cooldown)) ) ); } Copy function upgradeVaultImplementation(address newImplementation) external { require(msg.sender == _getGuardian(), unicode"Only Guardian / 仅限 Guardian"); UpgradeableBeacon(beacon).upgradeTo(newImplementation); } function lockVaultUpgrades() external { require(msg.sender == _getGuardian(), unicode"Only Guardian / 仅限 Guardian"); UpgradeableBeacon(beacon).renounceOwnership(); } function isVaultUpgradesLocked() external view returns (bool locked) { locked = UpgradeableBeacon(beacon).owner() == address(0); } Copy // script/mainnet/bnb/DeployFreeCoinBeacon.s.sol function run() external { vm.startBroadcast(); FreeCoinVaultBeaconFactory factory = new FreeCoinVaultBeaconFactory(); console.log("FreeCoinVaultBeaconFactory deployed at:", address(factory)); console.log("UpgradeableBeacon deployed at: ", factory.beacon()); console.log("Beacon implementation deployed at: ", factory.beaconImplementation()); vm.stopBroadcast(); } Copy # Mainnet forge script script/mainnet/bnb/DeployFreeCoinBeacon.s.sol:DeployFreeCoinBeacon \ --rpc-url https://bsc-dataseed.bnbchain.org --broadcast --verify --private-key # Testnet forge script script/testnet/bnb/DeployFreeCoinBeacon.s.sol:DeployFreeCoinBeacon \ --rpc-url https://bsc-testnet-dataseed.bnbchain.org --broadcast --verify --private-key Copy // 1. Bonding curve function test_buyOnBCAndDispatch() public { uint256 vaultBefore = address(vault).balance; vm.startPrank(user1); _buyOnBC(token, 5 ether); vm.stopPrank(); _dispatchTax(token); // flush TaxProcessor → vault assertGt(address(vault).balance, vaultBefore); } // 2. Post-DEX function test_graduateAndDispatchPostDEX() public { vm.startPrank(user1); _buyOnBC(token, 5 ether); vm.stopPrank(); _dispatchTax(token); vm.startPrank(user2); _buyOnBC(token, 15 ether); vm.stopPrank(); // ...buy more if not yet graduated... assertEq(portal.getTokenV8Safe(token).status, 4, "should have graduated"); uint256 vaultBefore = address(vault).balance; vm.startPrank(user1); require(IERC20(token).transfer(token, 400_000 * 1e18)); // seed liquidation threshold _sell(token, IERC20(token).balanceOf(user1) - 400_000 * 1e18); vm.stopPrank(); _dispatchTax(token); assertGt(address(vault).balance, vaultBefore); } Copy function test_guardianCanUpgradeBeaconImplementation() public { FreeCoinVaultUpgradeable nextImplementation = new FreeCoinVaultUpgradeable(); vm.prank(TESTNET_GUARDIAN); factory.upgradeVaultImplementation(address(nextImplementation)); assertEq(factory.beaconImplementation(), address(nextImplementation)); } function test_nonGuardianCannotUpgradeBeaconImplementation() public { FreeCoinVaultUpgradeable nextImplementation = new FreeCoinVaultUpgradeable(); vm.expectRevert(bytes(unicode"Only Guardian / 仅限 Guardian")); vm.prank(USER); factory.upgradeVaultImplementation(address(nextImplementation)); } function test_guardianCanLockVaultUpgrades() public { assertTrue(!factory.isVaultUpgradesLocked()); vm.prank(TESTNET_GUARDIAN); factory.lockVaultUpgrades(); assertTrue(factory.isVaultUpgradesLocked()); // Further upgrades fail at the beacon's onlyOwner check, even when called by the Guardian FreeCoinVaultUpgradeable nextImplementation = new FreeCoinVaultUpgradeable(); vm.expectRevert(bytes("Ownable: caller is not the owner")); vm.prank(TESTNET_GUARDIAN); factory.upgradeVaultImplementation(address(nextImplementation)); } --- # Flap AI Oracle | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle.md) . [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#id-1.-overview-and-roadmap) 1\. Overview & Roadmap ---------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#overview) Overview Three simple steps — build your prompt on-chain, let us handle the AI, and get back a verified decision. **FlapAIProvider** is a standardized on-chain AI oracle plugin built for the Flap ecosystem. It gives Vault contracts (and any smart contract) access to verifiable LLM reasoning via a **commit-and-reveal** scheme inspired by Chainlink VRF. FlapAIProvider separates concerns of AI-powered smart contracts cleanly: * **On-chain (commit):** The consumer calls `reason()` with a prompt and pays a BNB fee. The contract records the request and emits an event. * **Off-chain:** The oracle backend listens for the event, feeds the prompt to the selected LLM, and obtains a numeric choice. * **On-chain (reveal):** The oracle calls `fulfillReasoning()` with the result and an IPFS CID linking to the full reasoning proof. The consumer's callback is invoked automatically. Every request is permanently auditable: the IPFS CID stored on-chain pins the raw LLM inputs, outputs, temperature, model version, and salt that produced the decision. ### [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#architecture) Architecture ![](https://docs.flap.sh/flap/~gitbook/image?url=https%3A%2F%2F2671086575-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F5KujUBwRoHVjn8OZEgtZ%252Fuploads%252Fgit-blob-5d52fd6f6e0e7572426cf3cbe7989a243a424371%252Fimage-ai.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=87df5381&sv=2) Flap AI Oracle ### [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#roadmap) Roadmap Release Features **v1 (current)** Commit-and-reveal reasoning, multi-model registry, IPFS proof anchoring, refund fallback, `FlapAIConsumerBase` integration base, **tool calling** support (currently `ave_token_tool` available) **v2 (next)** Additional tools, multi-step reasoning workflows, and enhanced oracle features > **Note:** The LLM can only return a single numeric choice in the range `[0, numOfChoices)`.\ \ * * *\ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#id-2.-deployed-addresses-and-supported-models)\ \ 2\. Deployed Addresses & Supported Models\ \ \ ------------------------------------------------------------------------------------------------------------------------------------------------------------\ \ ### \ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#deployed-addresses)\ \ Deployed Addresses\ \ Network\ \ Chain ID\ \ Address\ \ AI Explorer\ \ Max Gas Limit\ \ BSC Testnet\ \ 97\ \ [`0xFfddcE44e8cFf7703Fd85118524bfC8B2f70b744`](https://testnet.bscscan.com/address/0xFfddcE44e8cFf7703Fd85118524bfC8B2f70b744)\ \ [AI Scan BNB](https://flap.sh/ai/bnb-testnet/scan)\ \ 2,000,000\ \ BNB Mainnet\ \ 56\ \ [`0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39`](https://bscscan.com/address/0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39)\ \ [AI Scan BNB](https://flap.sh/ai/bnb/scan)\ \ 2,000,000\ \ **Max Gas Limit:** The maximum gas limit supported for a `fulfillReasoning` callback.\ \ **Testnet note:** Since AI inference is expensive, our testnet LLM backend operates with a limited budget. Fulfillment may not respond if funds are exhausted. If you'd like to test on testnet, feel free to reach out to us. Note that some tools may not be supported on testnet (e.g. `ave_token_tool`).\ \ ### \ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#supported-models)\ \ Supported Models\ \ The following models are available on both BSC Testnet and BNB Mainnet:\ \ Model ID\ \ Name\ \ Price per Request\ \ `0`\ \ `google/gemini-3-flash`\ \ ~0.01 BNB~ 0.005 BNB\ \ `1`\ \ `anthropic/claude-sonnet-4.6`\ \ 0.05 BNB\ \ `2`\ \ `deepseek/deepseek-r1`\ \ 0.03 BNB\ \ `3`\ \ `deepseek/deepseek-v4-flash`\ \ 0.01 BNB\ \ Query any model on-chain at any time:\ \ * * *\ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#id-3.-tool-calling-support)\ \ 3\. Tool Calling Support\ \ \ ------------------------------------------------------------------------------------------------------------------------\ \ FlapAIProvider supports **tool calling**, allowing the LLM to fetch real-time external data before making a decision. The oracle backend automatically executes tool calls embedded in your prompt and injects the results into the LLM context.\ \ ### \ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#available-tools)\ \ Available Tools\ \ Currently, the following tool is supported:\ \ #### \ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#ave_token_tool)\ \ `ave_token_tool`\ \ Fetches comprehensive market data for a Flap token, including:\ \ * Current price and market cap\ \ * Price change percentages\ \ * Trading volume (24h, 7d)\ \ * Holder count\ \ * Liquidity status\ \ \ **Usage example in prompt:**\ \ The oracle will automatically detect the tool request, fetch the token data, and provide it to the LLM before it makes a choice.\ \ **Need additional tools?** If you need specific data sources or APIs to implement your vault strategy, feel free to reach out to us. We're actively expanding the tool library based on community needs.\ \ * * *\ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#id-4.-how-to-integrate)\ \ 4\. How to Integrate\ \ \ ----------------------------------------------------------------------------------------------------------------\ \ ### \ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#step-1-extend-flapaiconsumerbase)\ \ Step 1 — Extend `FlapAIConsumerBase`\ \ Your contract must inherit `FlapAIConsumerBase` and implement three members:\ \ ### \ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#step-2-submit-a-reasoning-request)\ \ Step 2 — Submit a Reasoning Request\ \ The vault triggers `reason()` inside `receive()`. An internal `_shouldReason()` method encapsulates the go/no-go logic (balance threshold, cooldown, etc.) so the `receive()` body stays clean:\ \ > **Cost note:** `reason()` does **not** refund excess `msg.value`. Overpayment becomes protocol revenue. Always pass exactly `model.price` (or query it on-chain first).\ \ ### \ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#step-3-verify-a-fulfilled-request)\ \ Step 3 — Verify a Fulfilled Request\ \ After the oracle calls back, the full reasoning proof is available on-chain forever:\ \ ### \ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#step-4-override-_getflapaiprovider-for-testing)\ \ Step 4 — Override `_getFlapAIProvider()` for Testing\ \ In tests or on unsupported chains, override the address resolver:\ \ ### \ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#full-example-minimal-consumer)\ \ Full Example — Minimal Consumer\ \ * * *\ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#id-5.-references)\ \ 5\. References\ \ \ ----------------------------------------------------------------------------------------------------\ \ ### \ \ [](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#iflapaiprovider.sol)\ \ `IFlapAIProvider.sol`\ \ [PreviousBeta: PVE Curve](https://docs.flap.sh/flap/developers/preview/beta-pve-curve)\ [NextFlap Candy Box](https://docs.flap.sh/flap/developers/preview/flap-candy-box)\ \ Last updated 2 months ago\ \ * [1\. Overview & Roadmap](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#id-1.-overview-and-roadmap)\ \ * [Overview](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#overview)\ \ * [Architecture](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#architecture)\ \ * [Roadmap](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#roadmap)\ \ * [2\. Deployed Addresses & Supported Models](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#id-2.-deployed-addresses-and-supported-models)\ \ * [Deployed Addresses](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#deployed-addresses)\ \ * [Supported Models](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#supported-models)\ \ * [3\. Tool Calling Support](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#id-3.-tool-calling-support)\ \ * [Available Tools](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#available-tools)\ \ * [4\. How to Integrate](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#id-4.-how-to-integrate)\ \ * [Step 1 — Extend FlapAIConsumerBase](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#step-1-extend-flapaiconsumerbase)\ \ * [Step 2 — Submit a Reasoning Request](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#step-2-submit-a-reasoning-request)\ \ * [Step 3 — Verify a Fulfilled Request](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#step-3-verify-a-fulfilled-request)\ \ * [Step 4 — Override \_getFlapAIProvider() for Testing](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#step-4-override-_getflapaiprovider-for-testing)\ \ * [Full Example — Minimal Consumer](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#full-example-minimal-consumer)\ \ * [5\. References](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#id-5.-references)\ \ * [IFlapAIProvider.sol](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle#iflapaiprovider.sol)\ \ \ Copy\ \ IFlapAIProvider.Model memory m = IFlapAIProvider(flapAIProvider).getModel(modelId);\ // m.name, m.price, m.enabled\ \ Copy\ \ string memory prompt = string(abi.encodePacked(\ "I am managing a vault for token 0x1234...5678. ",\ "My main goal is to use my fund for market making. ",\ "Check the market data (use ave_token_tool) of this token and decide: ",\ "(0) buy tokens to support the floor, ",\ "(1) sell holdings to gain more funds for future market making, ",\ "(2) generate volumes only (buy and sell immediately)."\ ));\ \ Copy\ \ // SPDX-License-Identifier: MIT\ pragma solidity ^0.8.13;\ \ import {FlapAIConsumerBase, IFlapAIProvider} from "src/plugins/AIProvider/IFlapAIProvider.sol";\ \ contract MyVault is FlapAIConsumerBase {\ // Track the outstanding request.\ uint256 private _lastRequestId;\ \ // Required: expose the pending request ID (0 = none).\ function lastRequestId() public view override returns (uint256) {\ return _lastRequestId;\ }\ \ // Required: handle the LLM's decision.\ // At most 1M gas is forwarded, so keep it efficient and avoid complex logic or external calls.\ function _fulfillReasoning(uint256 requestId, uint8 choice) internal override {\ require(requestId == _lastRequestId, "unknown request");\ _lastRequestId = 0;\ \ if (choice == 0) {\ _executeBuy();\ } else if (choice == 1) {\ _executeSell();\ } else {\ // noop\ }\ }\ \ // Required: handle a refund / failed request.\ function _onFlapAIRequestRefunded(uint256 requestId) internal override {\ require(requestId == _lastRequestId, "unknown request");\ _lastRequestId = 0;\ // Re-enable any paused operations or retry logic here.\ }\ }\ \ Copy\ \ receive() external payable {\ // Only one request at a time.\ if (_lastRequestId != 0) return;\ \ // Delegate the go/no-go decision to an internal helper.\ if (!_shouldReason()) return;\ \ uint256 MODEL_ID = 0; // google/gemini-3-flash\ uint8 NUM_CHOICES = 3;\ \ // Build a self-contained prompt that defines all choices.\ string memory prompt = string(abi.encodePacked(\ "You are a DeFi vault manager. The vault holds 10 BNB. ",\ "Current price is $300. 7-day trend is +15%. ",\ "Choose one action:\n",\ "0 = buy more BNB\n",\ "1 = sell all BNB\n",\ "2 = do nothing\n",\ "Respond with only the number of your choice."\ ));\ \ // Fetch the required fee from the registry.\ IFlapAIProvider provider = IFlapAIProvider(_getFlapAIProvider());\ uint256 fee = provider.getModel(MODEL_ID).price;\ \ _lastRequestId = provider.reason{value: fee}(MODEL_ID, prompt, NUM_CHOICES);\ }\ \ /// @dev Override to implement your go/no-go logic (e.g. balance threshold, cooldown).\ function _shouldReason() internal view virtual returns (bool);\ \ Copy\ \ string memory cid = IFlapAIProvider(_getFlapAIProvider()).getReasoningCid(requestId);\ // Fetch from IPFS: https://ipfs.io/ipfs/\ \ Copy\ \ function _getFlapAIProvider() internal view override returns (address) {\ return address(mockProvider); // your test double\ }\ \ Copy\ \ // SPDX-License-Identifier: MIT\ pragma solidity ^0.8.13;\ \ import {FlapAIConsumerBase, IFlapAIProvider} from "src/plugins/AIProvider/IFlapAIProvider.sol";\ import {VaultBase} from "src/interfaces/VaultBase.sol";\ \ /// @notice Minimal example consumer that lets an LLM decide between buy / sell / noop.\ contract MinimalVault is VaultBase, FlapAIConsumerBase {\ uint256 public lastRequestId;\ uint8 public lastChoice;\ \ uint256 constant MODEL_ID = 0; // google/gemini-3-flash\ uint8 constant NUM_CHOICES = 3;\ uint256 constant THRESHOLD = 1 ether; // only ask AI when vault holds > 1 BNB\ \ /// @notice Accept incoming tax revenue from the token.\ /// @dev Triggers AI reasoning when _shouldReason() approves.\ receive() external payable {\ // Only send one request at a time; skip if one is already pending.\ if (lastRequestId != 0) return;\ \ // Delegate the go/no-go decision to an internal helper.\ if (!_shouldReason()) return;\ \ IFlapAIProvider provider = IFlapAIProvider(_getFlapAIProvider());\ uint256 fee = provider.getModel(MODEL_ID).price;\ \ // Build a self-contained prompt that describes the vault state and\ // the meaning of every numbered choice.\ string memory prompt = string(abi.encodePacked(\ "You are a DeFi vault manager for a tax token on BNB Chain. ",\ "The vault currently holds ", _uint2str(address(this).balance / 1 ether), " BNB. ",\ "The threshold to act is 1 BNB. ",\ "Decide what to do with the accumulated BNB:\n",\ "0 = buyback tokens using all available BNB\n",\ "1 = hold and accumulate more BNB\n",\ "2 = distribute BNB to token holders\n",\ "Respond with only the number of your choice."\ ));\ \ lastRequestId = provider.reason{value: fee}(MODEL_ID, prompt, NUM_CHOICES);\ }\ \ /// @dev Override to implement your trigger logic (e.g. balance threshold, cooldown).\ function _shouldReason() internal view override returns (bool) {}\ \ function _fulfillReasoning(uint256 requestId, uint8 choice) internal override {\ require(requestId == lastRequestId);\ lastRequestId = 0;\ lastChoice = choice;\ \ if (choice == 0) {\ // TODO: execute buyback via _getPortal()\ } else if (choice == 1) {\ // hold — do nothing\ } else if (choice == 2) {\ // TODO: distribute BNB to token holders\ }\ }\ \ function _onFlapAIRequestRefunded(uint256 requestId) internal override {\ require(requestId == lastRequestId);\ lastRequestId = 0;\ // Next receive() call will retry automatically.\ }\ \ /// @inheritdoc VaultBase\ function description() public view override returns (string memory) {\ return string(abi.encodePacked(\ "Minimal AI vault. Balance: ", _uint2str(address(this).balance / 1 ether), " BNB. ",\ "Last choice: ", _uint2str(lastChoice), "."\ ));\ }\ \ // ----------------------------------------------------------------\ // Internal helpers\ // ----------------------------------------------------------------\ \ function _uint2str(uint256 v) internal pure returns (string memory) {\ if (v == 0) return "0";\ uint256 tmp = v;\ uint256 digits;\ while (tmp != 0) { digits++; tmp /= 10; }\ bytes memory buf = new bytes(digits);\ while (v != 0) { buf[--digits] = bytes1(uint8(48 + v % 10)); v /= 10; }\ return string(buf);\ }\ }\ \ Copy\ \ // SPDX-License-Identifier: MIT\ pragma solidity ^0.8.13;\ \ // ============================================================\ // IFlapAIProvider Interface\ // ============================================================\ \ /// @title IFlapAIProvider\ /// @notice Oracle interface for the FlapAIProvider commit-and-reveal AI reasoning service.\ /// @dev The FlapAIProvider operates as a commit-and-reveal oracle inspired by Chainlink VRF.\ /// Consumers (Vault contracts) submit a reasoning request on-chain by calling `reason()`,\ /// which emits an event. The off-chain oracle backend picks up the event, feeds the prompt\ /// to an LLM, and posts the result back via `fulfillReasoning()`. If the backend cannot\ /// process the request, it calls `refundRequest()` to return the BNB fee and notify the\ /// consumer. Consumers must extend `FlapAIConsumerBase` to receive callbacks securely.\ ///\ /// TOOL CALLING: The oracle backend supports tool calling, allowing the LLM to fetch\ /// real-time external data (e.g. token market data, prices) before producing its choice.\ /// Consumers embed tool invocations in the prompt using a structured format; the backend\ /// executes the tools and injects their results into the LLM context automatically.\ /// For the full list of supported tools, see:\ /// https://docs.flap.sh/flap/developers/preview/flap-ai-oracle\ ///\ /// Example — `ave_token_info` tool:\ /// "I am managing a vault for token 0x....7777, "\ /// "my main goal is to use my fund to market making for this token. "\ /// "Check the market data (use ave_token_info tool) of this token and then decide what to do: "\ /// "(0) buy tokens to support the floor "\ /// "(1) sell my holdings to gain more funds for future market making "\ /// "(2) generate volumes only (i.e: buy and then sell immediately)."\ interface IFlapAIProvider {\ // ----------------------------------------------------------------\ // Structs\ // ----------------------------------------------------------------\ \ /// @notice Represents a registered LLM model.\ /// @param name Human-readable name of the model (e.g. "gpt-4").\ /// @param price Per-request fee in native currency (BNB), in wei.\ /// @param enabled Whether the model currently accepts new requests.\ struct Model {\ string name;\ uint256 price;\ bool enabled;\ }\ \ /// @notice Lifecycle state of an AI reasoning request.\ /// @dev NONE = default, request was never created.\ /// PENDING = request submitted on-chain, awaiting oracle fulfillment.\ /// FULFILLED = oracle fulfilled the request and consumer callback succeeded.\ /// UNDELIVERED = oracle fulfilled but consumer callback reverted (result stored but not delivered).\ /// REFUNDED = oracle refunded the request fee to the consumer.\ enum RequestStatus {\ NONE,\ PENDING,\ FULFILLED,\ UNDELIVERED,\ REFUNDED\ }\ \ /// @notice Stores all data for a single AI reasoning request.\ /// @dev Tightly packed into two 32-byte storage slots:\ /// Slot 0 (immutable after `reason()`): consumer (160) + modelId (16) + numOfChoices (8) + timestamp (64) = 248 bits\ /// Slot 1 (written by oracle): feePaid (128) + status (8) + choice (8) + reserved (112) = 256 bits\ /// Separating immutable fields from mutable oracle results means `fulfillReasoning` and\ /// `refundRequest` only issue a single SSTORE to slot 1, leaving slot 0 untouched.\ /// @param consumer Address of the consuming contract that submitted the request.\ /// @param modelId ID of the LLM model used (max 65535 distinct models).\ /// @param numOfChoices Number of choices the LLM can pick from (valid range 0..numOfChoices-1).\ /// @param timestamp `block.timestamp` when the request was submitted via `reason()`.\ /// @param feePaid BNB amount paid with the request (full `msg.value`), capped at uint128.\ /// @param status Current lifecycle status of the request.\ /// @param choice The LLM's chosen action index; only valid when status is FULFILLED or UNDELIVERED.\ /// @param reserved Reserved for future upgrades.\ struct Request {\ // slot 0 — immutable after reason()\ address consumer; // 160 bits\ uint16 modelId; // 16 bits\ uint8 numOfChoices; // 8 bits\ uint64 timestamp; // 64 bits\ // slot 1 — written by fulfillReasoning() / refundRequest()\ uint128 feePaid; // 128 bits\ RequestStatus status; // 8 bits\ uint8 choice; // 8 bits\ uint112 reserved; // 112 bits\ }\ \ // ----------------------------------------------------------------\ // Custom Errors\ // ----------------------------------------------------------------\ \ /// @notice Read-only summary of a request, used exclusively by explorer view functions.\ /// @dev Avoids multiple RPC calls per request in list views by bundling all fields —\ /// including the IPFS CID — into a single return value.\ /// @param requestId The unique request ID.\ /// @param consumer Address of the consuming contract that submitted the request.\ /// @param modelId ID of the LLM model used.\ /// @param numOfChoices Number of choices the LLM could pick from.\ /// @param timestamp `block.timestamp` when the request was submitted.\ /// @param feePaid BNB amount paid with the request (in wei).\ /// @param status Current lifecycle status of the request.\ /// @param choice The LLM's chosen action index; only meaningful when status is FULFILLED or UNDELIVERED.\ /// @param reasoningCid IPFS CID of the reasoning proof; non-empty only when status is FULFILLED or UNDELIVERED.\ struct RequestView {\ uint256 requestId;\ address consumer;\ uint16 modelId;\ uint8 numOfChoices;\ uint64 timestamp;\ uint128 feePaid;\ RequestStatus status;\ uint8 choice;\ string reasoningCid;\ }\ \ /// @notice Reverts when the prompt byte length exceeds `maxPromptLength`.\ /// @param promptLength Actual length of the provided prompt in bytes.\ /// @param maxPromptLength Current maximum allowed prompt length.\ error FlapAIProviderPromptExceedsMaxLength(uint256 promptLength, uint256 maxPromptLength);\ \ /// @notice Reverts when `numOfChoices` is zero; there must be at least one choice.\ /// @param numOfChoices The invalid zero value that was passed.\ error FlapAIProviderInvalidNumOfChoices(uint8 numOfChoices);\ \ /// @notice Reverts when `fulfillReasoning` or `refundRequest` is called on a request that is not PENDING.\ /// @param requestId The request ID that is not in the PENDING state.\ error FlapAIProviderRequestNotPending(uint256 requestId);\ \ /// @notice Reverts when the oracle's `choice` is >= `numOfChoices`.\ /// @param choice The out-of-range choice returned by the oracle.\ /// @param numOfChoices The number of valid choices for the request.\ error FlapAIProviderChoiceOutOfRange(uint8 choice, uint8 numOfChoices);\ \ /// @notice Reverts when the BNB sent with `reason()` is less than the model's required price.\ /// @param sent Amount of BNB sent (in wei).\ /// @param required Minimum required fee for the selected model (in wei).\ error FlapAIProviderInsufficientFee(uint256 sent, uint256 required);\ \ /// @notice Reverts when `reason()` or `getModel()` is called with a `modelId` that was never registered.\ /// @param modelId The unregistered model ID.\ error FlapAIProviderModelNotRegistered(uint256 modelId);\ \ /// @notice Reverts when `reason()` is called with a registered but currently disabled model.\ /// @param modelId The disabled model ID.\ error FlapAIProviderModelNotEnabled(uint256 modelId);\ \ /// @notice Reverts when `setCallbackGasLimit()` is called with a value below the minimum of 1_000_000.\ /// @param provided The gas limit value that was passed.\ /// @param minimum The minimum allowed gas limit (1_000_000).\ error FlapAIProviderCallbackGasLimitTooLow(uint256 provided, uint256 minimum);\ \ // ----------------------------------------------------------------\ // Events\ // ----------------------------------------------------------------\ \ /// @notice Emitted when a new AI reasoning request is submitted.\ /// @param requestId Unique identifier for this request.\ /// @param consumer Address of the consuming contract.\ /// @param modelId ID of the LLM model selected.\ /// @param prompt The full prompt string sent to the oracle.\ /// @param numOfChoices Number of choices the LLM can return.\ /// @param feePaid BNB amount paid with the request (full msg.value).\ event FlapAIProviderRequestMade(\ uint256 requestId, address consumer, uint256 modelId, string prompt, uint8 numOfChoices, uint256 feePaid\ );\ \ /// @notice Emitted when the oracle successfully fulfills a request and the consumer callback succeeds.\ /// @param requestId The fulfilled request ID.\ /// @param consumer Address of the consuming contract.\ /// @param choice The choice returned by the LLM (0..numOfChoices-1).\ /// @param reasoningDetailsIpfsCid IPFS CID of the full reasoning proof stored on IPFS.\ event FlapAIProviderRequestFulfilled(\ uint256 requestId, address consumer, uint8 choice, string reasoningDetailsIpfsCid\ );\ \ /// @notice Emitted when the oracle fulfills a request but the consumer callback reverts.\ /// @param requestId The request ID whose consumer callback failed.\ /// @param consumer Address of the consuming contract.\ /// @param choice The choice returned by the LLM.\ /// @param reasoningDetailsIpfsCid IPFS CID of the reasoning proof (still stored on-chain).\ /// @param reason The revert data from the consumer callback.\ event FlapAIProviderRequestUndelivered(\ uint256 requestId, address consumer, uint8 choice, string reasoningDetailsIpfsCid, bytes reason\ );\ \ /// @notice Emitted when the oracle refunds a pending request.\ /// @param requestId The refunded request ID.\ /// @param consumer Address of the consuming contract that receives the refund.\ /// @param refundAmount BNB amount refunded (equals the original feePaid).\ event FlapAIProviderRequestRefunded(uint256 requestId, address consumer, uint256 refundAmount);\ \ /// @notice Emitted when the oracle marks a request as refunded but the consumer callback\ /// reverts or runs out of gas, leaving the BNB stranded in the provider.\ /// @param requestId The refunded request ID.\ /// @param consumer Address of the consuming contract whose callback failed.\ /// @param refundAmount BNB amount that could not be delivered (equals the original feePaid).\ /// @param reason The revert data from the consumer callback (empty on OOG).\ event FlapAIProviderRefundUndelivered(uint256 requestId, address consumer, uint256 refundAmount, bytes reason);\ \ /// @notice Emitted when the maximum prompt length is updated.\ /// @param oldMaxPromptLength Previous maximum prompt length in bytes.\ /// @param newMaxPromptLength New maximum prompt length in bytes.\ event FlapAIProviderMaxPromptLengthUpdated(uint256 oldMaxPromptLength, uint256 newMaxPromptLength);\ \ /// @notice Emitted when the consumer callback gas limit is updated.\ /// @param oldCallbackGasLimit Previous gas limit for consumer callbacks.\ /// @param newCallbackGasLimit New gas limit for consumer callbacks.\ event FlapAIProviderCallbackGasLimitUpdated(uint256 oldCallbackGasLimit, uint256 newCallbackGasLimit);\ \ /// @notice Emitted when a new LLM model is registered.\ /// @param modelId ID of the newly registered model.\ /// @param name Human-readable name of the model.\ /// @param price Per-request fee in native BNB (wei).\ event FlapAIProviderModelRegistered(uint256 modelId, string name, uint256 price);\ \ // ----------------------------------------------------------------\ // Functions\ // ----------------------------------------------------------------\ \ /// @notice Submit an AI reasoning request to the oracle.\ /// @dev Validates that:\ /// 1. `modelId` is a registered model (reverts FlapAIProviderModelNotRegistered).\ /// 2. The model is enabled (reverts FlapAIProviderModelNotEnabled).\ /// 3. `numOfChoices > 0` (reverts FlapAIProviderInvalidNumOfChoices).\ /// 4. `bytes(prompt).length <= maxPromptLength` (reverts FlapAIProviderPromptExceedsMaxLength).\ /// 5. `msg.value >= model.price` (reverts FlapAIProviderInsufficientFee).\ /// Any excess BNB beyond the model price is intentionally NOT refunded — it becomes protocol revenue.\ /// Emits {FlapAIProviderRequestMade}.\ ///\ /// TOOL CALLING: The oracle supports tool calling so the LLM can pull live data before\ /// reasoning. Declare the tools you want available inside the prompt string. The backend\ /// will execute any tool calls the LLM makes and feed the results back automatically.\ /// For the complete list of supported tools visit:\ /// https://docs.flap.sh/flap/developers/preview/flap-ai-oracle\ ///\ /// Example using `ave_token_info` to fetch live market data for a token:\ /// "I am managing a vault for token 0x....7777, "\ /// "my main goal is to use my fund to market making for this token. "\ /// "Check the market data (use ave_token_info tool) of this token and then decide what to do: "\ /// "(0) buy tokens to support the floor "\ /// "(1) sell my holdings to gain more funds for future market making "\ /// "(2) generate volumes only (i.e: buy and then sell immediately)."\ /// @param modelId ID of the LLM model to use for this request.\ /// @param prompt Combined system + user prompt. The consumer must embed the choice meanings\ /// (e.g., "0 = buy, 1 = sell") inside the prompt string. Tool descriptions\ /// may also be included here to enable tool calling (see @dev above).\ /// @param numOfChoices Number of choices the LLM may return (valid responses are 0..numOfChoices-1).\ /// @return requestId Unique uint256 identifier for this request. Store it to correlate with the callback.\ function reason(uint256 modelId, string calldata prompt, uint8 numOfChoices)\ external\ payable\ returns (uint256 requestId);\ \ /// @notice Return the full Model struct for a registered model.\ /// @dev Reverts with `FlapAIProviderModelNotRegistered` if the `modelId` was never registered.\ /// Does NOT revert for disabled models — callers can inspect the `enabled` field.\ /// @param modelId ID of the model to query.\ /// @return model The Model struct containing `name`, `price`, and `enabled`.\ function getModel(uint256 modelId) external view returns (Model memory model);\ \ /// @notice Fulfill a pending AI reasoning request.\ /// @dev Restricted to the oracle backend (FULFILLER_ROLE). Validates:\ /// 1. Request status is PENDING (reverts FlapAIProviderRequestNotPending).\ /// 2. `choice < request.numOfChoices` (reverts FlapAIProviderChoiceOutOfRange).\ /// Stores `reasoningDetailsIpfsCid` on-chain, then calls `fulfillReasoning(requestId, choice)`\ /// on the consumer inside a try/catch:\ /// - On success: status → FULFILLED, emits {FlapAIProviderRequestFulfilled}.\ /// - On consumer revert: status → UNDELIVERED, emits {FlapAIProviderRequestUndelivered}.\ /// The IPFS CID is always stored regardless of callback outcome.\ /// @param requestId The ID of the pending request to fulfill.\ /// @param choice The LLM's chosen action (must be < numOfChoices).\ /// @param reasoningDetailsIpfsCid IPFS CID of the full reasoning proof document.\ function fulfillReasoning(uint256 requestId, uint8 choice, string calldata reasoningDetailsIpfsCid) external;\ \ /// @notice Refund a pending request when the oracle cannot process it.\ /// @dev Restricted to the oracle backend (FULFILLER_ROLE). Validates that the request is PENDING.\ /// Sets status to REFUNDED, transfers the original `feePaid` BNB back to the consumer,\ /// then calls `onFlapAIRequestRefunded{gas: 1_000_000}(requestId)` on the consumer.\ /// Emits {FlapAIProviderRequestRefunded}.\ /// @param requestId The ID of the pending request to refund.\ function refundRequest(uint256 requestId) external;\ \ /// @notice Returns the current maximum allowed prompt length in bytes.\ /// @return The current `_maxPromptLength` value (default: 6000).\ function maxPromptLength() external view returns (uint256);\ \ /// @notice Update the maximum allowed prompt length in bytes.\ /// @dev Restricted to DEFAULT_ADMIN_ROLE. Emits {FlapAIProviderMaxPromptLengthUpdated}.\ /// @param newMaxPromptLength The new maximum prompt length in bytes.\ function setMaxPromptLength(uint256 newMaxPromptLength) external;\ \ /// @notice Returns the gas limit forwarded to consumer callbacks (`fulfillReasoning` and `onFlapAIRequestRefunded`).\ /// @return The current `_maxCallbackGas` value (default: 1_000_000).\ function callbackGasLimit() external view returns (uint256);\ \ /// @notice Update the gas limit forwarded to consumer callbacks.\ /// @dev Restricted to DEFAULT_ADMIN_ROLE. Reverts with `FlapAIProviderCallbackGasLimitTooLow` if\ /// `newCallbackGasLimit` is less than 1_000_000. Emits {FlapAIProviderCallbackGasLimitUpdated}.\ /// @param newCallbackGasLimit The new gas limit for consumer callbacks (must be >= 1_000_000).\ function setCallbackGasLimit(uint256 newCallbackGasLimit) external;\ \ // ----------------------------------------------------------------\ // Explorer View Functions\ // ----------------------------------------------------------------\ \ /// @notice Returns the total number of reasoning requests ever submitted.\ /// @dev Equivalent to `_nextRequestId - 1`. Used by the explorer to compute pagination metadata.\ /// @return total The total request count.\ function getTotalRequests() external view returns (uint256 total);\ \ /// @notice Returns the total number of reasoning requests submitted by a specific consumer.\ /// @dev Used to compute per-consumer pagination metadata.\ /// @param consumer The consumer address to query.\ /// @return total The number of requests made by `consumer`.\ function getTotalRequestsByConsumer(address consumer) external view returns (uint256 total);\ \ /// @notice Returns a single `RequestView` for the given request ID.\ /// @dev Populates `reasoningCid` for all statuses (non-empty only when FULFILLED or UNDELIVERED).\ /// Returns a zero-valued struct if `requestId` was never created.\ /// @param requestId The request ID to look up.\ /// @return view_ The populated `RequestView` for the given request.\ function getRequest(uint256 requestId) external view returns (RequestView memory view_);\ \ /// @notice Returns up to `limit` requests in newest-first order, starting at `offset` from the end.\ /// @dev `offset = 0, limit = 10` returns the 10 most recent requests.\ /// Returns an empty array if `offset >= getTotalRequests()`.\ /// Clamps the result to available entries — never reverts out-of-bounds.\ /// `reasoningCid` is populated inline for each entry.\ /// @param offset Zero-based offset from the most recent request (0 = most recent).\ /// @param limit Maximum number of entries to return.\ /// @return views Array of `RequestView` in newest-first order.\ function getRecentRequests(uint256 offset, uint256 limit) external view returns (RequestView[] memory views);\ \ /// @notice Returns up to `limit` requests by a specific consumer in newest-first order.\ /// @dev Same pagination semantics as `getRecentRequests` but scoped to a single consumer.\ /// Returns an empty array if `offset >= getTotalRequestsByConsumer(consumer)`.\ /// @param consumer The consumer address to filter by.\ /// @param offset Zero-based offset from the consumer's most recent request.\ /// @param limit Maximum number of entries to return.\ /// @return views Array of `RequestView` in newest-first order for the given consumer.\ function getRequestsByConsumer(address consumer, uint256 offset, uint256 limit)\ external\ view\ returns (RequestView[] memory views);\ }\ \ // ============================================================\ // FlapAIConsumerBase Abstract Contract\ // ============================================================\ \ /// @title FlapAIConsumerBase\ /// @notice Base contract for contracts consuming FlapAIProvider responses.\ /// @dev Inheritors must override:\ /// - `_fulfillReasoning(uint256 requestId, uint8 choice)`: executes the action chosen by the LLM.\ /// - `_onFlapAIRequestRefunded(uint256 requestId)`: handles refund cleanup or retry logic.\ /// - `lastRequestId()`: returns the consumer's most recent pending request ID (0 if none).\ /// The `onlyFlapAIProvider` modifier is applied automatically to the external entry-points\ /// `fulfillReasoning` and `onFlapAIRequestRefunded`, ensuring that only the FlapAIProvider\ /// contract can invoke them. The provider address is resolved at runtime via\ /// `_getFlapAIProvider()` using `block.chainid`, so no constructor parameter is needed.\ abstract contract FlapAIConsumerBase {\ // ----------------------------------------------------------------\ // Custom Errors\ // ----------------------------------------------------------------\ \ /// @notice Reverts when a function guarded by `onlyFlapAIProvider` is called by a non-provider address.\ error FlapAIConsumerOnlyProvider();\ \ /// @notice Reverts when `_getFlapAIProvider()` is queried on an unsupported chain.\ /// @param chainId The unsupported chain ID.\ error FlapAIConsumerUnsupportedChain(uint256 chainId);\ \ // ----------------------------------------------------------------\ // Modifier\ // ----------------------------------------------------------------\ \ /// @dev Guards external entry-points so only the FlapAIProvider contract can call them.\ /// Reverts with `FlapAIConsumerOnlyProvider` if called by any other address.\ modifier onlyFlapAIProvider() {\ if (msg.sender != _getFlapAIProvider()) revert FlapAIConsumerOnlyProvider();\ _;\ }\ \ // ----------------------------------------------------------------\ // Provider Address Resolution\ // ----------------------------------------------------------------\ \ /// @notice Returns the FlapAIProvider proxy address for the current chain.\ /// @dev Uses `block.chainid` to resolve the stable proxy address:\ /// - Chain 56 (BSC Mainnet): returns `address(0)` — placeholder, update when deployed.\ /// - Chain 97 (BSC Testnet): returns `address(0)` — placeholder, update when deployed.\ /// - Any other chain: reverts with `FlapAIConsumerUnsupportedChain`.\ /// Subclasses may override this function to support additional chains or test environments.\ /// @return The address of the FlapAIProvider proxy on the current chain.\ function _getFlapAIProvider() internal view virtual returns (address) {\ uint256 id = block.chainid;\ if (id == 56) {\ return 0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39;\ } else if (id == 97) {\ return 0xFfddcE44e8cFf7703Fd85118524bfC8B2f70b744;\ } else {\ revert FlapAIConsumerUnsupportedChain(id);\ }\ }\ \ // ----------------------------------------------------------------\ // Consumer State (abstract)\ // ----------------------------------------------------------------\ \ /// @notice Returns the request ID of the most recent AI reasoning request made by this consumer.\ /// @dev Must be overridden by the consuming contract. Returns `0` if no request has been made yet.\ /// This allows the provider backend and frontends to look up the consumer's outstanding request\ /// without iterating through the provider's full request history.\ /// @return The last submitted request ID, or 0 if none.\ function lastRequestId() public view virtual returns (uint256);\ \ // ----------------------------------------------------------------\ // External Entry-Points (access-controlled)\ // ----------------------------------------------------------------\ \ /// @notice Called by the FlapAIProvider to deliver the LLM's chosen action.\ /// @dev Only callable by the FlapAIProvider contract (enforced by `onlyFlapAIProvider`).\ /// Delegates to the internal virtual `_fulfillReasoning` for override-able logic.\ /// This external/internal split prevents inheritors from inadvertently removing the\ /// access control check while still overriding the business logic.\ /// @param requestId The ID of the fulfilled request.\ /// @param choice The LLM's chosen action (index in 0..numOfChoices-1).\ function fulfillReasoning(uint256 requestId, uint8 choice) external onlyFlapAIProvider {\ _fulfillReasoning(requestId, choice);\ }\ \ /// @notice Called by the FlapAIProvider when a request is refunded.\ /// @dev Only callable by the FlapAIProvider contract (enforced by `onlyFlapAIProvider`).\ /// Delegates to the internal virtual `_onFlapAIRequestRefunded`.\ /// The provider calls this with `{value: feePaid}` so the BNB refund is delivered here\ /// rather than via a bare ETH transfer (which would trigger `receive()`/`fallback()`).\ /// The provider also applies an explicit gas cap to prevent unbounded gas consumption.\ /// @param requestId The ID of the refunded request.\ function onFlapAIRequestRefunded(uint256 requestId) external payable onlyFlapAIProvider {\ _onFlapAIRequestRefunded(requestId);\ }\ \ // ----------------------------------------------------------------\ // Internal Virtual Hooks (to be overridden)\ // ----------------------------------------------------------------\ \ /// @notice Internal hook invoked when the FlapAIProvider delivers the LLM's choice.\ /// @dev Must be overridden by the consuming contract to execute the action corresponding to `choice`.\ /// The consumer contract is responsible for mapping each `choice` index to its intended action,\ /// consistent with the meanings encoded in the original prompt.\ /// @param requestId The ID of the fulfilled request.\ /// @param choice The LLM's chosen action index.\ function _fulfillReasoning(uint256 requestId, uint8 choice) internal virtual;\ \ /// @notice Internal hook invoked when the FlapAIProvider refunds a request.\ /// @dev Must be overridden by the consuming contract to perform cleanup or retry logic\ /// (e.g., reset a pending-action flag, re-enable a paused operation).\ /// `msg.value` equals the original `feePaid` — the BNB refund is delivered with this call.\ /// @param requestId The ID of the refunded request.\ function _onFlapAIRequestRefunded(uint256 requestId) internal virtual;\ } --- # Gift Vault (FlapXVault) | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/vault-developers/gift-vault.md) . [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#overview) Overview ------------------------------------------------------------------------------------------- The Gift Vault (also known as FlapXVault) allows you to assign an X (Twitter) account as the **Gift Owner**, who can direct the trading fees to any EVM address of their choice: 1. **Assign Gift Owner**: The X account you specify becomes the gift owner who controls where fees go. 2. **Flexible Beneficiary**: The gift owner can assign fees to any EVM address and change it anytime. 3. **7-Day Grace Period**: If the gift owner doesn't manage the fees at least once in the first 7 days, control is forfeited. 4. **Fallback: Buyback & Burn**: If control is forfeited, fees will automatically be used for buyback and burn. You can use it as a way to give tax fee to a X user or you can build more interesting use cases on top of it! We build the gift vault to make it easier for a web2 developer or an agent to manage the tax fee through social proof. Here are some interesting ideas: * Create an X account for an agent and launch a tax token that assigns the agent as the gift owner. The agent can then route the tax fee to any EVM address. The Agent can manage his own fund by accepting the funding request through X: The requesters can tweet to the agent's X account and ask the agent to support them by routing the tax fee to their address for some duration. The agent can choose to accept or reject the request by managing the vault through tweeting. This could be a charity funds or a VC that supports new projects. * A social game where players tweet to a game X account, and gets the tax fee routed to their address based on some social tasks or achievements. In the following sections, we introduce how to integrate with the Gift Vault through its API. By following this guide, you will be able to build a complete integration that allows users to manage their vaults using X tweets as social proof. [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#table-of-contents) Table of contents ------------------------------------------------------------------------------------------------------------- 1. [Prerequisites](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#prerequisites) 2. [Network Details](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#network-details) 3. [Architecture overview](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#architecture-overview) 4. [Step-by-step integration](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-by-step-integration) 5. [Complete code example](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#complete-code-example) 6. [API reference](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#api-reference) 7. [Error handling](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#error-handling) [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#prerequisites) Prerequisites ----------------------------------------------------------------------------------------------------- * **Web3 Library**: ethers.js v6, viem, or web3.js * **Vault Factory Contract Address**: See [Network Details](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#network-details) * **Tax Token Address** * **X Handle** (Twitter username of vault owner) * **Tweet** (you must fetch tweet data yourself) [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#network-details) Network Details --------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#bnb-smart-chain-mainnet) BNB Smart Chain Mainnet * **Gift Vault Factory**: `0x025549F52B03cF36f9e1a337c02d3AA7Af66ab32` * **Relayer API Endpoint**: `https://bnb-x-relayer.taxed.fun/submit` ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#bnb-smart-chain-testnet) BNB Smart Chain Testnet * **Gift Vault Factory**: `0xa02DA44D67DB6D692efa7f751b5952bd670d5326` * **Relayer API Endpoint**: `https://bnbtest-x-relayer.taxed.fun/submit` [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#architecture-overview) Architecture overview --------------------------------------------------------------------------------------------------------------------- Flow Diagram: [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-by-step-integration) Step-by-step integration --------------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-1-parse-and-validate-tweet-content) Step 1: Parse and validate tweet content Integrators must fetch the tweet themselves. The tweet format is **strictly enforced**: **Tweet Format:** `gift the fee from [tax_token_address] to [target_address] #FlapGift` **Implementation:** ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-2-mandatory-preflight-check) Step 2: ⚠️ MANDATORY PREFLIGHT CHECK **ALWAYS call this before backend submission!** **Using ethers.js v6:** **Using viem:** **Important Notes:** * **xHandle must be lowercase** when calling the contract * This is a **view function** - no gas required, instant response * Returns `[bool canManage, string errorMessage]` * **ALWAYS check this before backend** to avoid rate limit violations ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-3-submit-to-backend-api) Step 3: Submit to backend API **⚠️ ONLY call if Step 2 passed!** **Rate Limits:** * **1 request per minute per IP** * **Violating may result in IP ban** **Endpoint:** `POST /submit` ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-4-poll-for-on-chain-confirmation) Step 4: Poll for on-chain confirmation The relayer processes transactions asynchronously. Poll `canManageVault` to confirm success. **Why Poll?** * Relayer doesn't return transaction hash * Transaction is queued for later processing * Once processed, `canManageVault` returns `false` with error: "The tweet is outdated" **Implementation:** [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#complete-code-example) Complete code example --------------------------------------------------------------------------------------------------------------------- [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#api-reference) API reference ----------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#tweet-content-validation) Tweet content validation #### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#parsetweettext-text-string) parseTweetText(text: string) **Regex Pattern:** **Valid Example:** **Invalid Examples:** ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#contract-canmanagevault) Contract: canManageVault() **Parameters:** * `taxToken` (address): Tax token contract address * `xHandle` (string): X handle (MUST be lowercase) * `tweetId` (uint128): Tweet ID **Returns:** * `canManage` (bool): Whether vault can be managed * `errorMessage` (string): Error description if false **Possible Error Messages:** Error Message Meaning "FlapXVault not found for this tax token" No vault exists "xHandle does not match" Wrong X handle "Vault is in snowball mode" Vault cannot be managed "The tweet is outdated" Tweet already used (success!) "" (empty string) Can manage (success) ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#backend-api-post-submit) Backend API: POST /submit **Endpoint:** `{relayerEndpoint}/submit` **Rate Limit:** 1 request per minute per IP **Request:** **Success Response (200):** **Error Response (4xx/5xx):** [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#error-handling) Error handling ------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#common-errors) Common errors Error Cause Solution **Tweet Validation** "Tweet is not valid" Deleted/suspended Use valid, active tweet "Tweet has no text" Media-only tweet Ensure text content "Author mismatch" Wrong X handle Verify correct user "Format mismatch" Incorrect format Use exact format "Token mismatch" Wrong address Verify addresses match **Preflight Check** "Vault not found" No vault exists Check token has vault "xHandle does not match" Wrong vault owner Use correct X handle "Vault is in snowball mode" Vault locked Cannot manage anymore "Tweet is outdated" Already used Use new tweet ID **Backend** Rate limit error Too many requests Wait 60 seconds Network timeout Backend slow Retry later [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#manual-submission-fallback) Manual submission fallback ------------------------------------------------------------------------------------------------------------------------------- If the backend relayer times out after 60 seconds, you can submit the proof directly on-chain using the proof data returned from the backend: [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#summary-checklist) Summary checklist ------------------------------------------------------------------------------------------------------------- 1. ✅ Fetch tweet (you must do this yourself) 2. ✅ Validate tweet content (Step 1) * Check tweet exists and is valid * Verify author matches X handle * Parse tweet text using regex * Verify tax token matches 3. ✅ ⚠️ MANDATORY: Call canManageVault() (Step 2) * xHandle MUST be lowercase * If false, STOP - do NOT call backend 4. ✅ Submit to backend (Step 3) * Only if preflight passed * Rate limited: 1 request/minute 5. ✅ Poll canManageVault() (Step 4) * Every 15s for max 60s * Success = error: "The tweet is outdated" 6. ✅ Error handling * Implement timeouts * Have manual submission fallback **Key Points:** * NEVER skip preflight check - prevents IP ban * Backend is rate-limited (1/min) - no exceptions * xHandle must be lowercase for contract calls * Polling confirms success - relayer is async * Consider showing manual submission option to users after 30 seconds of polling, while continuing to poll up to 60 seconds in the background [](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#gift-vault-interface) Gift Vault interface ------------------------------------------------------------------------------------------------------------------- [PreviousFlap Tax Vault](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault) [NextGift Vault V2](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2) Last updated 5 months ago * [Overview](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#overview) * [Table of contents](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#table-of-contents) * [Prerequisites](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#prerequisites) * [Network Details](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#network-details) * [BNB Smart Chain Mainnet](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#bnb-smart-chain-mainnet) * [BNB Smart Chain Testnet](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#bnb-smart-chain-testnet) * [Architecture overview](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#architecture-overview) * [Step-by-step integration](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-by-step-integration) * [Step 1: Parse and validate tweet content](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-1-parse-and-validate-tweet-content) * [Step 2: ⚠️ MANDATORY PREFLIGHT CHECK](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-2-mandatory-preflight-check) * [Step 3: Submit to backend API](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-3-submit-to-backend-api) * [Step 4: Poll for on-chain confirmation](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#step-4-poll-for-on-chain-confirmation) * [Complete code example](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#complete-code-example) * [API reference](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#api-reference) * [Tweet content validation](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#tweet-content-validation) * [Contract: canManageVault()](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#contract-canmanagevault) * [Backend API: POST /submit](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#backend-api-post-submit) * [Error handling](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#error-handling) * [Common errors](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#common-errors) * [Manual submission fallback](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#manual-submission-fallback) * [Summary checklist](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#summary-checklist) * [Gift Vault interface](https://docs.flap.sh/flap/developers/vault-developers/gift-vault#gift-vault-interface) Copy ┌──────────────────┐ │ Start Request │ └────────┬─────────┘ │ ▼ ┌─────────────────────────────────────┐ │ Step 1: Parse & Validate Tweet │ │ - Fetch tweet from Twitter API │ │ - Verify author matches xHandle │ │ - Parse tweet text format │ │ - Verify tax token matches │ └────────┬────────────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ ⚠️ Step 2: MANDATORY PREFLIGHT │ │ Call canManageVault() on-chain │ │ │ │ ⚠️ Backend is rate-limited (1/min) │ │ ⚠️ NEVER skip this step! │ └────────┬────────────────────────────┘ │ ▼ ┌────────┐ │ Pass? │ └───┬─┬──┘ Yes │ │ No → STOP! Do NOT call backend │ ▼ ┌─────────────────────────────────────┐ │ Step 3: Submit to Backend (1/min) │ │ POST /submit │ │ {tax_token, tweet_id} │ └────────┬────────────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ Step 4: Poll canManageVault() │ │ Every 15s for 60s max │ │ Success = errorMessage:"outdated" │ └─────────────────────────────────────┘ Copy // Type definitions interface ParsedTweet { targetAddress: string; taxTokenAddress: string; } interface Tweet { __typename: string; text?: string; user?: { screen_name?: string; legacy?: { screen_name?: string }; }; } // Parse tweet text using regex function parseTweetText(text: string): ParsedTweet | null { // Pattern: "gift the fee from to #FlapGift" const regex = /gift\s+the\s+fee\s+from\s+(0x[a-fA-F0-9]{40})\s+to\s+(0x[a-fA-F0-9]{40})\s+#FlapGift/i; const match = text.match(regex); if (!match) { return null; } return { taxTokenAddress: match[1], // First captured group targetAddress: match[2] // Second captured group }; } // Validate tweet content function validateTweetContent( tweet: Tweet, expectedXHandle: string, expectedTaxToken: string ): ParsedTweet { // Check tweet exists and is valid if (!tweet || tweet.__typename !== "Tweet") { throw new Error("Tweet is not valid (deleted, suspended, or not found)"); } // Check tweet has text if (!tweet.text) { throw new Error("Tweet has no text content"); } // Check tweet has user data if (!tweet.user) { throw new Error("Tweet has no user data"); } // Verify tweet author matches expected xHandle (case-insensitive) const tweetScreenName = tweet.user.screen_name || tweet.user.legacy?.screen_name; if (!tweetScreenName) { throw new Error("Tweet author screen_name not found"); } if (tweetScreenName.toLowerCase() !== expectedXHandle.toLowerCase()) { throw new Error( `Tweet author mismatch. Expected: @${expectedXHandle}, Found: @${tweetScreenName}` ); } // Parse tweet text format const parsed = parseTweetText(tweet.text); if (!parsed) { throw new Error( "Tweet text does not match the required format. " + "Expected: 'gift the fee from [tax token address] to [EVM address] #FlapGift'" ); } // Verify tax token matches if (parsed.taxTokenAddress.toLowerCase() !== expectedTaxToken.toLowerCase()) { throw new Error( `Tax token mismatch. Expected: ${expectedTaxToken}, Found: ${parsed.taxTokenAddress}` ); } return parsed; } Copy import { Contract, JsonRpcProvider } from 'ethers'; // Minimal ABI for canManageVault const VAULT_FACTORY_ABI = [\ {\ "inputs": [\ {"internalType": "address", "name": "taxToken", "type": "address"},\ {"internalType": "string", "name": "xHandle", "type": "string"},\ {"internalType": "uint128", "name": "tweetId", "type": "uint128"}\ ],\ "name": "canManageVault",\ "outputs": [\ {"internalType": "bool", "name": "canManage", "type": "bool"},\ {"internalType": "string", "name": "errorMessage", "type": "string"}\ ],\ "stateMutability": "view",\ "type": "function"\ }\ ]; async function preflightCheck( provider: JsonRpcProvider, factoryAddress: string, taxToken: string, xHandle: string, tweetId: string ): Promise { console.log("⚠️ PREFLIGHT CHECK (Mandatory before backend call)"); console.log(" Prevents rate limit violations (1 req/min)"); const contract = new Contract(factoryAddress, VAULT_FACTORY_ABI, provider); // xHandle MUST be lowercase for contract compatibility const result = await contract.canManageVault( taxToken, xHandle.toLowerCase(), BigInt(tweetId) ); const canManage: boolean = result[0]; const errorMessage: string = result[1]; if (!canManage) { throw new Error( `❌ PREFLIGHT CHECK FAILED: ${errorMessage}\n` + ` DO NOT call backend API - request will be rejected!` ); } console.log("✅ Preflight check PASSED - safe to proceed"); } Copy import { createPublicClient, http } from 'viem'; const VAULT_FACTORY_ABI = [\ {\ inputs: [\ { name: 'taxToken', type: 'address' },\ { name: 'xHandle', type: 'string' },\ { name: 'tweetId', type: 'uint128' }\ ],\ name: 'canManageVault',\ outputs: [\ { name: 'canManage', type: 'bool' },\ { name: 'errorMessage', type: 'string' }\ ],\ stateMutability: 'view',\ type: 'function'\ }\ ] as const; async function preflightCheckViem( rpcUrl: string, factoryAddress: `0x${string}`, taxToken: `0x${string}`, xHandle: string, tweetId: bigint ): Promise { const client = createPublicClient({ transport: http(rpcUrl) }); const result = await client.readContract({ address: factoryAddress, abi: VAULT_FACTORY_ABI, functionName: 'canManageVault', args: [\ taxToken,\ xHandle.toLowerCase(), // MUST be lowercase\ tweetId\ ] }); const [canManage, errorMessage] = result; if (!canManage) { throw new Error(`Preflight failed: ${errorMessage}`); } } Copy interface SubmitRequest { tax_token: string; // Ethereum address (0x...) tweet_id: string; // Twitter/X tweet ID as string } interface SubmitResponse { message: string; x_proof: { target_address: string; tax_token: string; x_handle: string; x_id: string; tweet_id: string; }; signature: string; // Oracle signature (hex) } async function submitToBackend( relayerEndpoint: string, taxToken: string, tweetId: string ): Promise { const url = `${relayerEndpoint}submit`; console.log("📡 Submitting to backend (rate-limited: 1/min)"); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ tax_token: taxToken, tweet_id: tweetId, }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(`Backend error: ${errorData.error}`); } const data: SubmitResponse = await response.json(); console.log("✅ Backend submission successful"); return data; } Copy async function pollForConfirmation( provider: JsonRpcProvider, factoryAddress: string, taxToken: string, xHandle: string, tweetId: string ): Promise { const startTime = Date.now(); const TIMEOUT = 60000; // 60 seconds const POLL_INTERVAL = 15000; // 15 seconds const contract = new Contract(factoryAddress, VAULT_FACTORY_ABI, provider); return new Promise((resolve, reject) => { const interval = setInterval(async () => { const elapsed = Date.now() - startTime; // Timeout after 60 seconds if (elapsed >= TIMEOUT) { clearInterval(interval); reject(new Error("Polling timeout (60s) - transaction may still be processing")); return; } try { const result = await contract.canManageVault( taxToken, xHandle.toLowerCase(), BigInt(tweetId) ); const canManage: boolean = result[0]; const errorMessage: string = result[1]; // Success: tweet marked as "outdated" means proof was recorded if (!canManage && errorMessage === "The tweet is outdated") { clearInterval(interval); console.log("✅ Transaction confirmed on-chain!"); resolve(true); return; } console.log(` Polling... (${Math.floor(elapsed / 1000)}s elapsed)`); } catch (error) { console.error("Polling error:", error); } }, POLL_INTERVAL); }); } Copy import { Contract, JsonRpcProvider } from 'ethers'; // Configuration const FACTORY_ADDRESS = "0xa02DA44D67DB6D692efa7f751b5952bd670d5326"; const RELAYER_ENDPOINT = "https://xrelayer.taxed.fun/"; const RPC_URL = "https://your-rpc-endpoint"; const VAULT_FACTORY_ABI = [\ {\ "inputs": [\ {"name": "taxToken", "type": "address"},\ {"name": "xHandle", "type": "string"},\ {"name": "tweetId", "type": "uint128"}\ ],\ "name": "canManageVault",\ "outputs": [\ {"name": "canManage", "type": "bool"},\ {"name": "errorMessage", "type": "string"}\ ],\ "stateMutability": "view",\ "type": "function"\ }\ ]; // Type definitions interface ParsedTweet { targetAddress: string; taxTokenAddress: string; } interface Tweet { __typename: string; text?: string; user?: { screen_name?: string; legacy?: { screen_name?: string }; }; } interface SubmitResponse { message: string; x_proof: { target_address: string; tax_token: string; x_handle: string; x_id: string; tweet_id: string; }; signature: string; } // Parse tweet text using regex function parseTweetText(text: string): ParsedTweet | null { const regex = /gift\s+the\s+fee\s+from\s+(0x[a-fA-F0-9]{40})\s+to\s+(0x[a-fA-F0-9]{40})\s+#FlapGift/i; const match = text.match(regex); if (!match) { return null; } return { taxTokenAddress: match[1], targetAddress: match[2] }; } // Validate tweet content function validateTweetContent( tweet: Tweet, expectedXHandle: string, expectedTaxToken: string ): ParsedTweet { if (!tweet || tweet.__typename !== "Tweet") { throw new Error("Tweet is not valid (deleted, suspended, or not found)"); } if (!tweet.text) { throw new Error("Tweet has no text content"); } if (!tweet.user) { throw new Error("Tweet has no user data"); } const tweetScreenName = tweet.user.screen_name || tweet.user.legacy?.screen_name; if (!tweetScreenName) { throw new Error("Tweet author screen_name not found"); } if (tweetScreenName.toLowerCase() !== expectedXHandle.toLowerCase()) { throw new Error( `Tweet author mismatch. Expected: @${expectedXHandle}, Found: @${tweetScreenName}` ); } const parsed = parseTweetText(tweet.text); if (!parsed) { throw new Error( "Tweet text does not match the required format. " + "Expected: 'gift the fee from [tax token address] to [EVM address] #FlapGift'" ); } if (parsed.taxTokenAddress.toLowerCase() !== expectedTaxToken.toLowerCase()) { throw new Error( `Tax token mismatch. Expected: ${expectedTaxToken}, Found: ${parsed.taxTokenAddress}` ); } return parsed; } // Preflight check async function preflightCheck( provider: JsonRpcProvider, factoryAddress: string, taxToken: string, xHandle: string, tweetId: string ): Promise { console.log("⚠️ PREFLIGHT CHECK (Mandatory before backend call)"); const contract = new Contract(factoryAddress, VAULT_FACTORY_ABI, provider); const result = await contract.canManageVault( taxToken, xHandle.toLowerCase(), BigInt(tweetId) ); const canManage: boolean = result[0]; const errorMessage: string = result[1]; if (!canManage) { throw new Error( `❌ PREFLIGHT CHECK FAILED: ${errorMessage}\n` + ` DO NOT call backend API - request will be rejected!` ); } console.log("✅ Preflight check PASSED"); } // Submit to backend async function submitToBackend( relayerEndpoint: string, taxToken: string, tweetId: string ): Promise { const url = `${relayerEndpoint}submit`; console.log("📡 Submitting to backend (rate-limited: 1/min)"); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ tax_token: taxToken, tweet_id: tweetId }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(`Backend error: ${errorData.error}`); } const data: SubmitResponse = await response.json(); console.log("✅ Backend submission successful"); return data; } // Poll for confirmation async function pollForConfirmation( provider: JsonRpcProvider, factoryAddress: string, taxToken: string, xHandle: string, tweetId: string ): Promise { const startTime = Date.now(); const TIMEOUT = 60000; const POLL_INTERVAL = 15000; const contract = new Contract(factoryAddress, VAULT_FACTORY_ABI, provider); return new Promise((resolve, reject) => { const interval = setInterval(async () => { const elapsed = Date.now() - startTime; if (elapsed >= TIMEOUT) { clearInterval(interval); reject(new Error("Polling timeout (60s)")); return; } try { const result = await contract.canManageVault( taxToken, xHandle.toLowerCase(), BigInt(tweetId) ); const canManage: boolean = result[0]; const errorMessage: string = result[1]; if (!canManage && errorMessage === "The tweet is outdated") { clearInterval(interval); console.log("✅ Transaction confirmed on-chain!"); resolve(true); return; } console.log(` Polling... (${Math.floor(elapsed / 1000)}s elapsed)`); } catch (error) { console.error("Polling error:", error); } }, POLL_INTERVAL); }); } // Main integration function async function submitVaultProof( tweet: Tweet, taxToken: string, xHandle: string, tweetId: string ): Promise { const provider = new JsonRpcProvider(RPC_URL); try { console.log("=== Gift Vault Proof Submission ===\n"); // STEP 1: Validate Tweet Content console.log("Step 1: Validating tweet content..."); const parsed = validateTweetContent(tweet, xHandle, taxToken); console.log("✅ Tweet validation passed"); console.log(` Author: @${tweet.user?.screen_name}`); console.log(` Tax Token: ${parsed.taxTokenAddress}`); console.log(` Target: ${parsed.targetAddress}\n`); // STEP 2: MANDATORY PREFLIGHT CHECK console.log("Step 2: ⚠️ PREFLIGHT CHECK"); await preflightCheck(provider, FACTORY_ADDRESS, taxToken, xHandle, tweetId); console.log("✅ Preflight passed - safe to call backend\n"); // STEP 3: Submit to Backend console.log("Step 3: Submitting to backend..."); const proofData = await submitToBackend(RELAYER_ENDPOINT, taxToken, tweetId); console.log("✅ Backend accepted submission\n"); // STEP 4: Poll for Confirmation console.log("Step 4: Waiting for on-chain confirmation..."); await pollForConfirmation(provider, FACTORY_ADDRESS, taxToken, xHandle, tweetId); console.log("\n✅ SUCCESS: Vault proof recorded on-chain!"); console.log("=== Submission Complete ==="); return proofData; } catch (error) { console.error("\n❌ FAILED:", error); throw error; } } // Usage Example async function main() { const tweet = { __typename: "Tweet", text: "gift the fee from 0x1234567890abcdef1234567890abcdef12345678 to 0xabcdefabcdefabcdefabcdefabcdefabcdefabcd #FlapGift", user: { screen_name: "username" } }; await submitVaultProof( tweet, "0x1234567890abcdef1234567890abcdef12345678", "username", "1234567890123456789" ); } Copy /gift\s+the\s+fee\s+from\s+(0x[a-fA-F0-9]{40})\s+to\s+(0x[a-fA-F0-9]{40})\s+#FlapGift/i Copy gift the fee from 0x1234567890abcdef1234567890abcdef12345678 to 0xabcdefabcdefabcdefabcdefabcdefabcdefabcd #FlapGift Copy send fees from 0x... to 0x... // Wrong keyword gift fees to 0x... from 0x... // Wrong order gift the fee from 0x123 to 0xabc // Invalid address format Copy { "tax_token": "0x1234567890abcdef...", "tweet_id": "1234567890123456789" } Copy { "message": "Proof submitted successfully", "x_proof": { "target_address": "0xabcdef...", "tax_token": "0x123456...", "x_handle": "username", "x_id": "123456789", "tweet_id": "1234567890123456789" }, "signature": "0x1234abcd..." } Copy { "error": "Error description" } Copy async function manualSubmit( provider: JsonRpcProvider, factoryAddress: string, taxToken: string, proofData: SubmitResponse, signer: any // Wallet signer ): Promise { const MANUAL_SUBMIT_ABI = [\ {\ "inputs": [\ {"name": "taxToken", "type": "address"},\ {\ "name": "proof",\ "type": "tuple",\ "components": [\ {"name": "targetAddress", "type": "address"},\ {"name": "taxToken", "type": "address"},\ {"name": "xHandle", "type": "string"},\ {"name": "XId", "type": "uint256"},\ {"name": "tweetId", "type": "uint128"}\ ]\ },\ {"name": "signature", "type": "bytes"}\ ],\ "name": "manageByProof",\ "outputs": [],\ "stateMutability": "nonpayable",\ "type": "function"\ }\ ]; const contract = new Contract(factoryAddress, MANUAL_SUBMIT_ABI, signer); const tx = await contract.manageByProof( taxToken, { targetAddress: proofData.x_proof.target_address, taxToken: proofData.x_proof.tax_token, xHandle: proofData.x_proof.x_handle, XId: BigInt(proofData.x_proof.x_id), tweetId: BigInt(proofData.x_proof.tweet_id) }, proofData.signature ); console.log("Manual submission tx:", tx.hash); await tx.wait(); console.log("✅ Manual submission confirmed"); } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @title IFlapXVault /// @notice Interface for the Gift Vault (FlapXVault) implementation. /// @dev Derived from the on-chain FlapXVault contract. interface IFlapXVault { /// @notice The vault states. enum State { ACCUMULATING, STREAMING, FALLBACK_SNOWBALL } /// @notice The type of balance update for snowball events. enum BalanceUpdateType { ACCUMULATION, SNOWBALL } /// @notice Aggregate vault statistics. /// @param totalBNBSpent Total BNB spent on buyback. /// @param totalTokenBurn Total tax tokens burned. struct VaultStats { uint128 totalBNBSpent; uint128 totalTokenBurn; } /// @notice Historical proof record. /// @param XId The X (Twitter) account id. /// @param tweetId The tweet id used as proof. /// @param targetAddress The streaming target address. struct ProofRecord { uint128 XId; uint128 tweetId; address targetAddress; } /// @notice XProof structure for EIP-712 signature verification. /// @param targetAddress The desired streaming target. /// @param taxToken The tax token tied to this vault. /// @param xHandle The fee manager's X handle. /// @param XId The X (Twitter) account id. /// @param tweetId The tweet id used as proof. struct XProof { address targetAddress; address taxToken; string xHandle; uint128 XId; uint128 tweetId; } /// @notice Emitted when the vault state changes. /// @param token The tax token address. /// @param newState The new state (0: ACCUMULATING, 1: STREAMING, 2: FALLBACK_SNOWBALL). event FlapTaxVaultStateChanged(address token, uint8 newState); /// @notice Emitted when the streaming target is updated. /// @param token The tax token address. /// @param newTarget The new streaming target address. event FlapTaxVaultStreamingTargetUpdated(address token, address newTarget); /// @notice Emitted when snowball balance is updated. /// @param token The tax token address. /// @param vault The vault address. /// @param newBalance The new BNB balance. /// @param updateType The type of update (ACCUMULATION or SNOWBALL). event FlapSnowballBalanceUpdated(address token, address vault, uint256 newBalance, BalanceUpdateType updateType); /// @notice Emitted when forwarding to streaming target fails. /// @param token The tax token address. /// @param target The target address that rejected the transfer. /// @param amount The amount that failed to transfer. event FlapStreamingForwardFailed(address token, address target, uint256 amount); /// @notice Error when trying to use an outdated proof. /// @param providedTweetId The tweet id provided in the proof. /// @param lastTweetId The latest stored tweet id. error OutdatedProof(uint128 providedTweetId, uint128 lastTweetId); /// @notice Error when the vault is in the wrong state for the operation. /// @param currentState The current vault state. error InvalidState(State currentState); /// @notice Error when xHandle is empty. error EmptyXHandle(); /// @notice Error when proof verification fails. error InvalidProof(); /// @notice Error when proof tax token does not match this vault. error MismatchedTaxToken(); /// @notice Error when proof xHandle does not match this vault. error MismatchedXHandle(); /// @notice Error when attempting to revoke the guardian role. error CannotRevokeGuardianRole(); /// @notice Role allowed to execute snowball buybacks. function SNOWBALL_ROLE() external view returns (bytes32); /// @notice Dead address used for burns. function DEAD_ADDRESS() external view returns (address); /// @notice Initialize the vault after cloning. /// @param _taxToken The tax token address. /// @param _quoteToken The quote token address. /// @param _xHandle The fee manager's X handle. /// @param _timeoutPeriod Time before fallback state (seconds). function initialize(address _taxToken, address _quoteToken, string calldata _xHandle, uint256 _timeoutPeriod) external; /// @notice Current computed vault state. /// @return The current state (computed from storage and time). function state() external view returns (State); /// @notice Transition state if timeout rules are met. function transitState() external; /// @notice Manage the vault using a signed X proof. /// @param proof The proof payload. /// @param signature The EIP-712 signature. function manageByProof(XProof calldata proof, bytes calldata signature) external; /// @notice Buy back and burn tax tokens using accumulated BNB. /// @param quoteAmt The amount of BNB to use for buyback. function snowball(uint256 quoteAmt) external; /// @notice Return historical proofs with pagination. /// @param offset Starting index for pagination (0 = most recent). /// @param limit Maximum number of records to return. /// @return records Array of proof records in descending order (newest first). /// @return total Total number of historical proofs. function getHistoricalProofs(uint256 offset, uint256 limit) external view returns (ProofRecord[] memory records, uint256 total); /// @notice Returns a human-readable description of the vault. /// @return A string describing the vault state and key metrics. function description() external view returns (string memory); /// @notice Override of AccessControl's revokeRole to protect guardian. /// @param role The role to revoke. /// @param account The account to revoke the role from. function revokeRole(bytes32 role, address account) external; /// @notice Current aggregate stats. function stats() external view returns (uint128 totalBNBSpent, uint128 totalTokenBurn); /// @notice The tax token associated with this vault. function taxToken() external view returns (address); /// @notice The quote token used for snowball swaps. function quoteToken() external view returns (address); /// @notice The fee manager's X handle. function xHandle() external view returns (string memory); /// @notice Timeout period before fallback. function timeoutPeriod() external view returns (uint256); /// @notice Creation timestamp for this vault. function createdAt() external view returns (uint256); /// @notice Vault factory address. function factory() external view returns (address); /// @notice Current streaming target (only in STREAMING state). function streamingTarget() external view returns (address); /// @notice Total streamed amount for a beneficiary. /// @param beneficiary The beneficiary address. function streamedAmount(address beneficiary) external view returns (uint256); /// @notice Latest recorded tweet id used for monotonicity checks. function lastTweetId() external view returns (uint128); } --- # Vault & VaultFactory Specification | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification.md) . [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#table-of-contents) Table of Contents --------------------------------------------------------------------------------------------------------------------------------------- * [Quick start](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#quick-start) * [Overview](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#overview) * [The Vault Specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#the-vault-specification) * [VaultBase (V1)](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultbase-v1) * [VaultBaseV2](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultbasev2) * [Example vault — BlackHoleVault](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#example-vault--blackholevault) * [The Flap Guardian](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#the-flap-guardian) * [Adapter for legacy vaults](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#adapter-for-legacy-vaults) * [VaultFactory Specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultfactory-specification) * [IVaultFactory (V1)](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#ivaultfactory-v1) * [VaultFactoryBaseV2](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultfactorybasev2) * [V2.1: Validation hook — `onBeforeNewTokenV6WithVault`](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v21-validation-hook--onbeforenewtokenv6withvault) * [V2.2: Generic validation hook — `onBeforeLaunch(bytes)`](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v22-generic-validation-hook--onbeforelaunchbytes) * [V2.2: Validation payload — `LaunchValidationDataV1`](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v22-validation-payload--launchvalidationdatav1) * [V2.2: Spec version discovery — `factorySpecVersion()`](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v22-spec-version-discovery--factoryspecversion) * [V2.1+: Policy discovery — `tokenCreationPolicies`](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v21-policy-discovery--tokencreationpolicies) * [Recommended commission fee structure](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#recommended-commission-fee-structure) * [UI Schema Reference (IVaultSchemasV1)](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#ui-schema-reference-ivaultschemasv1) * [FieldDescriptor](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#fielddescriptor) * [VaultDataSchema](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultdataschema) * [FactoryPolicy](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#factorypolicy) * [VaultUISchema & supporting types](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultuischema--supporting-types) * [Get your vault verified](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#get-your-vault-verified) * [FAQ](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#faq) [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#quick-start) Quick start --------------------------------------------------------------------------------------------------------------------------- 1. **Follow the example** — Work through the [FlapVaultExample repository](https://github.com/flap-sh/FlapVaultExample) to see a complete, working vault and vault factory implementation. It is the fastest way to understand the patterns you need to follow. 2. **Verify your implementation** — Once you have written your vault, use the [FlapVaultSpecChecker](https://github.com/flap-sh/FlapVaultSpecChecker) AI toolkit to automatically check that your implementation correctly follows this specification before deploying. [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#overview) Overview --------------------------------------------------------------------------------------------------------------------- Flap's vault system allows anyone to build smart contracts for managing and distributing BNB revenue generated from tax tokens. Vaults can be customized to fit various use cases — splitting revenue among multiple recipients, routing funds based on social proof, funding a game treasury, or anything else you can imagine. **Permissionless vault creation** — You can now create and deploy your own vaults and vault factories **without any permission or registration**. Vault factories no longer need to be registered in the VaultPortal before they can be used to launch tokens. Users can use any vault they want when launching tokens. For a complete working example of a vault and vault factory implementation, check out the [FlapVaultExample repository](https://github.com/flap-sh/FlapVaultExample) . There are two generations of the vault specification, with a point release of the factory spec: Version Vault base contract Factory base contract Key addition V1 `VaultBase` `IVaultFactory` Core spec — `description()`, Guardian mandate V2 `VaultBaseV2` (extends `VaultBase`) `VaultFactoryBaseV2` (implements `IVaultFactory`) On-chain UI schema for automatic UI generation V2.1 _(no change)_ `VaultFactoryBaseV2` (updated) Legacy V6 validation hook + machine-readable policy discovery V2.2 _(no change)_ `VaultFactoryBaseV2` (current) Wrapper-agnostic validation via `onBeforeLaunch(bytes)` and normalized launch payloads V2, V2.1, and V2.2 are **fully backwards-compatible** with V1. Existing vaults that extend `VaultBase` are unaffected. New vault implementations should extend `VaultBaseV2` and new factory implementations should extend `VaultFactoryBaseV2` to gain UI schema support. New factory implementations should generally target **V2.2**, which is the current default behavior of `VaultFactoryBaseV2` in the FlapTaxVaults repo. [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#the-vault-specification) The Vault Specification --------------------------------------------------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultbase-v1) VaultBase (V1) To be compatible with the VaultPortal system, your vault smart contract must inherit the `VaultBase` contract: **Implementation requirements:** 1. **Implement** `**description()**` — Return a dynamic string that describes the vault's current state. The description should change based on the vault's state (e.g. balance, streaming status, etc.). 2. **Implement** `**receive()**` — Accept BNB from the tax token and process the revenue according to your vault's logic. 3. **Guardian mandate** — If you have any permissioned functions that should be triggered by an external address, and it is not suitable to make them public (e.g. buyback which may be sandwich attacked), you **must** also give the Guardian address the permissions alongside other allowed addresses as a backup. See the [Flap Guardian](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#the-flap-guardian) section for details. ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultbasev2) VaultBaseV2 `VaultBaseV2` inherits from `VaultBase` and adds a single new abstract method: `vaultUISchema()`. This allows the UI to automatically discover and render the vault's user-facing methods — without needing custom code for each vault type. **What changes from V1:** * All V1 obligations still apply (`description()`, `receive()`, Guardian mandate). * You must additionally override `vaultUISchema()` to return a `VaultUISchema` describing every user-facing method your vault exposes. See the [UI Schema Reference](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#ui-schema-reference-ivaultschemasv1) section for the full struct definitions. **Why this matters:** Without a self-describing mechanism, every new vault type would require a custom UI to be built and deployed before users could interact with it. `vaultUISchema()` solves this problem by enabling **automatic UI generation for any future vault type**. Existing vault types (FlapXVault, SplitVault, SnowBallVault, BlackHoleVault) already have purpose-built UIs. `VaultBaseV2` is designed for **future** vault implementations — the UI can automatically generate a full interaction page for any vault that implements this interface. **Example — Hypothetical DonationVault:** **Example — StakingVault with approve actions:** **Example — NoteVault (string input, paginated array output):** ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#the-flap-guardian) The Flap Guardian The Flap Guardian is a privileged address that can always call permissioned functions in vault contracts that implement the Vault Specification. The Guardian serves as a backup mechanism to ensure that critical functions can be executed even if the primary authorized addresses are unable to do so. At this moment, the Guardian is an empty but upgradeable contract managed by Flap: Ideally, we don't need to implement any functionality in the Guardian contract. It just serves as a trusted address that can step in when necessary to protect users' funds. If your vault has permissioned functions, you **must** grant the Guardian the same permissions. The Guardian's access **must not** be revocable by any other account — only the Guardian itself may renounce its own access. When using OpenZeppelin's `AccessControl`, override `revokeRole()`: ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#adapter-for-legacy-vaults) Adapter for legacy vaults If you have built a vault to receive tax token revenue before the Vault Specification was introduced, you can still make your vault compatible with the VaultPortal system by creating an adapter contract that inherits from `VaultBase` and wraps around your existing vault. The adapter will implement the `description()` method and forward calls to your legacy vault as needed. This way, you can leverage VaultPortal features without modifying your original vault contract. Please reach out to our team for assistance in creating an adapter for your legacy vault. [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultfactory-specification) VaultFactory Specification --------------------------------------------------------------------------------------------------------------------------------------------------------- A vault factory deploys vault instances for new tax tokens. When a user launches a token through the `VaultPortal`, the portal calls your factory's `newVault()` method to create the vault. **No registration required** — In V2, any contract that implements `VaultFactoryBaseV2` can be passed to the VaultPortal to launch tokens. You do not need to register your factory on-chain. ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#ivaultfactory-v1) IVaultFactory (V1) The base interface that all vault factories must implement: **Key points:** * Currently, only BNB (`quoteToken == address(0)`) is supported as the quote token, but future support for other quote tokens may be added. * The `newVault` method is called by the VaultPortal when a new tax token is being created. The tax token does **not** exist yet when this method is called — the VaultPortal predicts the token address and passes it. The actual token is created **after** the vault. ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultfactorybasev2) VaultFactoryBaseV2 `VaultFactoryBaseV2` implements `IVaultFactory` and adds a new abstract method: `vaultDataSchema()`. This allows the UI to discover what `vaultData` encoding the factory expects, and render the launch form accordingly. **What changes from V1:** * All V1 obligations still apply (`newVault()`, `isQuoteTokenSupported()`). * You must additionally override `vaultDataSchema()` to return a `VaultDataSchema` describing the fields your factory expects in the `vaultData` parameter. * The factory now provides `_getVaultPortal()` and `_getGuardian()` helpers. **Example — Single tuple schema:** **Example — Array of tuples schema:** **Example — Factory that ignores vaultData:** ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v2.1-validation-hook-onbeforenewtokenv6withvault) V2.1: Validation hook — `onBeforeNewTokenV6WithVault` `VaultFactoryBaseV2` v2.1 adds a **legacy** validation hook that `VaultPortal` may call immediately before creating a V6 tax token. Factories that want to enforce constraints on the legacy `NewTokenV6WithVaultParams` wrapper can override this hook. **Important current behavior:** in the current repo implementation, the base contract does **not** silently accept by default. It reverts with `LegacyV6ValidationHookNotImplemented()` so that new factories do not accidentally opt into the old V6-only validation surface. **When this path is used:** `VaultPortal` only uses this hook for factories that are detected as **legacy V6 factories**. New V2.2 factories should use `onBeforeLaunch(bytes)` instead. **Return value contract** — when the hook is implemented, `VaultPortal` calls it via a low-level call and interprets the result as follows: Outcome Meaning VaultPortal action Returns `(true, "")` Params are accepted Continue with token creation Returns `(false, reason)` Params rejected Revert with `reason` as the error message Reverts with error data Unexpected error in hook Propagate the revert Selector missing / empty returndata Factory does not expose the legacy hook Treat as “no legacy hook present” **Example — Enforce** `**dividendToken**` **equals** `**quoteToken**`**:** The hook receives the full `NewTokenV6WithVaultParams` struct so it can inspect any field — tax rates, quote token, dividend token, salt, etc. Override it only when your factory is intentionally staying on the legacy V6 validation path. ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v2.2-generic-validation-hook-onbeforelaunch-bytes) V2.2: Generic validation hook — `onBeforeLaunch(bytes)` V2.2 introduces a **wrapper-agnostic** validation hook. Instead of coupling factory validation to a specific launch entrypoint such as `newTokenV6WithVault(...)`, `VaultPortal` now normalizes the launch parameters into a shared payload and calls the generic hook below: The default implementation is provided by `VaultFactoryBaseV2` itself. In most cases, your factory should **override** `**_validateBeforeLaunch(...)**`, not `onBeforeLaunch(...)` directly: This makes factory validation independent from whether the user launched via V6, V7, or some future wrapper. `VaultPortal` is responsible for translating wrapper-specific params into the shared validation payload. **Return value contract** — `VaultPortal` calls `onBeforeLaunch(bytes)` via low-level `staticcall` and interprets the result as follows: Outcome Meaning VaultPortal action Returns `(true, "")` Params are accepted Continue with token creation Returns `(false, reason)` Params rejected Revert with `reason` Reverts with error data Unexpected validation error Bubble up the revert Selector missing / empty returndata Factory claimed V2.2 but does not expose the hook correctly Revert with `"Factory validation hook missing"` `onBeforeLaunch(bytes)` is **read-only**. It is called via `staticcall`, so it should only inspect inputs and return pass/fail + reason. Any stateful setup still belongs in `newVault()`. **When to use V2.2 vs V2.1:** * **New factories should use V2.2** by overriding `_validateBeforeLaunch(...)` and keeping `factorySpecVersion()` at the default `"v2.2"`. * **Legacy factories that still depend on** `**NewTokenV6WithVaultParams**` may explicitly stay on V2.1 by overriding `factorySpecVersion()` to return `"v2.1"` and implementing `onBeforeNewTokenV6WithVault(...)`. **Concrete example —** `**GiftV4VaultFactoryV2**`**:** The current repo's `src/GiftV4VaultFactoryV2.sol` uses `_validateBeforeLaunch(...)` to enforce these product rules: * `tokenVersion == TOKEN_V3_PERMIT` * `quoteToken == address(0)` (native BNB only) * `dividendBps == 0` * `dividendToken == address(0)` * `buyTaxRate == 0` * `sellTaxRate == 0` ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v2.2-validation-payload-launchvalidationdatav1) V2.2: Validation payload — `LaunchValidationDataV1` The generic V2.2 hook receives a normalized payload defined in `src/interfaces/IVaultFactory.sol`: **Why this exists:** `newTokenV6WithVault(...)` and `newTokenV7WithVault(...)` do not expose fee data in the same shape. V6 passes separate top-level fields like `mktBps`, while V7 passes fee settings as `feeConfigs[]`. Rather than forcing every factory to understand multiple launch wrapper ABIs, `VaultPortal` converts both launch styles into the same semantic payload before validation. **How** `**VaultPortal**` **builds the payload today:** * For **V6 launches**, it maps: * `mktBps -> vaultBps` * `deflationBps -> deflationBps` * `dividendBps -> dividendBps` * `lpBps -> lpBps` * `dividendToken -> dividendToken` * `minimumShareBalance -> minimumShareBalance` * For **V7 launches**, it scans `feeConfigs[]` and extracts the semantic equivalents for: * vault / marketing fee * deflation fee * dividend fee + dividend token + minimum share balance * LP fee So from the factory's perspective, `_validateBeforeLaunch(...)` always sees the same shape regardless of which launch wrapper the user chose. ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v2.2-spec-version-discovery-factoryspecversion) V2.2: Spec version discovery — `factorySpecVersion()` `VaultFactoryBaseV2` now exposes a version string that tells `VaultPortal` which validation generation the factory belongs to: **Important differences from older V2.1-era docs:** * In the current repo code, this method is **virtual**. * The default return value is `**"v2.2"**`, not `"v2.1"`. * Factories may override it to stay on an older validation generation, for example returning `"v2.1"` for a legacy V6-only factory. **How** `**VaultPortal**` **interprets it:** * `v2.2`, `v2.3`, `v3.0`, etc. → use the generic `onBeforeLaunch(bytes)` path * `v2.1` and below → treat as legacy / probe `onBeforeNewTokenV6WithVault(...)` * missing / reverting selector → probe for the legacy V6 hook directly The parser uses **major/minor semantics**, so anything `v2.2+` counts as part of the normalized pre-launch validation generation. Do not return `"v2.2"` unless your factory actually supports the generic validation path. If `VaultPortal` detects `v2.2+`, it will call `onBeforeLaunch(bytes)` and expect a valid `(bool,string)` response. ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v2.1-policy-discovery-tokencreationpolicies) V2.1+: Policy discovery — `tokenCreationPolicies` Policy discovery was introduced in V2.1 and remains part of V2.2. In the current repo, policies are best understood as **machine-readable UI hints** that mirror whichever validation hook the factory actually uses. #### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#tokencreationpolicies) `tokenCreationPolicies()` Returns an array of `FactoryPolicy` structs describing the constraints this factory enforces. The default implementation returns an empty array (no declared policies). `**FactoryPolicy**` **struct:** **Supported operators:** Operator Meaning `"eq"` Field must equal `value` `"neq"` Field must not equal `value` `"gt"` Field must be strictly greater than `value` `"gte"` Field must be ≥ `value` `"lt"` Field must be strictly less than `value` `"lte"` Field must be ≤ `value` `"in"` Field must be one of a set (ABI-encoded array) `"notIn"` Field must not be any of a set Unknown operators must be **ignored** by the UI (forward-compatible). **How the UI uses policies:** 1. Call `factory.tokenCreationPolicies()` to get the policy array. 2. For each policy, decode `value` using the known ABI type for `target` from the normalized launch payload semantics. 3. Apply `operator` as a client-side validation rule on the corresponding input field and display `description` as an inline hint or error message. 4. Policies do **not** disable fields or block submission — they are advisory hints only. The actual enforcement happens inside the factory validation hook: * `onBeforeLaunch(bytes)` for V2.2+ factories * `onBeforeNewTokenV6WithVault(...)` for legacy V2.1 factories Policies are **informational only**. Always implement the actual enforcement in the corresponding validation hook. Policies without a corresponding hook have no effect on-chain. **Concrete example —** `**GiftV4VaultFactoryV2**`**:** The current `src/GiftV4VaultFactoryV2.sol` returns six policies matching its V2.2 validation logic: 1. `tokenVersion == TOKEN_V3_PERMIT` 2. `quoteToken == address(0)` 3. `dividendBps == 0` 4. `dividendToken == address(0)` 5. `buyTaxRate == 0` 6. `sellTaxRate == 0` This is a good pattern to follow: keep `tokenCreationPolicies()` and your real validation hook in sync so both the UI and on-chain behavior express the same product rules. **Worked examples:** ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#recommended-commission-fee-structure) Recommended commission fee structure Vault factories can charge a commission fee from the tax revenue. The fee structure must be clearly described in the vault's `description()` method. The recommended fee calculation is based on the tax rate (`taxRateBps`) and the received tax revenue (`msg.value`): * If `taxRate` ≤ 1% (100 bps), the fee is **6%** of `msg.value`. * If `taxRate` > 1%, the fee is `(msg.value * 6) / taxRateBps`. [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#ui-schema-reference-ivaultschemasv1) UI Schema Reference (IVaultSchemasV1) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The shared struct definitions in `IVaultSchemasV1.sol` power the automatic UI generation for both vault factories (token launch forms) and vaults (vault interaction pages). ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#fielddescriptor) FieldDescriptor The unified leaf type shared by both the factory schema and the vault UI schema: **Supported** `**fieldType**` **values:** `fieldType` UI widget Notes `"string"` Text input `"address"` Address input With checksum validation `"uint16"` Number input 0–65535 `"uint256"` Big-number input `"uint128"` Number input `"bool"` Checkbox `"bytes"` Hex input `"bytes32"` Hex input 32 bytes `"time"` Date/time picker Alias for `uint256`; value is a Unix timestamp in seconds. Displayed as human-readable time or countdown in outputs `"msg.value"` Number input **Special Type**. Represents `tx.value` (wei). **Not** ABI-encoded. Only valid for write method inputs. `**decimals**` **behaviour:** * **Factory encoding (input):** If `decimals > 0`, the UI multiplies the user's input by $10^{\\text{decimals}}$ before ABI-encoding. If `0`, the raw value is used. * **Vault display (output):** If `decimals > 0`, the UI divides the raw on-chain value by $10^{\\text{decimals}}$ for display. If `0`, the raw value is displayed. * For non-numeric fields (`string`, `address`, `bytes`, `bool`), `decimals` should always be `0`. ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultdataschema) VaultDataSchema Returned by `VaultFactoryBaseV2.vaultDataSchema()`. Describes the shape of the `vaultData` bytes the factory expects: **How the UI uses VaultDataSchema:** 1. Call `factory.vaultDataSchema()` to get the schema. 2. For each field in `fields`, render the appropriate input widget based on `fieldType`. 3. If `isArray == true`, render an "Add Item" button for dynamic array entries. 4. Encode the user input: if `isArray` use `abi.encode(tuple[])`; otherwise use `abi.encode(tuple)`. 5. The encoded bytes are passed as `vaultData` in `NewTaxTokenWithVaultParams`. If `fields` is empty and `isArray` is false, the factory ignores `vaultData` entirely. ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#factorypolicy) FactoryPolicy Returned by `VaultFactoryBaseV2.tokenCreationPolicies()`. Each struct describes one constraint the factory enforces on the **normalized launch validation payload** used by the current factory spec. For legacy v2.1 factories, these policies normally correspond to checks on `NewTokenV6WithVaultParams`. For v2.2+ factories, they normally correspond to checks on `LaunchValidationDataV1`. See the [V2.1+: Policy discovery](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v21-policy-discovery--tokencreationpolicies) section for the full operator table, encoding rules, and worked examples. ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultuischema-and-supporting-types) VaultUISchema & supporting types Returned by `VaultBaseV2.vaultUISchema()`. Describes the vault's entire UI surface: **How the UI uses VaultUISchema:** 1. Display `vaultType` as a badge/header and `description` as subtitle. 2. Always call `vault.description()` and display the result as a dynamic status banner (polled periodically). 3. For each method: * **View methods** (`isWriteMethod == false`): If no inputs, call immediately and display results. If inputs exist, render input fields with a "Query" button. * **Write methods** (`isWriteMethod == true`): Render a form with inputs. If `approvals` is non-empty, execute each `ApproveAction` before sending the write transaction. 4. Methods are displayed in the order returned. **ApproveAction workflow:** 1. Resolve the token address from `tokenType`: `"taxToken"` → call `vault.taxToken()`, `"lpToken"` → call `vault.lpToken()`, unknown → skip (forward-compatible). 2. Read the amount from the user's input field named by `amountFieldName` (already scaled by `decimals`). 3. Check current allowance: `token.allowance(user, vault)`. If sufficient, skip. 4. Send `token.approve(vault, amount)` and wait for confirmation. [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#get-your-vault-verified) Get your vault verified --------------------------------------------------------------------------------------------------------------------------------------------------- Please reach out to our team if you want to get your vault or vault factory verified by us or our auditing partners. Before reaching out, make sure: * You have correctly implemented the Vault Specification (the `description()` method, the Guardian access to permissioned functions, and `vaultUISchema()` / `vaultDataSchema()` if using V2). * Your factory contract is not upgradeable. If you want to upgrade your factory in the future, you must deploy a new factory contract and get it verified again. * The commission fee structure is clearly described in the vault's `description()` method. * You have passed some basic AI auditing tools — see the FAQ below. **We strongly recommend using AI auditing tools before deploying your smart contracts and tokens.** This can help you resolve critical vulnerabilities before submitting for manual review. [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#faq) FAQ ----------------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#how-to-use-ai-to-audit-your-smart-contracts) How to use AI to audit your smart contracts? You can use any AI for auditing your smart contracts. For example, you can use [Google's AI Studio which is free to use](https://aistudio.google.com/prompts/new_chat?model=gemini-2.5-pro) . The prompt is as follows: This can help you identify common vulnerabilities and issues in your smart contracts. If your comments are clear enough, it will even help identify logical issues. However, AI tools are not perfect and may miss vulnerabilities or provide incorrect suggestions. Always review your smart contracts thoroughly before deploying them on mainnet. [PreviousDeployed Contract Addresses](https://docs.flap.sh/flap/developers/vault-developers/deployed-contract-addresses) [NextFlap Tax Vault](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault) Last updated 1 month ago * [Table of Contents](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#table-of-contents) * [Quick start](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#quick-start) * [Overview](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#overview) * [The Vault Specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#the-vault-specification) * [VaultBase (V1)](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultbase-v1) * [VaultBaseV2](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultbasev2) * [The Flap Guardian](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#the-flap-guardian) * [Adapter for legacy vaults](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#adapter-for-legacy-vaults) * [VaultFactory Specification](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultfactory-specification) * [IVaultFactory (V1)](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#ivaultfactory-v1) * [VaultFactoryBaseV2](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultfactorybasev2) * [V2.1: Validation hook — onBeforeNewTokenV6WithVault](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v2.1-validation-hook-onbeforenewtokenv6withvault) * [V2.2: Generic validation hook — onBeforeLaunch(bytes)](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v2.2-generic-validation-hook-onbeforelaunch-bytes) * [V2.2: Validation payload — LaunchValidationDataV1](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v2.2-validation-payload-launchvalidationdatav1) * [V2.2: Spec version discovery — factorySpecVersion()](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v2.2-spec-version-discovery-factoryspecversion) * [V2.1+: Policy discovery — tokenCreationPolicies](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#v2.1-policy-discovery-tokencreationpolicies) * [Recommended commission fee structure](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#recommended-commission-fee-structure) * [UI Schema Reference (IVaultSchemasV1)](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#ui-schema-reference-ivaultschemasv1) * [FieldDescriptor](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#fielddescriptor) * [VaultDataSchema](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultdataschema) * [FactoryPolicy](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#factorypolicy) * [VaultUISchema & supporting types](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#vaultuischema-and-supporting-types) * [Get your vault verified](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#get-your-vault-verified) * [FAQ](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#faq) * [How to use AI to audit your smart contracts?](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification#how-to-use-ai-to-audit-your-smart-contracts) Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @title VaultBase /// @notice Abstract base contract for all vault implementations /// @author The Flap Team abstract contract VaultBase { /// @notice Error thrown when the current chain is not supported error UnsupportedChain(uint256 chainId); /// @notice Get the Portal address for the current chain /// @dev Currently supports BNB Chain (chain ID 56) and BNB Testnet (chain ID 97) /// @return portal The Portal contract address function _getPortal() internal view returns (address portal) { uint256 chainId = block.chainid; if (chainId == 56) { return 0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0; } else if (chainId == 97) { return 0x5bEacaF7ABCbB3aB280e80D007FD31fcE26510e9; } revert UnsupportedChain(chainId); } /// @notice Get the Guardian address for the current chain /// @dev Currently supports BNB Chain (chain ID 56) and BNB Testnet (chain ID 97) /// @return guardian The Guardian contract address function _getGuardian() internal view returns (address guardian) { uint256 chainId = block.chainid; if (chainId == 56) { return 0x9e27098dcD8844bcc6287a557E0b4D09C86B8a4b; } else if (chainId == 97) { return 0x76Fa8C526f8Bc27ba6958B76DeEf92a0dbE46950; } revert UnsupportedChain(chainId); } /// @notice Returns a description of the vault /// @dev Must be overridden to provide a dynamic description based on the vault's current state /// @return A string describing the vault's current state and configuration function description() public view virtual returns (string memory); } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {VaultBase} from "./VaultBase.sol"; import {VaultUISchema} from "./IVaultSchemasV1.sol"; /// @title VaultBaseV2 /// @author The Flap Team abstract contract VaultBaseV2 is VaultBase { /// @notice Returns the UI schema describing which methods the UI should /// render for this vault. /// @return schema The complete UI schema for this vault. function vaultUISchema() public pure virtual returns (VaultUISchema memory schema); } Copy function vaultUISchema() public pure override returns (VaultUISchema memory schema) { schema.vaultType = "DonationVault"; schema.description = "Collects BNB donations and lets a designated charity withdraw."; schema.methods = new VaultMethodSchema[](3); // View: totalDonated() — no inputs, one uint256 output schema.methods[0].name = "totalDonated"; schema.methods[0].description = "Returns the total BNB donated so far."; schema.methods[0].outputs = new FieldDescriptor[](1); schema.methods[0].outputs[0] = FieldDescriptor("total", "uint256", "Total BNB donated", 18); schema.methods[0].approvals = new ApproveAction[](0); // Write: donate() — msg.value input // fieldType "msg.value": the UI sets tx.value on the transaction instead of // ABI-encoding the amount as a calldata parameter. Only valid on write methods. schema.methods[1].name = "donate"; schema.methods[1].description = "Send BNB as a donation."; schema.methods[1].inputs = new FieldDescriptor[](1); schema.methods[1].inputs[0] = FieldDescriptor("amount", "msg.value", "BNB to donate", 18); schema.methods[1].outputs = new FieldDescriptor[](0); schema.methods[1].approvals = new ApproveAction[](0); schema.methods[1].isWriteMethod = true; // Write: withdraw() — no inputs schema.methods[2].name = "withdraw"; schema.methods[2].description = "Withdraws accumulated donations to the charity address."; schema.methods[2].inputs = new FieldDescriptor[](0); schema.methods[2].outputs = new FieldDescriptor[](0); schema.methods[2].approvals = new ApproveAction[](0); schema.methods[2].isWriteMethod = true; } Copy function vaultUISchema() public pure override returns (VaultUISchema memory schema) { schema.vaultType = "StakingVault"; schema.description = "Stakes tax tokens or LP tokens to earn rewards."; schema.methods = new VaultMethodSchema[](2); // View: stakedBalance(address) schema.methods[0].name = "stakedBalance"; schema.methods[0].description = "Returns the staked balance for a given user."; schema.methods[0].inputs = new FieldDescriptor[](1); schema.methods[0].inputs[0] = FieldDescriptor("user", "address", "The user address to query", 0); schema.methods[0].outputs = new FieldDescriptor[](1); schema.methods[0].outputs[0] = FieldDescriptor("balance", "uint256", "Staked token balance", 18); // Write: deposit(uint256) — requires ERC-20 approval of the tax token schema.methods[1].name = "deposit"; schema.methods[1].description = "Stake tax tokens into the vault to earn rewards."; schema.methods[1].inputs = new FieldDescriptor[](1); schema.methods[1].inputs[0] = FieldDescriptor("amount", "uint256", "Amount of tax tokens to stake", 18); schema.methods[1].approvals = new ApproveAction[](1); schema.methods[1].approvals[0] = ApproveAction("taxToken", "amount"); schema.methods[1].isWriteMethod = true; } Copy function vaultUISchema() public pure override returns (VaultUISchema memory schema) { schema.vaultType = "NoteVault"; schema.description = "A public on-chain diary. Anyone can submit a note with an optional BNB tip. " "All entries are stored on-chain and readable page-by-page."; schema.methods = new VaultMethodSchema[](3); // View: noteCount() — no inputs, plain uint256 output (raw count, decimals = 0) schema.methods[0].name = "noteCount"; schema.methods[0].description = "Total number of notes submitted."; schema.methods[0].inputs = new FieldDescriptor[](0); schema.methods[0].outputs = new FieldDescriptor[](1); schema.methods[0].outputs[0] = FieldDescriptor("count", "uint256", "Total notes", 0); schema.methods[0].approvals = new ApproveAction[](0); // View: getNotes(uint256 offset, uint256 limit) — paginated, returns Note[] // isOutputArray = true: outputs describe the fields of *each tuple* in the returned array, // not a single return value. The UI renders a table with one row per Note. schema.methods[1].name = "getNotes"; schema.methods[1].description = "Fetch a page of notes. Use offset + limit to paginate."; schema.methods[1].inputs = new FieldDescriptor[](2); schema.methods[1].inputs[0] = FieldDescriptor("offset", "uint256", "Start index (0-based)", 0); schema.methods[1].inputs[1] = FieldDescriptor("limit", "uint256", "Max items to return", 0); schema.methods[1].outputs = new FieldDescriptor[](3); schema.methods[1].outputs[0] = FieldDescriptor("author", "address", "Author address", 0); schema.methods[1].outputs[1] = FieldDescriptor("createdAt", "time", "Submitted at", 0); schema.methods[1].outputs[2] = FieldDescriptor("content", "string", "Note content", 0); schema.methods[1].isOutputArray = true; // ← output is Note[], not a single tuple schema.methods[1].approvals = new ApproveAction[](0); // Write: submitNote(string content) — string input + optional msg.value tip schema.methods[2].name = "submitNote"; schema.methods[2].description = "Submit a note. Attach a BNB tip (optional, set 0 to skip)."; schema.methods[2].inputs = new FieldDescriptor[](2); schema.methods[2].inputs[0] = FieldDescriptor("content", "string", "Note content", 0); schema.methods[2].inputs[1] = FieldDescriptor("tip", "msg.value", "Optional BNB tip", 18); schema.methods[2].outputs = new FieldDescriptor[](0); schema.methods[2].approvals = new ApproveAction[](0); schema.methods[2].isWriteMethod = true; } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @title FlapGuardian /// @notice Guardian contract for vault emergency management and CTO cases /// @dev Currently does nothing, but this contract is upgradeable. contract FlapGuardian { function version() external pure returns (string memory) { return "0.0.1"; } } Copy function revokeRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) { address guardian = _getGuardian(); if (account == guardian) { revert CannotRevokeGuardianRole(); } super.revokeRole(role, account); } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @title IVaultFactory /// @notice Interface that all vault factory contracts must implement interface IVaultFactory { error OnlyVaultPortal(); error ZeroAddress(); /// @notice Creates a new vault instance for a tax token /// @dev IMPORTANT: The taxToken does not exist yet when this method is called. /// The VaultPortal predicts the token address and passes it here. /// The actual token will be created AFTER the vault is created. /// @param taxToken The predicted address of the tax token (not yet deployed) /// @param quoteToken The quote token address (e.g., address(0) for native BNB) /// @param creator The original msg.sender to VaultPortal who initiated token creation /// @param vaultData Custom encoded data specific to this vault type /// @return vault The address of the newly created vault function newVault(address taxToken, address quoteToken, address creator, bytes calldata vaultData) external returns (address vault); /// @notice Checks if a quote token is supported by this vault factory /// @param quoteToken The quote token address to check /// @return supported True if the quote token is supported, false otherwise function isQuoteTokenSupported(address quoteToken) external view returns (bool supported); } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {IVaultFactory} from "./IVaultFactory.sol"; import {VaultDataSchema} from "./IVaultSchemasV1.sol"; /// @title VaultFactoryBaseV2 /// @author The Flap Team abstract contract VaultFactoryBaseV2 is IVaultFactory { error UnsupportedChain(uint256 chainId); /// @notice Returns the schema describing the `vaultData` bytes expected /// by this factory's `newVault()` method. /// @return schema The vault data schema for this factory. function vaultDataSchema() public pure virtual returns (VaultDataSchema memory schema); /// @notice Get the VaultPortal address for the current chain. function _getVaultPortal() internal view returns (address vaultPortal) { uint256 chainId = block.chainid; if (chainId == 56) { return 0x90497450f2a706f1951b5bdda52B4E5d16f34C06; } else if (chainId == 97) { return 0x027e3704fC5C16522e9393d04C60A3ac5c0d775f; } revert UnsupportedChain(chainId); } /// @notice Get the Guardian address for the current chain. function _getGuardian() internal view returns (address guardian) { uint256 chainId = block.chainid; if (chainId == 56) { return 0x9e27098dcD8844bcc6287a557E0b4D09C86B8a4b; } else if (chainId == 97) { return 0x76Fa8C526f8Bc27ba6958B76DeEf92a0dbE46950; } revert UnsupportedChain(chainId); } } Copy function vaultDataSchema() public pure override returns (VaultDataSchema memory schema) { schema.description = "Creates a staking vault. Specify a reward rate and lock duration."; schema.fields = new FieldDescriptor[](2); schema.fields[0] = FieldDescriptor("rewardRateBps", "uint16", "Annual reward rate in basis points", 0); schema.fields[1] = FieldDescriptor("lockDuration", "uint256", "Lock duration in seconds", 0); schema.isArray = false; } Copy function vaultDataSchema() public pure override returns (VaultDataSchema memory schema) { schema.description = "Creates a vault that distributes received BNB " "among a dynamic set of payees by basis-point shares."; schema.fields = new FieldDescriptor[](2); schema.fields[0] = FieldDescriptor("payee", "address", "Payee wallet address", 0); schema.fields[1] = FieldDescriptor("bps", "uint16", "Basis points share (10000 = 100%)", 0); schema.isArray = true; // vaultData = abi.encode((address,uint16)[]) } Copy function vaultDataSchema() public pure override returns (VaultDataSchema memory schema) { schema.description = "Creates a vault with no configurable parameters. " "No user input is required — vaultData is ignored."; schema.fields = new FieldDescriptor[](0); schema.isArray = false; } Copy function onBeforeNewTokenV6WithVault(IVaultPortalTypes.NewTokenV6WithVaultParams calldata params) external virtual returns (bool success, string memory reason) { revert LegacyV6ValidationHookNotImplemented(); } Copy function onBeforeNewTokenV6WithVault(IVaultPortalTypes.NewTokenV6WithVaultParams calldata params) external override returns (bool success, string memory reason) { address resolvedQuote = params.quoteToken == address(0) ? WBNB : params.quoteToken; if (params.dividendToken != resolvedQuote) { return (false, "Dividend token must equal the quote token."); } return (true, ""); } Copy function onBeforeLaunch(bytes calldata validationData) external view virtual returns (bool success, string memory reason) { LaunchValidationDataV1 memory data = abi.decode(validationData, (LaunchValidationDataV1)); return _validateBeforeLaunch(data); } Copy function _validateBeforeLaunch(LaunchValidationDataV1 memory data) internal view virtual returns (bool success, string memory reason) { data = data; return (true, ""); } Copy function _validateBeforeLaunch(LaunchValidationDataV1 memory data) internal pure override returns (bool success, string memory reason) { if (data.tokenVersion != IPortalTypes.TokenVersion.TOKEN_V3_PERMIT) { return (false, "Gift V4 requires TOKEN_V3_PERMIT."); } if (data.quoteToken != address(0)) { return (false, "Gift V4 currently supports native BNB only."); } if (data.dividendBps != 0) { return (false, "Use tracker-only dividend mode. The vault decides when to deposit rewards."); } if (data.dividendToken != address(0)) { return (false, "Dividend claims should be paid in native BNB (wrapped internally by the Dividend contract)."); } if (data.buyTaxRate != 0 || data.sellTaxRate != 0) { return (false, "Gift V4 requires buyTaxRate = sellTaxRate = 0%."); } return (true, ""); } Copy struct LaunchValidationDataV1 { IPortalTypes.TokenVersion tokenVersion; address quoteToken; uint16 buyTaxRate; uint16 sellTaxRate; uint16 vaultBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; address dividendToken; uint256 minimumShareBalance; } Copy function factorySpecVersion() public pure virtual returns (string memory) { return "v2.2"; } Copy function tokenCreationPolicies() public pure virtual returns (FactoryPolicy[] memory policies) { return new FactoryPolicy[](0); } Copy struct FactoryPolicy { string target; // Field name in normalized launch params (e.g. "dividendToken") string operator; // Comparison operator (see table below) bytes value; // ABI-encoded expected value string description; // Human-readable hint shown in the UI } Copy function tokenCreationPolicies() public pure returns (FactoryPolicy[] memory policies) { policies = new FactoryPolicy[](3); // Example A — dividendToken must equal a specific address (WBNB) policies[0] = FactoryPolicy({ target: "dividendToken", operator: "eq", value: abi.encode(address(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c)), description: "Dividend token must equal the quote token (WBNB)." }); // Example B — dividendBps must be at least 100 (1%) policies[1] = FactoryPolicy({ target: "dividendBps", operator: "gte", value: abi.encode(uint256(100)), description: "Dividend BPS must be at least 100 (1%) for this vault type." }); // Example C — quoteToken must be one of an allowed set address[] memory allowed = new address[](2); allowed[0] = WBNB; allowed[1] = USDT; policies[2] = FactoryPolicy({ target: "quoteToken", operator: "in", value: abi.encode(allowed), description: "Quote token must be WBNB or USDT." }); } Copy receive() external payable { if (msg.value == 0) return; if (taxRateBps == 0) { try ITaxToken(taxToken).taxRate() returns (uint256 _taxRate) { if (_taxRate > 0) { taxRateBps = _taxRate; } } catch {} } uint256 fee = 0; if (taxRateBps <= 100) { // 6% of msg.value if taxRate <= 1% fee = msg.value * 600 / 10000; } else { // Examples: // 1% (100 bps) → 6% // 2% (200 bps) → 3% // 3% (300 bps) → 2% // 10% (1000 bps) → 0.6% fee = (msg.value * 6) / taxRateBps; } // your main logic — accumulate fee or send fee } Copy /// @notice Describes a single field (parameter, return value, or vault-data component). struct FieldDescriptor { string name; // Machine-readable name (e.g. "recipient", "bps", "amount") string fieldType; // Solidity ABI type string (e.g. "address", "uint256", "string") string description; // Human-readable explanation shown as label/tooltip uint8 decimals; // Decimal precision hint for numeric fields } Copy /// @notice Describes the shape of the `vaultData` bytes expected by a factory's `newVault()`. struct VaultDataSchema { string description; // Free-form explanation shown to the user FieldDescriptor[] fields; // Ordered list of tuple components bool isArray; // true = vaultData is abi.encode(tuple[]) } Copy /// @notice A single constraint on normalized launch validation data. struct FactoryPolicy { string target; // Field name in normalized launch params string operator; // "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" | "notIn" bytes value; // ABI-encoded expected value (type inferred from target field) string description; // Human-readable hint shown in the UI } Copy /// @notice An ERC-20 approve action the UI must execute before calling a write method. struct ApproveAction { string tokenType; // "taxToken" or "lpToken" string amountFieldName; // Name of the input field whose value is the approve amount } /// @notice Describes a single view or write method the UI should render. struct VaultMethodSchema { string name; // Solidity method name string description; // Human-readable explanation FieldDescriptor[] inputs; // Ordered input parameters FieldDescriptor[] outputs; // Ordered return values ApproveAction[] approvals; // ERC-20 approvals required before a write bool isInputArray; // true = input is tuple[] bool isOutputArray; // true = output is tuple[] bool isWriteMethod; // true = state-changing, false = view } /// @notice Top-level schema describing the vault's entire UI surface. struct VaultUISchema { string vaultType; // e.g. "FlapXVault", "SplitVault" string description; // Overall explanation of the vault VaultMethodSchema[] methods; // All methods the UI should render (ordered) } Copy You are a professional smart contract auditor. Generate an audit report for the following Solidity smart contract code. Identify potential vulnerabilities, code smells, questions, and best practice violations. Provide recommendations for improvements. Return the result in markdown format and put the markdown in a code block. ======================== --- # Launch token through Portal | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal.md) . This page explains how to launch a token directly through `Portal`. The current launch entry points are: * `newTokenV6` for `TOKEN_V2_PERMIT` and all currently supported tax-token versions (`TOKEN_TAXED`, `TOKEN_TAXED_V2`, `TOKEN_TAXED_V3`) * `newTokenV7` for `TOKEN_V3_PERMIT`, the TokenV3-based non-tax flow used for CL-style migration Legacy methods (`newTokenV2`, `newTokenV3`, `newTokenV4`, `newTokenV5`) remain available for backward compatibility. `TOKEN_TAXED_V3` (`FlapTaxTokenV3`) is the recommended token type for all new tax token launches. It supports asymmetric buy/sell tax rates, commission receivers, and bidirectional dynamic liquidation thresholds. Use `newTokenV6` with `tokenVersion = TOKEN_TAXED_V3` for new integrations. `TOKEN_V3_PERMIT` is launched through `newTokenV7`, not `newTokenV6`. Use it when you want the TokenV3 non-tax path together with CL-style migration. [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#id-1-prepare-token-metadata) 1) Prepare token metadata --------------------------------------------------------------------------------------------------------------------------------------------------------- Token metadata is stored on IPFS. You can upload your image and metadata JSON through the Flap upload API. Example (TypeScript): Copy async function uploadTokenMeta(cfg: { buy: string | null; creator: string; description: string; sell: string | null; telegram: string | null; twitter: string | null; website: string | null; image_path: string; }) { const form = new FormData(); const MUTATION_CREATE = ` mutation Create($file: Upload!, $meta: MetadataInput!) { create(file: $file, meta: $meta) } `; form.append( "operations", JSON.stringify({ query: MUTATION_CREATE, variables: { file: null, meta: { website: cfg.website, twitter: cfg.twitter, telegram: cfg.telegram, description: cfg.description, creator: "0x0000000000000000000000000000000000000000", } }, }) ); form.append( "map", JSON.stringify({ "0": ["variables.file"], }) ); const file = new File([fs.readFileSync(cfg.image_path)], "image.png", { type: "image/png", }); form.append("0", file); const res = await axios.postForm(FlapConfig.api, form, { headers: { "Content-Type": "multipart/form-data", }, }); if (res.status !== 200) { throw new Error(`failed to upload the token meta: ${res.statusText}`); } const cid = res.data.data.create; return cid; } You must upload the image to IPFS and pin the metadata using our API: [https://funcs.flap.sh/api/upload](https://funcs.flap.sh/api/upload) . Our indexer will not be able to fetch your file if it is not pinned on our gateway. Besides, many terminals fetch files through our gateway and we will warm the CDN on upload to improve loading speed. [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#id-2-call-newtokenv6) 2) Call `newTokenV6` --------------------------------------------------------------------------------------------------------------------------------------------- `newTokenV6` is the recommended unified entry point for all token types. The token implementation is selected via the `tokenVersion` field. `NewTokenV6Params` covers all token types via `tokenVersion`: ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#token-version-dispatch) Token version dispatch The `tokenVersion` field controls which token implementation is created: `tokenVersion` Token type Notes `TOKEN_V2_PERMIT` Standard ERC-20 with permit Set both tax rates to 0, `commissionReceiver` must be `address(0)` `TOKEN_TAXED` FlapTaxToken V1 Legacy; symmetric rates only, `mktBps` must be 10000, no commission. **Not recommended for new launches.** `TOKEN_TAXED_V2` FlapTaxTokenV2 Legacy; symmetric rates only, `mktBps` must not be 10000, no commission. **Not recommended for new launches.** `TOKEN_TAXED_V3` FlapTaxTokenV3 **Recommended.** Asymmetric rates, commission supported. `TOKEN_TAXED` and `TOKEN_TAXED_V2` are deprecated. New integrations should always use `TOKEN_TAXED_V3`. `TOKEN_V3_PERMIT` is intentionally not part of `newTokenV6`. Use `newTokenV7` for that launch path. ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#launching-a-tax-token-v3-recommended) Launching a Tax Token V3 (recommended) Set `tokenVersion = TOKEN_TAXED_V3` and fill in the tax fields: Key notes: * `meta` is the IPFS CID of your metadata JSON. * `migratorType` must be `V2_MIGRATOR` for tax tokens. * `quoteToken = address(0)` uses the native gas token. * `salt` must produce the required vanity suffix: `7777` for tax tokens, `8888` for standard tokens. * `mktBps + deflationBps + dividendBps + lpBps` must equal 10000. * `dividendToken` supports three modes: **Case 1** — `0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd` (`MAGIC_DIVIDEND_SELF`, distributes the launching token itself); **Case 2** — `address(0)` or the quote token address (most common); **Case 3** — any ERC-20 that has a registered swap path in `SwapRegistry`. See [Tax Token V3](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3#dividend-token) for full details. * Set `commissionReceiver` to your address to earn a protocol-calculated commission from every taxable transaction. ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#launching-a-standard-token) Launching a standard token Set `tokenVersion = TOKEN_V2_PERMIT` and all tax fields to 0: [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#id-3-call-newtokenv7) 3) Call `newTokenV7` --------------------------------------------------------------------------------------------------------------------------------------------- `newTokenV7` is the TokenV3-based launch entry point. It is intended for CL-style migration and introduces `feeConfigs`, which replace the old `beneficiary + mktBps/deflationBps/dividendBps/lpBps` split used by `newTokenV6` tax launches. ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#current-newtokenv7-rollout-details) Current `newTokenV7` rollout details The interface includes both `TOKEN_V3_PERMIT` and `TOKEN_TAXED_V3` paths, but the current implementation is narrower: * Public `newTokenV7` launches currently support `TOKEN_V3_PERMIT` only. * `TOKEN_TAXED_V3` through `newTokenV7` currently reverts with `NewTokenV7RuleViolation(4)`. * For tax-token launches, keep using `newTokenV6`. * The current implementation accepts `PCS_INFINITY_CL_MIGRATOR` for the public `TOKEN_V3_PERMIT` path. * `commissionReceiver` must be `address(0)` for `TOKEN_V3_PERMIT`. * `buyTaxRate` and `sellTaxRate` must both be `0` for `TOKEN_V3_PERMIT`. * `antiFarmerDuration` must not exceed `365 days`. Although `MigratorType` includes `V4_UNI_MIGRATOR`, the current `TOKEN_V3_PERMIT` implementation behind `newTokenV7` validates against `PCS_INFINITY_CL_MIGRATOR`. If you send any other migrator type on the public path, the call reverts with `InvalidMigratorType()`. ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#how-feeconfigs-work) How `feeConfigs` work `feeConfigs` has 4 fixed slots. General validation rules from the launcher are: * every slot with `feeType != NONE` must have `bps > 0` * duplicate fee types are not allowed * all active slots together must sum to exactly `10000` bps * `MARKETING_OR_VAULT` requires a non-zero `marketingAddress` * `DIVIDEND` uses `dividendToken` and `minimumShareBalance` For the current public `TOKEN_V3_PERMIT` rollout, there are extra restrictions: * only **one** active fee mode is currently supported * supported mode A: a single `MARKETING_OR_VAULT` slot with `bps = 10000` * supported mode B: a single `DIVIDEND` slot with `bps = 10000` and `dividendToken == quoteToken` * `DEFLATION` and `LP_BPS` are not enabled yet for `TOKEN_V3_PERMIT` and revert with `UnsupportedV7TokenV3PermitFeeType(...)` There is no standalone `beneficiary` field in `NewTokenV7Params`. If you use `MARKETING_OR_VAULT`, the first such slot's `marketingAddress` becomes the primary beneficiary used by the launcher. ### [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#example-zero-tax-token_v3_permit-launch) Example: zero-tax `TOKEN_V3_PERMIT` launch If you use the dividend-only mode instead of the marketing mode, set exactly one `DIVIDEND` slot with `bps = 10000`, and set `dividendToken` to the same address as `quoteToken`. [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#id-4-find-the-salt-vanity-suffix) 4) Find the salt (vanity suffix) --------------------------------------------------------------------------------------------------------------------------------------------------------------------- `Portal` uses CREATE2 for deterministic deployments. The `salt` must produce a token address with the required suffix: * Non-tax token: address ends with `8888`. * Tax token: address ends with `7777`. To find a valid `salt`, repeatedly hash a random seed and check the predicted address until it matches the suffix. You can predict the address using CREATE2 with the `Portal` address and the token implementation address for the token type you’re launching. Example (TypeScript): Use the correct `tokenImpl` and `portal` addresses for your network (see the deployed addresses page in this section). Token implementation selection for launch methods: * `TOKEN_V2_PERMIT` (standard token): use the **Standard Token Impl** (`tokenImplV2`). * `TOKEN_TAXED_V3` (recommended tax token): use the **Tax Token V3 Impl** (`tokenImplTaxedV3`). * `TOKEN_TAXED` (legacy V1, not recommended): use the **Tax Token V1 Impl** (`tokenImplTaxed`). * `TOKEN_TAXED_V2` (legacy V2, not recommended): use the **Tax Token V2 Impl** (`tokenImplTaxedV2`). * `TOKEN_V3_PERMIT` via `newTokenV7`: use the **Standard Token V3 Impl**. It is still a non-tax launch, so the vanity suffix is `8888`. See [deployed-contract-addresses.md](https://docs.flap.sh/flap/developers/token-launcher-developers/deployed-contract-addresses) for the exact implementation addresses. For new launches, always use `tokenImplTaxedV3` when computing salts for tax tokens. Legacy tax token implementations (`TOKEN_TAXED`, `TOKEN_TAXED_V2`) are deprecated and should not be used for new integrations. `newTokenV7` currently does not expose a public `TOKEN_TAXED_V3` rollout. For tax-token launches, compute the salt against the `newTokenV6` implementation path and use `newTokenV6`. [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#id-5-legacy-methods) 5) Legacy methods ----------------------------------------------------------------------------------------------------------------------------------------- `newTokenV2`, `newTokenV3`, `newTokenV4`, and `newTokenV5` are still supported for legacy integrations. They follow the same flow but provide fewer features than the current launch paths. New integrations should use `newTokenV6` for tax tokens and `TOKEN_V2_PERMIT`, or `newTokenV7` for `TOKEN_V3_PERMIT`. [](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#iportal.sol) IPortal.sol --------------------------------------------------------------------------------------------------------------------------- Full interface reference for developers: [PreviousDeployed Contract Addresses](https://docs.flap.sh/flap/developers/token-launcher-developers/deployed-contract-addresses) [NextLaunch token through VaultPortal](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal) Last updated 1 month ago * [1) Prepare token metadata](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#id-1-prepare-token-metadata) * [2) Call newTokenV6](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#id-2-call-newtokenv6) * [Token version dispatch](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#token-version-dispatch) * [Launching a Tax Token V3 (recommended)](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#launching-a-tax-token-v3-recommended) * [Launching a standard token](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#launching-a-standard-token) * [3) Call newTokenV7](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#id-3-call-newtokenv7) * [Current newTokenV7 rollout details](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#current-newtokenv7-rollout-details) * [How feeConfigs work](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#how-feeconfigs-work) * [Example: zero-tax TOKEN\_V3\_PERMIT launch](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#example-zero-tax-token_v3_permit-launch) * [4) Find the salt (vanity suffix)](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#id-4-find-the-salt-vanity-suffix) * [5) Legacy methods](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#id-5-legacy-methods) * [IPortal.sol](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal#iportal.sol) Copy /// @notice Create a new token (V6) — unified entry point for all token versions. /// @param params The parameters for the new token (see NewTokenV6Params). /// @return token The address of the created token. function newTokenV6(NewTokenV6Params calldata params) external payable returns (address token); Copy struct NewTokenV6Params { string name; string symbol; string meta; DexThreshType dexThresh; bytes32 salt; MigratorType migratorType; address quoteToken; uint256 quoteAmt; address beneficiary; bytes permitData; bytes32 extensionID; bytes extensionData; DEXId dexId; V3LPFeeProfile lpFeeProfile; // Tax fields (set to 0 for non-tax tokens) uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; uint64 antiFarmerDuration; uint16 mktBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; uint256 minimumShareBalance; // V3-only fields address dividendToken; address commissionReceiver; TokenVersion tokenVersion; } Copy import { parseEther, zeroAddress } from "viem"; const params = { name: "My Token", symbol: "MTK", meta: "", dexThresh: DexThreshType.TWO_THIRDS, salt, // must produce 7777 vanity suffix migratorType: MigratorType.V2_MIGRATOR, quoteToken: zeroAddress, // native gas token quoteAmt: parseEther("0.01"), beneficiary: beneficiaryAddress, permitData: "0x", extensionID: "0x0000000000000000000000000000000000000000000000000000000000000000", extensionData: "0x", dexId: DEXId.DEX0, lpFeeProfile: V3LPFeeProfile.LP_FEE_PROFILE_STANDARD, buyTaxRate: 300, // 3% sellTaxRate: 1000, // 10% taxDuration: 365n * 24n * 60n * 60n, antiFarmerDuration: 60n * 60n, mktBps: 10000, // all tax to beneficiary (after protocol fee) deflationBps: 0, dividendBps: 0, lpBps: 0, minimumShareBalance: 0n, dividendToken: zeroAddress, // Case 2: quote token (zeroAddress = native) // Case 1: 0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd (MAGIC_DIVIDEND_SELF = launching token itself) // Case 3: any ERC-20 supported by SwapRegistry commissionReceiver: zeroAddress, // set to your address to earn commission tokenVersion: TokenVersion.TOKEN_TAXED_V3, }; const hash = await walletClient.writeContract({ address: portalAddress, abi: portalAbi, functionName: "newTokenV6", args: [params], value: parseEther("0.01"), }); Copy function launchStandardToken(IPortal portal) external payable returns (address token) { IPortalTypes.NewTokenV6Params memory params = IPortalTypes.NewTokenV6Params({ // ... base fields ... buyTaxRate: 0, sellTaxRate: 0, // ... other tax fields = 0 ... commissionReceiver: address(0), tokenVersion: IPortalTypes.TokenVersion.TOKEN_V2_PERMIT }); token = portal.newTokenV6{value: msg.value}(params); } Copy /// @notice Create a new token with V4/PCS Infinity migration support /// @param params The V7 token parameters /// @return token The created token address function newTokenV7(NewTokenV7Params calldata params) external payable returns (address token); enum FeeType { NONE, MARKETING_OR_VAULT, DIVIDEND, DEFLATION, LP_BPS } struct FeeConfig { FeeType feeType; uint16 bps; address marketingAddress; address dividendToken; uint256 minimumShareBalance; } struct NewTokenV7Params { string name; string symbol; string meta; DexThreshType dexThresh; bytes32 salt; MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; DEXId dexId; uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; uint64 antiFarmerDuration; address commissionReceiver; TokenVersion tokenVersion; FeeConfig[4] feeConfigs; } Copy const zeroAddress = "0x0000000000000000000000000000000000000000"; const params = { name: "My CL Token", symbol: "MCL", meta: "", dexThresh: DexThreshType.FOUR_FIFTHS, salt: vanitySalt, // must produce 8888 suffix migratorType: MigratorType.PCS_INFINITY_CL_MIGRATOR, quoteToken: zeroAddress, quoteAmt: parseEther("0.1"), permitData: "0x", extensionID: "0x0000000000000000000000000000000000000000000000000000000000000000", extensionData: "0x", dexId: DEXId.DEX0, buyTaxRate: 0, sellTaxRate: 0, taxDuration: 0, antiFarmerDuration: 0, commissionReceiver: zeroAddress, tokenVersion: TokenVersion.TOKEN_V3_PERMIT, feeConfigs: [\ {\ feeType: FeeType.MARKETING_OR_VAULT,\ bps: 10000,\ marketingAddress: beneficiaryAddress,\ dividendToken: zeroAddress,\ minimumShareBalance: 0n,\ },\ {\ feeType: FeeType.NONE,\ bps: 0,\ marketingAddress: zeroAddress,\ dividendToken: zeroAddress,\ minimumShareBalance: 0n,\ },\ {\ feeType: FeeType.NONE,\ bps: 0,\ marketingAddress: zeroAddress,\ dividendToken: zeroAddress,\ minimumShareBalance: 0n,\ },\ {\ feeType: FeeType.NONE,\ bps: 0,\ marketingAddress: zeroAddress,\ dividendToken: zeroAddress,\ minimumShareBalance: 0n,\ },\ ], }; const tx = await portal.newTokenV7(params, { value: params.quoteAmt }); Copy /// Find a vanity salt by predicting the CREATE2 address. async function findVanityTokenSalt(suffix: string, tokenImpl: Address, portal: Address) { if (suffix.length !== 4) { throw new Error("Suffix must be exactly 4 characters"); } const predictVanityTokenAddress = (salt: Hex): Address => { const bytecode = "0x3d602d80600a3d3981f3363d3d373d3d3d363d73" + tokenImpl.slice(2).toLowerCase() + "5af43d82803e903d91602b57fd5bf3" as Hex; return getContractAddress({ from: portal, salt: toBytes(salt), bytecode, opcode: "CREATE2", }); }; // you don't need to use privatekey, you can use any random seed as long as it is unique for each attempt. Using a privatekey is just a convenient way to get a random 32-byte value. const seed = generatePrivateKey(); let salt = keccak256(toHex(seed)); let iterations = 0; while (!predictVanityTokenAddress(salt).endsWith(suffix)) { salt = keccak256(salt); iterations++; } return { salt, address: predictVanityTokenAddress(salt), iterations, }; } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {IAccessControlUpgradeable} from "@openzeppelin-contracts-upgradeable/access/IAccessControlUpgradeable.sol"; import {IUniswapV3MintCallback} from "uni-v3-core/interfaces/callback/IUniswapV3MintCallback.sol"; import {IPancakeV3MintCallback} from "pancake-v3-core/interfaces/callback/IPancakeV3MintCallback.sol"; /// @dev Magic address value for `dividendToken` in NewTokenV6Params. /// When set to this address, the dividend token is resolved to the tax token's own address /// after it is created. This is a sugar for devs who do not want to pre-compute the tax token /// address (it is deterministically computed from the salt). /// Uses a well-known non-conflicting sentinel (0xfEED...fEED) to avoid conflict with: /// - address(0): native gas token dividend (only valid when quoteToken is also native gas) /// - any ERC-20 address: use that token as dividend (including quoteToken) address constant MAGIC_DIVIDEND_SELF = address(0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd); /// @title Common Types /// @notice This interface defines common types shared across the portal interface IPortalCommonTypes { /// @dev curve Types enum CurveType { CURVE_LEGACY_15, // r = 15 CURVE_4, // r = 4 CURVE_0_974, // r = 0.974 CURVE_0_5, // r = 0.5 CURVE_1000, // r = 1000 CURVE_20000, // r = 20000 CURVE_2500, // r = 2500 CURVE_500, // r = 500 CURVE_2, // r = 2 CURVE_6, // r = 6 CURVE_75, // r = 75 CURVE_4M, // r= 4 M CURVE_28, // r = 28 CURVE_21_25, // r = 21.25 CURVE_RH_UNUSED, // r = 27.6, h = 352755468 CURVE_RH_28D25_108002126, // r = 28.25, h = 108002126, k = 31301060059.5 CURVE_RH_14981_108002125, // r = 14981, h = 108002125, k = 16598979834625 CURVE_RH_TOSHI_MORPH_2ETH, // r = 0.7672, h = 107036751, k = 849318595.3672 - TOSHI/MORPH 2ETH curve CURVE_RH_TOSHI, // r = 6140351, h = 107036752, k = 6797594227179952 - TOSHI Curve CURVE_RH_BGB, // r = 767.5, h = 107036752, k = 849650707160 - BGB curve CURVE_RH_BNB, // r = 6.14, h = 107036752, k = 6797205657.28 - BNB Curve CURVE_RH_USD, // r = 3837, h = 107036752, k = 4247700017424 - USD curve CURVE_RH_MONAD, // r = 50000, h = 107036752, k = 55351837600000 - MONAD curve CURVE_RH_MONAD_V2, // r = 107400, h = 107036752, k = 118895747164800 - MONAD V2 curve CURVE_RH_KGST // r = 380000, h = 107036752, k = 420673965760000 - KGST curve } /// @dev dex threshold types enum DexThreshType { TWO_THIRDS, // 66.67% supply FOUR_FIFTHS, // 80% supply HALF, // 50% supply _95_PERCENT, // 95% supply _81_PERCENT, // 81% supply _1_PERCENT // 1% supply => mainly for testing } /// @notice Fee profile for tokens /// @dev Determines the fee structure applied to a token's trades and liquidity operations /// Fees are represented in basis points (bps), where 1% = 100 bps enum FlapFeeProfile { FEE_GLOBAL_DEFAULT, // Default fee profile used when no specific profile is set for a token FEE_FLAPSALE_V0, // Fee profile for FlapSale V0 FEE_ZERO // Zero fee profile: no protocol fee charged on trades } // // Custom Errors for Common Types // /// @notice error if the curve type is invalid /// @param curveType The invalid curve type error InvalidCurveType(CurveType curveType); /// @notice error if the dex threshold type is invalid error InvalidDexThresholdType(DexThreshType threshold); } /// @title Types and Structs /// @notice This interface defines the types and structs used in the portal interface IPortalTypes is IPortalCommonTypes { // // public constants // // // Types and Structs // /// @dev Profile types for deployment-specific parameters or behaviors enum Profile { DEFAULT, TOSHI_MART, X_LAYER, MORPH, MONAD } /// @dev Token version /// Which token implementation is used enum TokenVersion { TOKEN_LEGACY_MINT_NO_PERMIT, TOKEN_LEGACY_MINT_NO_PERMIT_DUPLICATE, // for historical reasons, both 0 and 1 are the same: TOKEN_LEGACY_MINT_NO_PERMIT TOKEN_V2_PERMIT, // 2 TOKEN_GOPLUS, // 3 TOKEN_TAXED, // 4: The original tax token (FlapTaxToken) TOKEN_TAXED_V2, // 5: The new advanced tax token (FlapTaxTokenV2) TOKEN_TAXED_V3, // 6: The next-generation tax token with asymmetric buy/sell rates (FlapTaxTokenV3) TOKEN_V3_PERMIT // 7: Non-tax token using TokenV3 implementation with permit support } /// @dev the quote token, i.e, the token as the reserve enum QuoteTokenType { NATIVE_GAS_TOKEN, // The native gas token ERC20_TOKEN_WITH_PERMIT, // The ERC20 token with permit ERC20_TOKEN_WITHOUT_PERMIT // The ERC20 token without permit } /// @notice the status of a token /// The token has 5 statuses: // - Tradable: The token can be traded(buy/sell) // - InDuel: (obsolete) The token is in a battle, it can only be bought but not sold. // - Killed: (obsolete) The token is killed, it can not be traded anymore. Can only be redeemed for another token. // - DEX: The token has been added to the DEX // - Staged: The token is staged but not yet created (address is predetermined) enum TokenStatus { Invalid, // The token does not exist Tradable, InDuel, // obsolete Killed, // obsolete DEX, Staged // The token is staged (address determined, but not yet created) } /// @notice the migrator type /// @dev the migrator type determines how the liquidity is added to the DEX. /// Note: To mitigate the risk of DOS, if a V3 migrator is used but the liquidity cannot /// be added to v3 pools, the migrator will fallback to a V2 migrator. /// A TAX token must use a V2 migrator. enum MigratorType { V3_MIGRATOR, // Migrate the liquidity to a Uniswap V3 like pool V2_MIGRATOR, // Migrate the liquidity to a Uniswap V2 like pool V4_UNI_MIGRATOR, // Migrate the liquidity to a Uniswap V4 pool (Base, XLayer) PCS_INFINITY_CL_MIGRATOR // Migrate the liquidity to a Pancake Infinity CL Pool (BNB) } /// @notice the V3 LP fee profile /// @dev determines the LP fee tier to use when migrating tokens to Uniswap V3 or Pancake V3 enum V3LPFeeProfile { LP_FEE_PROFILE_STANDARD, // Standard fee tier: 0.25% on PancakeSwap, 0.3% on Uniswap LP_FEE_PROFILE_LOW, // Low fee tier: typically, 0.01% on PancakeSwap, 0.05% on Uniswap LP_FEE_PROFILE_HIGH // High fee tier (1% for exotic pairs) } /// @notice the DEX ID /// @dev determines the DEX we want to migrate to /// On BSC: /// - only DEX0 will be enabled, which is PancakeSwap /// On xLayer: /// - only DEX0 will be enabled, which is PotatoSwap /// On Monad: /// - DEX0 is Uniswap /// - DEX1 is PancakeSwap /// - DEX2 is Monday /// Note that, currently, we only support at most 3 DEXes /// We may add more DEXes in the future if needed enum DEXId { DEX0, DEX1, DEX2 } /// @notice the state of a token (with dex related fields) struct TokenStateV2 { TokenStatus status; // the status of the token uint256 reserve; // the reserve of the token uint256 circulatingSupply; // the circulatingSupply of the token uint256 price; // the price of the token TokenVersion tokenVersion; // the version of the token implementation this token is using uint256 r; // the r of the curve of the token uint256 dexSupplyThresh; // the cirtulating supply threshold for adding the token to the DEX } /// @notice the state of a token (with all V2 fields plus quoteTokenAddress and nativeToQuoteSwapEnabled) struct TokenStateV3 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; } /// @notice the state of a token (with all V3 fields plus extensionID and 'r' curve parameter only) struct TokenStateV4 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; } /// @notice the state of a token (with all V4 fields plus all curve parameters) struct TokenStateV5 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; } /// @notice the state of a token (with all V5 fields plus taxRate, pool, and progress) struct TokenStateV6 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The tax rate in basis points (0 if not a tax token) uint256 taxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; } /// @notice the state of a token (with all V6 fields plus lpFeeProfile) struct TokenStateV7 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The tax rate in basis points (0 if not a tax token) uint256 taxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; /// The V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; /// The Dex Id DEXId dexId; } struct TokenStateV8 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The buy tax rate in basis points (0 if not a tax token) uint256 buyTaxRate; /// The sell tax rate in basis points (0 if not a tax token) uint256 sellTaxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; /// The V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; /// The Dex Id DEXId dexId; } /// @notice A forward-compatible version of TokenStateV8, where enum-typed fields are returned /// as uint8 instead of their Solidity enum types. /// @dev Safe because Solidity revert when decoding an enum value that exceeds the enum's /// declared maximum (e.g. a new TOKEN_TAXED_V3 value read by a contract compiled against /// an old TokenVersion enum). By using uint8 for TokenStatus, TokenVersion, /// V3LPFeeProfile, and DEXId, callers are not affected when new variants are added to /// any of those enums in the future. Use getTokenV8 if you are always up-to-date with /// the latest interface; use this method if you need forward/backward compatibility. struct TokenStateV8Safe { /// The status of the token (see TokenStatus enum for interpretation) uint8 status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum for interpretation) uint8 tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The buy tax rate in basis points (0 if not a tax token) uint256 buyTaxRate; /// The sell tax rate in basis points (0 if not a tax token) uint256 sellTaxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; /// The V3 LP fee profile for the token (see V3LPFeeProfile enum for interpretation) uint8 lpFeeProfile; /// The Dex Id (see DEXId enum for interpretation) uint8 dexId; } /// @notice Parameters for creating a new token (V2) struct NewTokenV2Params { /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The tax rate in basis points (if non-zero, this is a tax token) uint16 taxRate; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token /// For rev share tokens, this is the address that can claim the LP fees /// For tax tokens, this is the address that receives the tax fees address beneficiary; /// The optional permit data for the quote token bytes permitData; } /// @notice Parameters for creating a new token (V3) with extension support struct NewTokenV3Params { /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The tax rate in basis points (if non-zero, this is a tax token) uint16 taxRate; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token /// For rev share tokens, this is the address that can claim the LP fees /// For tax tokens, this is the address that receives the tax fees address beneficiary; /// The optional permit data for the quote token bytes permitData; /// @notice The ID of the extension to be used for the new token if not zero bytes32 extensionID; /// @notice Additional extension specific data to be passed to the extension's `onTokenCreation` method, check the extension's documentation for details on the expected format and content. bytes extensionData; } /// @notice Parameters for creating a new token (V4) with DEX ID and LP fee profile support struct NewTokenV4Params { /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The tax rate in basis points (if non-zero, this is a tax token) uint16 taxRate; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token /// For rev share tokens, this is the address that can claim the LP fees /// For tax tokens, this is the address that receives the tax fees address beneficiary; /// The optional permit data for the quote token bytes permitData; /// @notice The ID of the extension to be used for the new token if not zero bytes32 extensionID; /// @notice Additional extension specific data to be passed to the extension's `onTokenCreation` method, check the extension's documentation for details on the expected format and content. bytes extensionData; /// @notice The preferred DEX ID for the token DEXId dexId; /// @notice The preferred V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; } /// @notice Parameters for creating a new token (V5) with tax V2 support struct NewTokenV5Params { /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The tax rate in basis points (if non-zero, this is a tax token) uint16 taxRate; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token /// For rev share tokens, this is the address that can claim the LP fees /// For tax tokens, this is the address that receives the tax fees address beneficiary; /// The optional permit data for the quote token bytes permitData; /// @notice The ID of the extension to be used for the new token if not zero bytes32 extensionID; /// @notice Additional extension specific data to be passed to the extension's `onTokenCreation` method, check the extension's documentation for details on the expected format and content. bytes extensionData; /// @notice The preferred DEX ID for the token DEXId dexId; /// @notice The preferred V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; // New V5 tax-specific fields (only used when taxRate > 0) /// Tax duration in seconds (max: 100 years) uint64 taxDuration; /// Anti-farmer duration in seconds (max: 1 year) uint64 antiFarmerDuration; /// Market allocation basis points (to beneficiary) uint16 mktBps; /// Deflation basis points (burned) uint16 deflationBps; /// Dividend basis points (to dividend contract) uint16 dividendBps; /// Liquidity provision basis points (LP to dead address) uint16 lpBps; /// Minimum balance for dividend eligibility (min: 10K ether, required when dividendBps > 0) uint256 minimumShareBalance; } /// @dev Magic address value for `dividendToken` in NewTokenV6Params. /// When set to MAGIC_DIVIDEND_SELF, the dividend token is resolved to the tax token's own address /// after it is created. See MAGIC_DIVIDEND_SELF file-level constant. /// @notice Parameters for creating a new token (V6) with asymmetric tax and custom dividend token struct NewTokenV6Params { // --- Same base fields as V5 --- /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token address beneficiary; /// The optional permit data for the quote token bytes permitData; /// The ID of the extension to be used for the new token if not zero bytes32 extensionID; /// Additional extension specific data bytes extensionData; /// The preferred DEX ID for the token DEXId dexId; /// The preferred V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; // --- Tax fields (all zero = non-tax token, behaves like newTokenV5) --- /// Buy tax rate in basis points (0 = no buy tax) uint16 buyTaxRate; /// Sell tax rate in basis points (0 = no sell tax) uint16 sellTaxRate; /// Tax duration in seconds uint64 taxDuration; /// Anti-farmer duration in seconds uint64 antiFarmerDuration; /// Market allocation basis points (to beneficiary) uint16 mktBps; /// Deflation basis points (burned) uint16 deflationBps; /// Dividend basis points (to dividend contract) uint16 dividendBps; /// Liquidity provision basis points (LP to dead address) uint16 lpBps; /// Minimum balance for dividend eligibility (required when dividendBps > 0) uint256 minimumShareBalance; // --- New V3-only fields --- /// @notice Dividend distribution token: /// address(0) = native gas token dividend (only valid when quoteToken is also address(0)) /// MAGIC_DIVIDEND_SELF = distribute the tax token itself as dividend /// quoteToken address = explicitly use the quote token as dividend (must be set explicitly) /// any other ERC-20 = use that specific ERC-20 as dividend token address dividendToken; /// @notice Commission receiver address (zero = disabled). /// MUST be address(0) for non-tax tokens (buyTaxRate == 0 && sellTaxRate == 0). /// commissionBps is NOT provided here — it is calculated internally by the launcher /// based on the effective tax rate via _commissionForTax(). address commissionReceiver; /// @notice The token version to create. Determines which launcher path is used. /// TOKEN_V2_PERMIT — non-tax token (standard ERC-20 with permit) /// TOKEN_TAXED — V1 tax token (symmetric rates, mktBps=10000, no commission) /// TOKEN_TAXED_V2 — V2 tax token (asymmetric rates, flexible distribution, no commission, mktBps != 10000) /// TOKEN_TAXED_V3 — V3 tax token (asymmetric rates, flexible distribution, commission supported) TokenVersion tokenVersion; } // NOTE: `converter` is NOT in this struct — it is provided as an immutable // in PortalBase and automatically passed to TaxProcessor at initialization. // ─── V7 Fee Configuration ──────────────────────────────────────────────── /// @notice Fee type for token V7 /// @dev Not only tax fees but also LP fees and other fee types enum FeeType { NONE, // Not used / disabled MARKETING_OR_VAULT, // Fee goes to marketing address or vault DIVIDEND, // Fee distributed as dividends DEFLATION, // Fee is burned LP_BPS // Fee added to LP } /// @notice Fee configuration for a single fee slot /// @dev Not only tax fees but also possibly the LP fee. /// Only the fields relevant to the feeType need to be populated: /// MARKETING_OR_VAULT → marketingAddress + bps /// DIVIDEND → dividendToken + minimumShareBalance + bps /// DEFLATION → bps /// LP_BPS → bps /// NONE → all fields ignored struct FeeConfig { FeeType feeType; // The type of fee uint16 bps; // Basis points for this fee (0-10000) address marketingAddress; // Only used for MARKETING_OR_VAULT type address dividendToken; // Only used for DIVIDEND type (the token to distribute) uint256 minimumShareBalance; // Only used for DIVIDEND type (min balance for eligibility) } /// @notice Parameters for launching a V7 token (V4/PCS Infinity migration support) /// @dev Flat struct — all fields from V6 are duplicated here for clarity and forward-compatibility. /// If a V8 is needed, copy this struct and append new fields (same pattern). /// /// Fee distribution is fully described by feeConfigs[4]. Each slot is independent and /// can hold any FeeType (NONE, MARKETING_OR_VAULT, DIVIDEND, DEFLATION, LP_BPS). /// All non-NONE slots' bps MUST sum to exactly 10000. /// The first MARKETING_OR_VAULT entry's marketingAddress is used as the primary beneficiary /// for LP-fee claiming purposes. Up to 4 MARKETING_OR_VAULT slots are supported. struct NewTokenV7Params { // ── Base fields (same as NewTokenV6Params) ────────────────── /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The optional permit data for the quote token bytes permitData; /// The ID of the extension to be used for the new token if not zero bytes32 extensionID; /// Additional extension specific data bytes extensionData; /// The preferred DEX ID for the token DEXId dexId; // ── Tax fields (all zero = non-tax token) ─────────────────── /// Buy tax rate in basis points (0 = no buy tax) uint16 buyTaxRate; /// Sell tax rate in basis points (0 = no sell tax) uint16 sellTaxRate; /// Tax duration in seconds uint64 taxDuration; /// Anti-farmer duration in seconds uint64 antiFarmerDuration; /// @notice Commission receiver address (zero = disabled). /// @dev Only meaningful for TOKEN_TAXED_V3. /// MUST be address(0) for TOKEN_V3_PERMIT (non-tax tokens do not support commission). /// commissionBps is NOT provided here — it is auto-computed internally by the launcher /// based on the effective tax rate via _commissionForTax(). address commissionReceiver; /// @notice The token version to create (see TokenVersion enum) TokenVersion tokenVersion; /// @notice Fee distribution configuration (4 slots, must sum to 10000 bps). /// @dev Not only tax fees but also possibly the LP fee. FeeConfig[4] feeConfigs; } /// @notice Parameters for staging a new token (V5) - immutable parameters only struct StageNewTokenV5Params { /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// Whether this is a tax token bool isTaxToken; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// @notice The preferred DEX ID for the token DEXId dexId; } /// @notice Parameters for committing a staged token (V5) - mutable/deployment-time parameters struct CommitNewTokenV5Params { /// The salt for deterministic deployment (must match staged salt) bytes32 salt; /// The tax rate in basis points (0 for non-tax tokens) uint16 taxRate; /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token /// For rev share tokens, this is the address that can claim the LP fees /// For tax tokens, this is the address that receives the tax fees address beneficiary; /// The optional permit data for the quote token bytes permitData; // New V5 tax-specific fields (only used when taxRate > 0) /// Tax duration in seconds (max: 100 years) uint64 taxDuration; /// Anti-farmer duration in seconds (max: 1 year) uint64 antiFarmerDuration; /// Market allocation basis points (to beneficiary) uint16 mktBps; /// Deflation basis points (burned) uint16 deflationBps; /// Dividend basis points (to dividend contract) uint16 dividendBps; /// Liquidity provision basis points (LP to dead address) uint16 lpBps; /// Minimum balance for dividend eligibility (required when dividendBps > 0) uint256 minimumShareBalance; } /// @dev The configuration of the "native to quote" swap /// i.e How to swap ETH for the quote token when the quote token is not ETH enum NativeToQuoteSwapType { SWAP_DISABLED, // 0: disabled SWAP_VIA_V2_POOL, // 1: swap through v2 pool SWAP_VIA_V3_2500_POOL, // 2: swap through v3 2500 pool SWAP_VIA_V3_500_POOL, // 3: swap through v3 500 pool SWAP_VIA_V3_3000_POOL, // 4: swap through v3 3000 pool SWAP_VIA_V3_10000_POOL, // 5: swap through v3 10000 pool SWAP_VIA_MIXED_ROUTER // 6: multi-hop via PancakeSwap Infinity MixedQuoter + UniversalRouter (BSC only) // used for tokens like uUSD that route BNB ↔ USDT(V3) ↔ uUSD(BinPool). // The actual routing logic is bypassed in _shouldUseMixedRouter() before // the enum is checked, so this value serves as a meaningful marker when // calling setQuoteTokenConfiguration — any non-SWAP_DISABLED value would // work, but this makes intent explicit. } /// @dev the quote token configurations struct QuoteTokenConfiguration { uint8 enabled; // 8bit: 1 if allowed, 0 if not allowed CurveType defaultCurve; // 8bit: the default token curve type of the quote token CurveType alternativeCurve; // 8bit: the alternative token curve type of the quote token NativeToQuoteSwapType nativeToQuoteSwapType; // 8bit: the native to quote swap feature configuration of the quote token uint8 dexId; // 8bit: DEX ID for multiple DEXes support } /// @dev Enum for DEX pool types enum PoolType { V2, // Uniswap V2 style pools V3, // Uniswap V3 style pools V4, // Uniswap V4 style pools PCS_INFINITY_CL // PancakeSwap Infinity Concentrated Liquidity pools } /// @dev Packed DEX pool information struct PackedDexPool { address pool; // 160 bits: pool address uint24 fee; // 24 bits: fee tier (for V3), 0 for V2 PoolType poolType; // 8 bits: enum for pool type uint24 tickSpacing; // 24 bits: tick spacing (for V4/PCS), 0 for V2/V3 uint40 unused; // 40 bits: reserved for future use } /// @notice Records who locked a CREATE2 salt and for which token version. /// locker + tokenVersion + isUsed pack into a single 32-byte storage slot. struct SaltLockEntry { address locker; // 20 bytes — address that paid to reserve this salt uint8 tokenVersion; // 1 byte — TokenVersion enum value at lock time bool isUsed; // 1 byte — true once the salt has been consumed by a token launch // locker + tokenVersion + isUsed pack into a single 32-byte slot } // // Events // /// @notice emitted when a token is staged (but not yet created) /// /// @param ts The timestamp of the event /// @param creator The address of the creator /// @param token The predetermined address of the token event FlapTokenStaged(uint256 ts, address creator, address token); /// @notice emitted when a new token is created /// /// @param ts The timestamp of the event /// @param creator The address of the creator /// @param nonce The nonce of the token /// @param token The address of the token /// @param name The name of the token /// @param symbol The symbol of the token /// @param meta The meta URI of the token event TokenCreated( uint256 ts, address creator, uint256 nonce, address token, string name, string symbol, string meta ); /// @notice emitted when a token is bought /// /// @param ts The timestamp of the event /// @param token The address of the token /// @param buyer The address of the buyer /// @param amount The amount of tokens bought /// @param eth The amount of ETH spent /// @param fee The amount of ETH spent on fee /// @param postPrice The price of the token after this trade event TokenBought( uint256 ts, address token, address buyer, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice ); /// @notice emitted when a token is sold /// /// @param ts The timestamp of the event /// @param token The address of the token /// @param seller The address of the seller /// @param amount The amount of tokens sold /// @param eth The amount of ETH received /// @param fee The amount of ETH deducted as a fee /// @param postPrice The price of the token after this trade event TokenSold( uint256 ts, address token, address seller, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice ); /// emitted when a token's curve is set /// @param token The address of the token /// @param curve The address of the curve /// @param curveParameter The parameter of the curve event TokenCurveSet(address token, address curve, uint256 curveParameter); /// @notice emitted when a token's curve parameters are set (V2) /// @param token The address of the token /// @param r The virtual ETH reserve parameter /// @param h The virtual token reserve parameter /// @param k The square of the virtual Liquidity parameter event TokenCurveSetV2(address token, uint256 r, uint256 h, uint256 k); /// emitted when a token's dexSupplyThresh is set /// @param token The address of the token /// @param dexSupplyThresh The new dexSupplyThresh of the token event TokenDexSupplyThreshSet(address token, uint256 dexSupplyThresh); /// emitted when a token's implementation is set /// @param token The address of the token /// @param version The version of the token event TokenVersionSet(address token, TokenVersion version); /// @notice emitted when a new vanity token is created /// @param token The address of the created token /// @param creator The address of the creator /// @param beneficiary The address of the beneficiary event VanityTokenCreated(address token, address creator, address beneficiary); /// @notice emitted when a token's quote token is set /// @param token The address of the token /// @param quoteToken The address of the quote token event TokenQuoteSet(address token, address quoteToken); /// @notice emitted when a token's migrator is set /// @param token The address of the token /// @param migratorType The migrator type event TokenMigratorSet(address token, MigratorType migratorType); /// @notice emitted when a token's extension is enabled /// @param token The address of the token /// @param extensionID The extension ID /// @param extensionAddress The address of the extension contract /// @param version The version of the extension event TokenExtensionEnabled(address token, bytes32 extensionID, address extensionAddress, uint8 version); /// @notice emitted when a token's pool info is updated /// @param token The address of the token /// @param poolInfo The new pool information event TokenPoolInfoUpdated(address token, PackedDexPool poolInfo); /// @notice emitted when a trader's fee exemption status is updated /// @param trader The address of the trader /// @param isExempted Whether the trader is exempted from fees event FeeExemptionUpdated(address indexed trader, bool isExempted); // // events // /// @notice emitted when token is redeemed /// @param ts The timestamp of the event /// @param srcToken The address of the token to redeem /// @param dstToken The address of the token to receive /// @param srcAmount The amount of srcToken to redeem /// @param dstAmount The amount of dstToken to receive /// @param who The address of the redeemer event TokenRedeemed( uint256 ts, address srcToken, address dstToken, uint256 srcAmount, uint256 dstAmount, address who ); /// @notice emitted when the bit flags are changed /// @param oldFlags The old flags /// @param newFlags The new flags event BitFlagsChanged(uint256 oldFlags, uint256 newFlags); /// @notice emitted when adding liquidity to DEX /// @param token The address of the token /// @param pool The address of the pool /// @param amount The amount of token added /// @param eth The amount of quote Token added event LaunchedToDEX(address token, address pool, uint256 amount, uint256 eth); /// @notice Emitted when V4/PCS LP fees are collected and forwarded event V4LPFeesCollected(address indexed token, uint256 quoteAmount, uint256 tokenAmount); /// @notice emitted when a new CL pool is created for a token /// @param token The address of the token /// @param poolId The ID of the created CL pool /// @param sqrtPriceX96 The initial sqrt price used to initialize the CL pool event FlapTokenCLPoolCreated(address token, bytes32 poolId, uint160 sqrtPriceX96); /// @notice emitted when the progress of a token changes /// @param token The address of the token /// @param newProgress The new progress value in Wad event FlapTokenProgressChanged(address token, uint256 newProgress); // // Token V2 supply change // /// @notice emitted when the circulating supply of a token changes /// @param token The address of the token /// @param newSupply The new circulating supply event FlapTokenCirculatingSupplyChanged(address token, uint256 newSupply); /// @notice emitted when a new tax is set for a token /// @dev For V3 tokens with asymmetric rates, this carries max(buyTax, sellTax) for backward compatibility. /// Listen for FlapTokenAsymmetricTaxSet to get the full buy/sell split. /// @param token The address of the token /// @param tax The tax value set for the token (max of buy and sell rates for V3 tokens) event FlapTokenTaxSet(address token, uint256 tax); /// @notice Emitted when a token is launched with asymmetric buy/sell tax rates (V3 tokens only). /// @dev Emitted in addition to FlapTokenTaxSet (which carries max(buyTax, sellTax) for backward /// compatibility). New systems that support asymmetric rates should listen to this event. /// Old systems that only read FlapTokenTaxSet will continue to work correctly. /// @param token The address of the token /// @param buyTax The buy tax rate in basis points /// @param sellTax The sell tax rate in basis points event FlapTokenAsymmetricTaxSet(address token, uint256 buyTax, uint256 sellTax); // operation related // should remove later /// @notice emitted when a users successfully checked in /// @param user The address of the user event CheckedIn(address user); /// @notice emitted when a beneficiary claims fees /// @param token The address of the token /// @param beneficiary The address of the beneficiary /// @param tokenAmount The amount of the token claimed /// @param ethAmount The amount of ETH claimed event BeneficiaryClaimed(address token, address beneficiary, uint256 tokenAmount, uint256 ethAmount); /// @notice emitted when a token beneficiary is changed /// @param token The address of the token /// @param oldBeneficiary The previous beneficiary address /// @param newBeneficiary The new beneficiary address event BeneficiaryChanged(address token, address oldBeneficiary, address newBeneficiary); /// @notice emitted when an extension is registered /// @param extensionId The unique identifier for the extension /// @param extensionAddress The address of the extension contract /// @param version The version of the extension event ExtensionRegistered(bytes32 extensionId, address extensionAddress, uint8 version); /// @notice emitted when a spammer's blocked status is changed /// @param spammer The address of the spammer /// @param blocked True if blocked, false if unblocked event SpammerBlockedStatusChanged(address spammer, bool blocked); /// @notice emitted when a user is rate limited from creating tokens /// @param user The address of the rate-limited user /// @param lastCreationTime The timestamp of their last successful token creation event RateLimited(address user, uint256 lastCreationTime); /// @notice emitted when a quote token configuration is set /// @param quoteToken The address of the quote token /// @param config The configuration set for the quote token event QuoteTokenConfigurationSet(address quoteToken, QuoteTokenConfiguration config); /// @notice emitted when a V3 favored fee is set for a quote token /// @param quoteToken The address of the quote token /// @param favoredFee The favored fee set for the quote token event V3FavoredFeeSet(address quoteToken, uint24 favoredFee); /// @notice emitted when a token's DEX preference is set /// @param token The address of the token /// @param dexId The preferred DEX ID for the token /// @param lpFeeProfile The preferred V3 LP fee profile for the token event TokenDexPreferenceSet(address token, DEXId dexId, V3LPFeeProfile lpFeeProfile); /// @notice emitted when a message is sent /// @param sender The address of the sender /// @param token The address of the token /// @param message The message sent event MsgSent(address sender, address token, string message); // // Custom Errors // /// @notice error if the dex is both pancake and algebra1.9 /// which is impossible error DEXCannotBeBothPancakeAndAlgebra1_9(); /// @notice error if the portal lens address is zero error PortalLensCannotBeZero(); /// @notice error if the portal lens v2 address is zero error PortalLensV2CannotBeZero(); /// @notice error if the multi dex router address is zero error MultiDexRouterCannotBeZero(); /// @notice error if the token does not exist error TokenNotFound(address token); /// @notice error if the amount is too small error AmountTooSmall(uint256 amount); /// @notice error if slippage is too high /// i.e: actualAmount < minAmount error SlippageTooHigh(uint256 actualAmount, uint256 minAmount); /// @notice error if the input token & output token of a swap is the same error SameToken(address tokenA); /// @notice error if trying to trade a killed token error TokenKilled(address token); /// @notice error if token is not tradable error TokenNotTradable(address token); /// @notice error if trying to sell a token that is in a battle error TokenInDuel(address token); /// @notice error if trying to redeem a token that is not killed error TokenNotKilled(address token); /// @notice error if the token has already been added to the DEX error TokenAlreadyDEXed(address token); /// @notice error if the token has already been staged or created error TokenAlreadyStaged(address token); /// @notice error if the token is not in staged status error TokenNotStaged(address token); /// @notice error if the token is not listed on DEX yet error TokenNotDEXed(address token); /// @notice error if there is no conversion path from srcToken to dstToken error NoConversionPath(address srcToken, address dstToken); /// @notice error if the round is not found error RoundNotFound(uint256 id); /// @notice error if the round id is invalid error InvalidRoundID(uint256 id); /// @notice error if try to start a new round but the last round is not resolved error LastRoundNotResolved(); /// @notice cannot use a token for the next round of the game error InvalidTokenForBattle(address token); /// @notice error if the signature is invalid error InvalidSigner(address signer); /// @notice error if the seq is not found in Game queue error SeqNotFound(uint256 seq); /// @notice error if not implemented yet error NotImplemented(); /// @notice error a token is already in the game error TokenAlreadyInGame(address token); /// @notice error if a call reverted but without any data error CallReverted(); /// @notice error if creating token is disabled error PermissionlessCreateDisabled(); /// @notice error if trading is disabled error TradeDisabled(); /// @notice error if the circuit breakers are off error ProtocolDisabled(); /// @notice error if the game supply threshold is not valid error InvalidGameSupplyThreshold(); /// @notice error if the dex supply threshold is not valid error InvalidDEXSupplyThreshold(); /// @notice error if the proof does not match the msg.sender error MismatchedAddressInProof(address expected, address actual); /// @notice error if the whitlist creator cannot create more tokens error NoQuotaForCreator(uint256 created, uint256 max); /// @notice error if the piggyback lenght is not valid error InvalidPiggybackLength(uint256 expected, uint256 actual); /// @notice error if the tax processor is not set for UniV4 related operations error TaxProcessorUniV4Required(); /// @notice error if the feature is disabled error FeatureDisabled(); /// @notice error if caller is not authorized (e.g., only SaleForge can call) error OnlySaleForge(); /// @notice error if the quote token is not allowed error QuoteTokenNotAllowed(address quoteToken); /// @notice error if a native to quote swap is required but not supported /// native to quote swap: i.e, swap the input token to the desired quote token error NativeToQuoteSwapNotSupported(); /// @notice error if the native to quote swap v3 fee type is not supported /// @param NativeToQuoteSwapType The unsupported native to quote swap type error NativeToQuoteSwapFeeTierNotSupported(uint8 NativeToQuoteSwapType); /// @notice error if met any dirty bits error DirtyBits(); // // Dex Related // /// @notice error if sqrPriceA is gte than sqrtPriceB error PriceAMustLTPriceB(uint160 sqrtPriceA, uint160 sqrtPriceB); /// @notice error if the actual amount is more than the expected amount error ActualAmountMustLTEAmount(uint256 actualAmount, uint256 amount1); /// @notice error if the msg.sender is not a Uniswap V3 pool error NotUniswapV3Pool(address sender); /// @notice error if the uniswap v2 pool's liquidity is not zero error UniswapV2PoolNotZero(address pool, uint256 liquidity); /// @notice error if the required token amount for adding Uniswap v2 liquidity is more than the remaining token error RequiredTokenMustLTE(uint256 requiredToken, uint256 reserveToken); /// @notice revert when calling slot0 of a Uniswap V3 pool failed error UniswapV3Slot0Failed(); /// @notice error if a non-position NFT is received error NonPositionNFTReceived(address collection); /// @notice error if the provided dex threshold is invalid error InvalidDexThreshold(uint256 threshold); /// @notice error if the provided address is not a valid pool error InvalidPoolAddress(address pool, address expected); // // staking related // /// @notice error if the locks are invalid error InvalidLocks(); /// @notice error if staking feature is not enabled error StakingDisabled(); /// @notice error if the operator does not have the roller role error NotRoller(); // operation related /// @notice error if the user cannot check in yet /// @param next The timestamp when the user can check in again error cannotCheckInUntil(uint256 next); // misc /// @notice error if another token has the same meta error MetaAlreadyUsedByOtherToken(string meta); /// @notice error if the creation fee is insufficient error InsufficientCreationFee(uint256 required, uint256 provided); /// @notice error if the provided ETH is insufficient to cover the required fee /// @param required The required fee amount /// @param provided The provided ETH amount error InsufficientFee(uint256 required, uint256 provided); /// @notice error if the vanity address requirement is not met /// @param token The generated token address error VanityAddressRequirementNotMet(address token); /// @notice error if the token is not in DEX status error TokenNotInDEXStatus(address token); /// @notice error if the caller is not the token's beneficiary error CallerNotBeneficiary(address caller, address expected); /// @notice error if no locks are available for the token error NoLocksAvailable(address token); /// @notice error if the provided ETH is insufficient to cover the required input amount /// @param provided The provided ETH amount /// @param required The required ETH amount error InsufficientEth(uint256 provided, uint256 required); /// @notice error if the provided tax bps is invalid /// @param tax The provided tax bps error InvalidTaxBps(uint256 tax); /// @notice error if the transferFrom call failed /// @param token The address of the token /// @param from The address from which the tokens were to be transferred /// @param amount The amount of tokens that were to be transferred error TransferFromFailed(address token, address from, uint256 amount); /// @notice error if the migrator type is invalid error InvalidMigratorType(); /// @notice error if the quote token is not native but not using PortalTradeV2 error QuoteTokenNotNativeButNotUsingTradeV2(); /// @notice error if the msg.value is less than expected value when creating /// a tax token using an ERC20 (e.g: USDC) token as the quote token. /// @dev For the tax token's tax splitter to work properly, we need approximately 1gwei due to our /// implemenation of the tax splitter. error InsufficientValueForTaxTokenCreation(uint256 expected, uint256 provided); /// @notice error if the caller is not a guardian or admin /// @param caller The address of the caller error NotGuardian(address caller); /// @notice error if the extension version is not supported /// @param version The unsupported extension version error UnsupportedExtensionVersion(uint8 version); /// @notice error if the token uses an extension but is traded through the legacy PortalTrade contract /// @param token The address of the token with an extension error TokenWithExtensionNotSupported(address token); /// @notice error if the parameters for ToshiMart are invalid error InvalidParamsForToshiMart(); /// @notice error if the parameters for X_LAYER are invalid error InvalidParamsForXLayer(); /// @notice error if the quote token configuration is invalid error InvalidQuoteTokenConfiguration(); /// @notice error when trying to use PortalTrade with tokens that have non-zero h parameter error ErrShouldUsePortalTradeV2(); /// @notice error when no supported DEX is found for the token error NoSupportedDEX(); /// @notice invalid fee tier for DEX error InvalidFeeTierForDEX(); /// @notice error if the tax distribution percentages don't add up to 100% error InvalidTaxDistribution(); /// @notice error if tax duration exceeds maximum allowed error TaxDurationTooLong(); /// @notice error if tax duration is less than minimum allowed error TaxDurationTooShort(); /// @notice error if anti-farmer duration exceeds maximum allowed error AntiFarmerDurationTooLong(); /// @notice error when the swap from quoteToken to dividendToken is not supported by the SwapRegistry error DividendSwapNotSupported(address quoteToken, address dividendToken); /// @notice error if minimum share balance is too low for dividend eligibility error MinimumShareBalanceTooLow(); /// @notice error when dividend parameters are missing but required error DividendParametersRequired(); /// @notice error when `dividendBps > 0` is configured together with a dividend token that is /// not the quote token. Emitted by `launchTaxTokenV3` while live quote→custom-token /// dividend conversion is not yet enabled. Custom dividend tokens remain permitted /// when `dividendBps == 0` (tracker-only mode). error DividendTokenMustEqualQuoteToken(); /// @notice error when a non-NONE FeeConfig slot has bps == 0 error InvalidFeeConfigBps(); /// @notice error when a FeeConfig slot type appears more than once in the feeConfigs array /// @param feeType The uint8 value of the duplicated FeeType enum entry error DuplicateFeeSlot(uint8 feeType); /// @notice error when a MARKETING_OR_VAULT fee slot has a zero marketing/vault address error ZeroMarketingAddress(); /// @notice error when a newTokenV7 request violates the currently supported launch rules. /// @dev Detailed code mapping is documented in `src/PortalTokenLauncher.sol`. error NewTokenV7RuleViolation(uint8 code); /// @notice error when TOKEN_V3_PERMIT via newTokenV7 is configured with a fee type /// that is not enabled for the initial V7 release. /// @param feeType The unsupported FeeType enum value. error UnsupportedV7TokenV3PermitFeeType(uint8 feeType); /// @notice error when user is rate limited from creating tokens /// @param user The address that is rate limited /// @param lastCreationTime The timestamp of the user's last successful token creation error RateLimitExceeded(address user, uint256 lastCreationTime); /// @notice error when user is permanently blocked from creating tokens /// @param user The address that is blocked error SpammerBlocked(address user); // --- Salt Locking --- /// @notice Emitted when a salt is locked by a user. /// @param locker The address that paid to lock the salt. /// @param salt The CREATE2 salt that was locked. /// @param tokenAddress The predicted token address derived from salt + tokenVersion. /// @param tokenVersion The TokenVersion enum value the lock applies to. /// @param ts Block timestamp of the lock. event FlapSaltLocked(address locker, bytes32 salt, address tokenAddress, uint8 tokenVersion, uint256 ts); /// @notice Emitted when a locked salt is consumed by a successful launch. /// @param locker The address that originally locked the salt. /// @param salt The CREATE2 salt that was used. /// @param tokenAddress The token address that was launched. /// @param tokenVersion The TokenVersion enum value that was locked. /// @param ts Block timestamp of the launch. event FlapSaltUsed(address locker, bytes32 salt, address tokenAddress, uint8 tokenVersion, uint256 ts); /// @notice msg.value does not equal the required SALT_LOCK_FEE. error SaltLockFeeMismatch(uint256 required, uint256 provided); /// @notice The salt is already locked by a different address. error SaltAlreadyLockedByAnotherUser(bytes32 salt, address existingLocker); /// @notice The caller tried to lock a salt they already hold the lock for. error SaltAlreadyLockedBySelf(bytes32 salt); /// @notice Launch was rejected because the salt is locked by someone other than the caller. error FlapSaltLockedByAnotherUser(bytes32 salt, address locker); /// @notice Launch was rejected because the locked tokenVersion does not match the one being launched. error SaltLockTokenVersionMismatch(bytes32 salt, TokenVersion locked, TokenVersion provided); /// @notice The tokenVersion passed to lockSalt() is not currently supported. error UnsupportedTokenVersion(TokenVersion provided); } /// @notice Callback interface implemented by VaultPortal (or any trusted caller). /// Portal calls getSaltOwner() when msg.sender == VAULT_PORTAL to resolve the /// effective user for salt-lock enforcement without changing any parameter structs. interface ISaltOwnerProvider { /// @notice Return the address that should be treated as the salt owner for the current launch. /// @dev VaultPortal must set _pendingSaltOwner before delegating into Portal. /// The call is a staticcall so it cannot modify state. function getSaltOwner() external view returns (address); } /// @title IPortalLauncherTwoStep /// @notice Interface for the two-step token launch process interface IPortalLauncherTwoStep is IPortalTypes { /// @notice Stage a new token (V5) without creating it yet /// @param params The immutable parameters for the new token /// @return token The predetermined address of the token /// @dev This stages a token by validating parameters and reserving the token address. /// The token contract is not deployed yet. Call commitNewTokenV5 to actually deploy it. /// Extensions are not supported in two-step launch. LP fee profile is always STANDARD. /// FIXME: this is not enabled in this version. Will be available once the audit for this part is ready. function stageNewTokenV5(StageNewTokenV5Params calldata params) external returns (address token); /// @notice Commit a staged token (V5) and create it /// @param params The parameters for token deployment (includes salt to identify staged token) /// @dev This deploys the token contract and initializes it. The token must have been staged first via stageNewTokenV5. /// The salt and isTaxToken in params must match what was used during staging. /// If taxRate > 0, creates FlapTaxTokenV2, otherwise creates regular token. /// FIXME: this is not enabled in this version. Will be available once the audit for this part is ready. function commitNewTokenV5(CommitNewTokenV5Params calldata params) external payable; } /// @notice Handles token creation and related operations interface IPortalLauncher is IPortalTypes { /// @notice Create a new token (V2) with flexible parameters /// @param params The parameters for the new token /// @return token The address of the created /// @dev due to the implementation limit, when creating a tax token and using an ERC20 token as the quote token, /// You need to pay an extra 1gwei native gas token (i.e msg.value = 1 gwei), or you will encounter an InsufficientValueForTaxTokenCreation error function newTokenV2(NewTokenV2Params calldata params) external payable returns (address token); /// @notice Create a new token (V3) with extension support /// @param params The parameters for the new token including extension configuration /// @return token The address of the created token /// @dev Similar to newTokenV2 but with extension support. Extension hooks will be called if extensionID is non-zero function newTokenV3(NewTokenV3Params calldata params) external payable returns (address token); /// @notice Create a new token (V4) with DEX ID and LP fee profile support /// @param params The parameters for the new token including DEX ID and LP fee profile /// @return token The address of the created token /// @dev Similar to newTokenV3 but with DEX ID and LP fee profile support. Allows specifying preferred DEX and fee tier function newTokenV4(NewTokenV4Params calldata params) external payable returns (address token); /// @notice Create a new token (V5) with tax V2 support /// @param params The parameters for the new token including advanced tax features /// @return token The address of the created token /// @dev Similar to newTokenV4 but with support for FlapTaxTokenV2 when taxRate > 0. /// When taxRate is 0, behaves like newTokenV4 (uses regular token or FlapTaxToken). /// When taxRate > 0, creates a FlapTaxTokenV2 with advanced tax distribution features. /// FIXME: this is not enabled in this version. Will be available once the audit for this part is ready. function newTokenV5(NewTokenV5Params calldata params) external payable returns (address token); /// @notice Create a new token (V6) — unified entry point for all token versions. /// @param params The parameters for the new token (see NewTokenV6Params). /// @return token The address of the created token. /// /// @dev Dispatch logic based on `params.tokenVersion`: /// /// TOKEN_V2_PERMIT (non-tax token): /// - buyTaxRate and sellTaxRate MUST both be 0. /// - commissionReceiver MUST be address(0). /// - Dispatched to PortalLauncherV5 → creates a standard ERC-20 (TOKEN_V2_PERMIT). /// /// TOKEN_TAXED (V1 tax token): /// - At least one tax rate must be > 0. /// - buyTaxRate MUST equal sellTaxRate (symmetric rates only). /// - mktBps MUST be 10000 (all tax goes to beneficiary after protocol fee). /// - dividendBps MUST be 0; deflationBps and lpBps MUST be 0. /// - commissionReceiver MUST be address(0) (commission not supported). /// - Dispatched to PortalLauncherV5Tax → creates a FlapTaxToken (TOKEN_TAXED). /// /// TOKEN_TAXED_V2 (V2 tax token): /// - At least one tax rate must be > 0. /// - buyTaxRate MUST equal sellTaxRate (symmetric rates only via V5 path). /// - mktBps MUST NOT be 10000 (use TOKEN_TAXED for that case). /// - mktBps + deflationBps + dividendBps + lpBps MUST equal 10000. /// - commissionReceiver MUST be address(0) (commission not supported). /// - Dispatched to PortalLauncherV5Tax → creates a FlapTaxTokenV2 (TOKEN_TAXED_V2). /// /// TOKEN_TAXED_V3 (V3 tax token): /// - At least one tax rate must be > 0. /// - Asymmetric rates allowed (buyTaxRate != sellTaxRate is OK). /// - mktBps + deflationBps + dividendBps + lpBps MUST equal 10000. /// - mktBps == 10000 IS allowed (all tax → protocol fee + commission, no marketing). /// - commissionReceiver CAN be non-zero (commission supported). /// - Dispatched to PortalLauncherTaxV3.launchTaxTokenV3 → creates a FlapTaxTokenV3 (TOKEN_TAXED_V3). /// /// Emits FlapTokenTaxSet (with max rate) AND FlapTokenAsymmetricTaxSet for tax tokens. function newTokenV6(NewTokenV6Params calldata params) external payable returns (address token); /// @notice Reserve the token address derived from `salt` by paying SALT_LOCK_FEE. /// @dev The caller must pass the `tokenVersion` they intend to lock for. /// Accepted values: TOKEN_V2_PERMIT (2) for non-tax tokens (NON_TAX_TOKEN_SUFFIX /// vanity requirement, typically 0x8888) and TOKEN_TAXED_V3 (6) for tax tokens /// (TAX_TOKEN_SUFFIX vanity requirement, typically 0x7777). Any other value /// reverts with UnsupportedTokenVersion. /// Fee is forwarded to FEE_RECEIVER. Emits FlapSaltLocked. /// @param salt CREATE2 salt to reserve. /// @param tokenVersion Token version to lock for. Must be TOKEN_V2_PERMIT or TOKEN_TAXED_V3. function lockSalt(bytes32 salt, TokenVersion tokenVersion) external payable; /// @notice Launch a new token with V4/PCS Infinity migration support /// @param params The V7 token parameters /// @return token The created token address function newTokenV7(NewTokenV7Params calldata params) external payable returns (address token); } /// @title Portal Lens Interface /// @notice Handles read-only token state queries interface IPortalLens is IPortalTypes { /// @notice Get token state /// @param token The address of the token /// @return state The state of the token function getTokenV2(address token) external view returns (TokenStateV2 memory state); /// @notice Get token state (V3) /// @param token The address of the token /// @return state The state of the token (V3) function getTokenV3(address token) external view returns (TokenStateV3 memory state); /// @notice Get token state (V4) /// @param token The address of the token /// @return state The state of the token (V4) with only 'r' curve parameter function getTokenV4(address token) external view returns (TokenStateV4 memory state); /// @notice Get token state (V5) /// @param token The address of the token /// @return state The state of the token (V5) with all curve parameters (r, h, k) function getTokenV5(address token) external view returns (TokenStateV5 memory state); /// @notice Get token state (V6) /// @param token The address of the token /// @return state The state of the token (V6) with all V5 fields plus taxRate, pool, and progress function getTokenV6(address token) external view returns (TokenStateV6 memory state); /// @notice Get token state (V7) /// @param token The address of the token /// @return state The state of the token (V7) with all V6 fields plus lpFeeProfile function getTokenV7(address token) external view returns (TokenStateV7 memory state); /// @notice Get token state (V8) /// @param token The address of the token /// @return state The state of the token (V8) with asymmetric buyTaxRate and sellTaxRate function getTokenV8(address token) external view returns (TokenStateV8 memory state); /// @notice Get token state (V8Safe) /// @dev Returns enum-typed fields (TokenStatus, TokenVersion, V3LPFeeProfile, DEXId) as uint8 /// instead of their Solidity enum types, preventing ABI-decoding reverts when new enum /// variants are introduced. Use this method when you need forward/backward compatibility /// with future contract upgrades that may add new enum values. /// @param token The address of the token /// @return state The state of the token with enum fields encoded as uint8 function getTokenV8Safe(address token) external view returns (TokenStateV8Safe memory state); /// @notice Get the quote token configuration for a given quote token address /// @param quoteToken The address of the quote token /// @return config The configuration of the quote token function getQuoteTokenConfiguration(address quoteToken) external view returns (QuoteTokenConfiguration memory config); } interface IPortalLensV2 is IPortalTypes { /// @notice Get the salt lock entry for a given CREATE2 salt. /// @param salt The CREATE2 salt to query. /// @return entry The SaltLockEntry (locker == address(0) means unlocked). function getSaltLock(bytes32 salt) external view returns (SaltLockEntry memory entry); /// @notice Get the total number of salts locked by a user for a specific token version in the on-chain index. /// @dev Only locks made from v5.11.0 onwards are present; pre-upgrade locks are not indexed. /// @param user The address of the user. /// @param tokenVersion The TokenVersion (as uint8) to filter on. /// @return count The number of salts in the on-chain index for this user and version. function getLockedSaltsCountByUserAndVersion(address user, uint8 tokenVersion) external view returns (uint256 count); /// @notice Paginated enumeration of salts locked by a user for a specific token version. /// @dev Only locks made from v5.11.0 onwards are present; pre-upgrade locks are not indexed. /// `limit` is capped at 100. /// @param user The address of the user. /// @param tokenVersion The TokenVersion (as uint8) to filter on. /// @param offset Zero-based start index within the user+version set. /// @param limit Maximum number of entries to return (capped at 100). /// @return salts The salt values in [offset, offset+limit).\ /// @return entries The corresponding SaltLockEntry structs.\ /// @return total The total number of salts in the on-chain index for this user and version.\ function getLockedSaltsByUserAndVersion(address user, uint8 tokenVersion, uint256 offset, uint256 limit)\ external\ view\ returns (bytes32[] memory salts, SaltLockEntry[] memory entries, uint256 total);\ }\ \ /// @title IPortalTrade Interface\ /// @notice Handles token trading and redemption\ interface IPortalTrade is IPortalTypes {\ /// @notice Buy token with ETH on creation\ /// @param token The address of the token to buy\ /// @param recipient The address to send the token to\ /// @param inputAmount The amount of ETH to spend\ ///\ /// @dev This function is mainly for internal use (be delegated called from the portal contract)\ /// The msg.value can be greater than inputAmount, the excess ETH will not be\ /// refunded to the caller. They will be charged as a fee.\ ///\ /// Note: the slippage is not checked in this function.\ ///\ function buyOnCreation(address token, address recipient, uint256 inputAmount)\ external\ payable\ returns (uint256 amount);\ \ /// @notice Buy token with ETH\ /// @param token The address of the token to buy\ /// @param recipient The address to send the token to\ /// @param minAmount The minimum amount of tokens to buy\ function buy(address token, address recipient, uint256 minAmount) external payable returns (uint256 amount);\ \ /// @notice Sell token for ETH\ /// @param token The address of the token to sell\ /// @param amount The amount of tokens to sell\ /// @param minEth The minimum amount of ETH to receive\ function sell(address token, uint256 amount, uint256 minEth) external returns (uint256 eth);\ \ /// @notice Redeem a killed token for another token\ /// @param srcToken The address of the token to redeem\ /// @param dstToken The address of the token to receive\ /// @param srcAmount The amount of srcToken to redeem\ /// @return dstAmount The amount of dstToken to receive\ function redeem(address srcToken, address dstToken, uint256 srcAmount) external returns (uint256 dstAmount);\ \ /// @notice Preview the amount of tokens to buy with ETH\ /// @param token The address of the token to buy\ /// @param eth The amount of ETH to spend\ /// @return amount The amount of tokens to buy\ function previewBuy(address token, uint256 eth) external view returns (uint256 amount);\ \ /// @notice Preview the amount of ETH to receive for selling tokens\ /// @param token The address of the token to sell\ /// @param amount The amount of tokens to sell\ /// @return eth The amount of ETH to receive\ function previewSell(address token, uint256 amount) external view returns (uint256 eth);\ \ /// @notice Preview redeem\ /// @param srcToken The address of the token to redeem\ /// @param dstToken The address of the token to receive\ /// @param srcAmount The amount of srcToken to redeem\ /// @return dstAmount The amount of dstToken to receive\ function previewRedeem(address srcToken, address dstToken, uint256 srcAmount)\ external\ view\ returns (uint256 dstAmount);\ }\ \ /// @title IPortalTradeV2 Interface\ /// @notice Handles unified token swaps and quoting\ interface IPortalTradeV2 is IPortalTypes {\ /// @notice Emitted when tax is paid on bonding curve for TAX_TOKEN\ /// @param token The address of the token\ /// @param amount The amount of tax paid\ event TaxOnBondingCurvePaid(address indexed token, uint256 amount);\ \ /// @notice Emitted when tax is paid on bonding curve for TAX_TOKEN_V2\ /// @param token The address of the token\ /// @param amount The amount of tax paid\ event TaxV2OnBondingCurvePaid(address indexed token, uint256 amount);\ \ /// @notice Parameters for swapping exact input amount for output token\ struct ExactInputParams {\ /// @notice The address of the input token (use address(0) for native asset)\ address inputToken;\ /// @notice The address of the output token (use address(0) for native asset)\ address outputToken;\ /// @notice The amount of input token to swap (in input token decimals)\ uint256 inputAmount;\ /// @notice The minimum amount of output token to receive\ uint256 minOutputAmount;\ /// @notice Optional permit data for the input token (can be empty)\ bytes permitData;\ }\ \ /// @notice Parameters for swapping exact input amount for output token (V3) with extension support\ struct ExactInputV3Params {\ /// @notice The address of the input token (use address(0) for native asset)\ address inputToken;\ /// @notice The address of the output token (use address(0) for native asset)\ address outputToken;\ /// @notice The amount of input token to swap (in input token decimals)\ uint256 inputAmount;\ /// @notice The minimum amount of output token to receive\ uint256 minOutputAmount;\ /// @notice Optional permit data for the input token (can be empty)\ bytes permitData;\ /// @notice Additional extension specific data to be passed to the extension's `onTrade` method, check the extension's documentation for details on the expected format and content\ bytes extensionData;\ }\ \ /// @notice Parameters for quoting the output amount for a given input\ struct QuoteExactInputParams {\ /// @notice The address of the input token (use address(0) for native asset)\ address inputToken;\ /// @notice The address of the output token (use address(0) for native asset)\ address outputToken;\ /// @notice The amount of input token to swap (in input token decimals)\ uint256 inputAmount;\ }\ /// @notice Swap exact input amount for output token\ /// @param params The swap parameters\ /// @return outputAmount The amount of output token received\ /// @dev Here are some possible scenarios:\ /// If the token's reserve is BNB or ETH (i.e: the quote token is the native gas token):\ /// - BUY: input token is address(0), output token is the token address\ /// - SELL: input token is the token address, output token is address(0)\ /// If the token's reserve is another ERC20 token (eg. USD*, i.e, the quote token is an ERC20 token):\ /// - BUY with USD*: input token is the USD* address, output token is the token address\ /// - SELL for USD*: input token is the token address, output token is the USD* address\ /// - BUY with BNB or ETH: input token is address(0), output token is the token address.\ /// (Note: this requires an internal swap to convert BNB/ETH to USD*, nativeToQuoteSwap must be anabled for this quote token)\ /// Note: Currently, this method supports trading tokens that is either still on the bonding curve or already listed on DEX.\ \ function swapExactInput(ExactInputParams calldata params) external payable returns (uint256 outputAmount);\ \ /// @notice Swap exact input amount for output token (V3) with extension support\ /// @param params The swap parameters including extension data\ /// @return outputAmount The amount of output token received\ /// @dev Similar to swapExactInput but with extension support. Extension hooks will be called if the token uses an extension\ function swapExactInputV3(ExactInputV3Params calldata params) external payable returns (uint256 outputAmount);\ \ /// @notice Quote the output amount for a given input\ /// @param params The quote parameters\ /// @return outputAmount The quoted output amount\ /// @dev refer to the swapExactInput method for the scenarios\ function quoteExactInput(QuoteExactInputParams calldata params) external returns (uint256 outputAmount);\ }\ \ interface IV4Locker {\ /// @notice Collect V4/PCS Infinity LP fees for a token and distribute via TaxProcessor\ /// @dev Can be called by anyone (permissionless keeper). The Locker's collectAddress is\ /// set to Portal, so fees always flow through Portal → TaxProcessor → distribution.\ /// @param token The token whose LP fees to collect\ function collectV4Fees(address token) external;\ \ /// @notice Add liquidity to locked V4/PCS Infinity LP positions for a token.\ /// @dev Called by TaxProcessor to reinvest LP-share tax revenue.\ /// Tokens must be transferred to Portal before calling this function.\ /// Portal (as lock owner) calls increaseLiquidity on the GoPlus/UNCX locker\ /// for both the quote-only (lower) and token-only (upper) positions.\ /// @param token The protocol token address\ /// @param tokenAmount Amount of protocol token available for LP\ /// @param quoteAmount Amount of quote token available for LP\ /// @return actualTokenUsed Total protocol token consumed\ /// @return actualQuoteUsed Total quote token consumed\ function addV4LPLiquidity(address token, uint256 tokenAmount, uint256 quoteAmount)\ external\ returns (uint256 actualTokenUsed, uint256 actualQuoteUsed);\ }\ \ /// @title IPortalCore Interface\ /// @notice Combines IPortalLauncher and IPortalTrade\ interface IPortalCore is IPortalLauncher, IPortalTrade, IPortalTradeV2 {}\ \ /// @title IPortalMigrator Interface\ /// @notice Add liquidity from the bonding curve to DEX\ /// @dev this is not a public interface of the portal.\ /// All the functions of this interface are either called from the portal\ /// or from the UniswapV3Pool contract.\ interface IPortalMigrator {\ /// @notice Add liquidity to DEX\ /// @param token The address of the token\ /// @dev This is an internal function\ /// Any dispatch to this function should be checked in portal contract\ /// This function may be dellegated called from a payable function.\ function luanchToDEX(address token) external payable;\ }\ \ /// @title IRoller Interface\ /// @notice This acts as the glue between the portal and the flap staking contract\ interface IRoller {\ /// @notice The lock the token is using\ enum LockType {\ INVALID_LOCK, // Invalid lock\ UNCX_LOCK, // The UNCX lock\ GOPLUS_UNIV3_LOCK, // The Goplus UNIv3 lock\ TOSHI_LP_LOCK, // The Toshi LP lock\ IZI_LP_LOCK // The IziSwap LP locker\ }\ \ /// @notice get the locks by token address\ /// @param token The address of the token\ /// @return locks The lock ids of the token\ function getLocks(address token) external view returns (uint256[] memory locks);\ \ /// @dev deprecated\ function rollv2(bytes calldata packedParams) external;\ \ /// @notice Revenue Share: Claim LP fees for a vanity token\ /// @param token The address of the token\ /// @return tokenAmount The amount of the token claimed\ /// @return ethAmount The amount of ETH claimed\ /// @dev Only the beneficiary of the token can call this function.\ function claim(address token) external returns (uint256 tokenAmount, uint256 ethAmount);\ \ /// @notice Allows the default admin to change the beneficiary of a token\ /// @param token The address of the token\ /// @param newBeneficiary The new beneficiary address\ function setTokenBeneficiary(address token, address newBeneficiary) external;\ \ /// @notice Allows a roller or default admin to claim LP fees on behalf of the beneficiary\ /// @param token The address of the token\ /// @return tokenAmount The amount of the token claimed\ /// @return quoteAmount The amount of quote token (or ETH) claimed\ /// @dev Only the roller or default admin can call this function.\ /// The claimed fee will be sent to the beneficiary of the token.\ function delegateClaim(address token) external returns (uint256 tokenAmount, uint256 quoteAmount);\ }\ \ interface IPortalDexRouter {\ // @notice Update the DEX pool information for a token\ // @dev can only be called by DEX_ROUTER_MANAGER_ROLE roles\ function updateTokenPoolInfo(address token, IPortalTypes.PackedDexPool calldata poolInfo) external;\ }\ \ /// @title IPortalTweak Interface\ /// @notice Handles admin-only configuration operations for the Portal\ interface IPortalTweak is IPortalTypes {\ /// @notice Parameters for updating tax token addresses\ struct TaxTokenAddressUpdate {\ /// @notice The address of the tax token to update\ address token;\ /// @notice The new beneficiary address for the tax splitter\ address beneficiary;\ /// @notice The new fee receiver address for the tax splitter\ address feeReceiver;\ }\ \ /// @notice Emitted when tax token addresses are updated\ /// @param token The address of the tax token\ /// @param beneficiary The new beneficiary address\ /// @param feeReceiver The new fee receiver address\ /// @dev Only Default ADMIN can change this\ event TaxTokenAddressesUpdated(address indexed token, address beneficiary, address feeReceiver);\ \ /// @notice Set the configuration for a quote token\ /// @dev Only callable by the default admin\ /// @param quoteToken The address of the quote token\ /// @param config The configuration struct for the quote token\ function setQuoteTokenConfiguration(address quoteToken, QuoteTokenConfiguration calldata config) external;\ \ /// @notice Set the fee exemption status for a list of traders\ /// @dev Only callable by the default admin\ /// @param traders The addresses of the traders to set exemption for\ /// @param isExempted Whether the traders should be exempted from fees\ function setFeeExemption(address[] memory traders, bool isExempted) external;\ \ /// @notice Get the current buy and sell fee rates\ /// @return buyFeeRate The current buy fee rate in basis points (e.g. 200 = 2%)\ /// @return sellFeeRate The current sell fee rate in basis points (e.g. 200 = 2%)\ function getFeeRate() external view returns (uint256 buyFeeRate, uint256 sellFeeRate);\ \ /// @notice Set the fee profile for a token\ /// @dev Only callable by DEFAULT_ADMIN_ROLE or TOKEN_FLAP_FEE_SETTER_ROLE\ /// @param token The address of the token\ /// @param feeProfile The fee profile to set\ function setFlapFeeProfile(address token, FlapFeeProfile feeProfile) external;\ \ /// @notice Update beneficiary and feeReceiver addresses for one or more tax tokens\ /// @dev Only callable by the default admin, TAX_MANAGER_ROLE, or TAX_GUARDIAN_ROLE\ /// @param updates Array of TaxTokenAddressUpdate structs containing token addresses and new addresses\ function updateTaxTokenAddresses(TaxTokenAddressUpdate[] calldata updates) external;\ \ /// @notice Register an extension for use with the portal\ /// @param extensionId The unique identifier for this extension\ /// @param extensionAddress The address of the extension contract\ /// @param version The version of the extension interface it implements (starting from 1)\ /// @dev Only callable by the default admin\ function registerExtension(bytes32 extensionId, address extensionAddress, uint8 version) external;\ \ /// @notice Block or unblock multiple addresses from creating tokens\ /// @param spammers The addresses to block or unblock\ /// @param blocked True to block, false to unblock\ /// @dev Only callable by the default admin or MODERATOR_ROLE\ function setSpammerBlockedBatch(address[] calldata spammers, bool blocked) external;\ \ /// @notice Quickly pause only `newTokenV7` while leaving other global-switch entrypoints unaffected.\ /// @dev Callable by V7_GUARDIAN_ROLE or DEFAULT_ADMIN_ROLE.\ function haltNewTokenV7() external;\ \ /// @notice Emitted when stuck tax tokens are recovered from the tax splitter\ /// @param taxToken The address of the tax token\ /// @param amountSentToToken The amount of tokens sent back to the tax token contract\ /// @param amountReturnedToSplitter The amount of tokens returned to the tax splitter\ event StuckTaxTokenRecovered(address indexed taxToken, uint256 amountSentToToken, uint256 amountReturnedToSplitter);\ \ /// @notice Recover stuck tax tokens from the tax splitter and re-inject up to liquidationThreshold into the token\ /// @dev Only callable by AUDITOR_ROLE. Currently supports V1 (TOKEN_TAXED) tax tokens.\ /// The name is intentionally generic to allow extension to V2/V3 tax tokens in the future.\ /// @param taxToken The address of the V1 tax token to recover stuck tokens for\ function recoverStuckTaxToken(address taxToken) external;\ \ /// @notice Emitted when stuck tax tokens are burned (sent to the dead address)\ /// @param taxToken The address of the tax token\ /// @param amountBurned The amount of tokens sent to the dead address\ event StuckTaxTokenBurned(address indexed taxToken, uint256 amountBurned);\ \ /// @notice Burn stuck tax tokens by sweeping them from the tax splitter and sending to the dead address\ /// @dev Only callable by DEFAULT_ADMIN_ROLE. Supports V1 (TOKEN_TAXED) tax tokens.\ /// @param taxToken The address of the V1 tax token to burn stuck tokens for\ function burnStuckTaxToken(address taxToken) external;\ \ /// @notice Emitted when marketAddress is updated for a V2/V3 tax token.\ event MarketWalletChanged(address indexed token, address indexed oldMarket, address indexed newMarket);\ \ /// @notice Changes the market wallet (TaxProcessor.marketAddress) for a TOKEN_TAXED_V2 or TOKEN_TAXED_V3 token.\ /// @dev Restricted to TAX_GUARDIAN_ROLE or DEFAULT_ADMIN_ROLE.\ /// @param token The tax token address.\ /// @param newMarketWallet The new market wallet address.\ function changeMarketWallet(address token, address newMarketWallet) external;\ }\ \ /// @title Portal Interface\ /// @notice This interface combines the core and game interfaces\ interface IPortal is\ IPortalCore,\ IAccessControlUpgradeable,\ IRoller,\ IV4Locker,\ IPortalDexRouter,\ IPortalTweak,\ IPortalLens,\ IPortalLensV2,\ IPortalLauncherTwoStep\ {\ /// @notice Get the version of the portal\ /// @return The version string\ function version() external view returns (string memory);\ \ /// @notice Check if tax on bonding curve is enabled\ /// @return enabled True if tax on bonding curve is enabled\ function enableTaxOnBondingCurve() external view returns (bool enabled);\ \ /// @notice Change the protocol bit flags\ /// @dev Can only be called with DEFAULT_ADMIN_ROLE\ /// @param flags The new flags\ function setBitFlags(uint256 flags) external;\ \ /// @notice Can only be called by the guardian role or the default admin role\ /// @dev This function is used to pause the protocol\ function halt() external;\ \ /// @notice Check if an address is blocked from creating tokens\ /// @param spammer The address to check\ /// @return True if the address is blocked\ function isSpammerBlocked(address spammer) external view returns (bool);\ \ /// @notice Send a message for a token\ /// @param token The address of the token\ /// @param message The message to send\ function sendMsg(address token, string memory message) external;\ \ /// @notice Get the current nonce of the portal\ function nonce() external view returns (uint256);\ } --- # World Cup Viewer | Flap For the complete documentation index, see [llms.txt](https://docs.flap.sh/flap/llms.txt) . This page is also available as [Markdown](https://docs.flap.sh/flap/developers/preview/worldcup-viewer.md) . [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#overview) Overview --------------------------------------------------------------------------------------- `WorldCupViewer` is a read-only smart contract deployed on BSC mainnet that provides a clean, high-level view of **2026 FIFA World Cup** match results. Users and integrators can query `WorldCupViewer` to find out the winner of the entire tournament or the winner of any specific group, without needing to understand the underlying oracle structure. `WorldCupViewer` is a read-only contract. All state — including match results and team names — is either stored in the contract or read live from the oracle. No transactions are required to query match results. * * * [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#deployed-address) Deployed address ------------------------------------------------------------------------------------------------------- Network Address BSC mainnet `0x00036192958C2aaAF9F445d3Cdc2979995EA333e` * * * [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#methods) Methods ------------------------------------------------------------------------------------- All query methods return a `MatchViewResult` struct: Copy struct MatchViewResult { uint256 matchId; // the match ID queried string matchName; // human-readable name of the match bool isResolved; // true if the oracle has settled this match uint256 teamId; // ID of the winning team (0 if not yet resolved; 50 = draw/tie; 49 = others) string teamName; // name of the winning team ("" if not yet resolved; "draw" for ties; "others" for unlisted teams) } `teamId` uses two reserved values: `49` (`others`) for any team not individually tracked by the oracle, and `50` (`draw`) when a match ends in a tie. ### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#getworldcupwinner) `getWorldCupWinner()` Returns the winner of the 2026 FIFA World Cup (match ID `1`). * If `isResolved = false`, the tournament winner has not yet been determined. * If `isResolved = true`, `teamId` and `teamName` identify the champion. If the champion is a team not individually tracked by the oracle (i.e., grouped under "Others"), the contract resolves the winner using the separately configured `othersWinner` field, which is set by the contract owner. * * * ### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#getgroupmatchwinners-uint256-matchid) `getGroupMatchWinners(uint256 matchId)` Returns the winner of a group stage match. Pass the match ID for the group you want to query (e.g., `2` for Group A, `3` for Group B, and so on up to `13` for Group L). * If `isResolved = false`, the group winner has not yet been determined. * If `isResolved = true`, `teamId` and `teamName` identify the group winner. * * * ### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#getmatchresult-uint256-matchid) `getMatchResult(uint256 matchId)` Returns the resolved result for any match ID directly from the underlying oracle mapping. * If `isResolved = false`, the match is not settled yet and the result remains pending. * If `isResolved = true`, `teamId` identifies the winning outcome mapped from oracle request IDs. * For a draw outcome (`teamId = 50`), `teamName` is returned as a tie message in the format `TeamA(teamIdA) and TeamB(teamIdB) are tied`. * For non-draw outcomes, `teamName` is the mapped team name for that `teamId`. * * * ### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#getteamname-uint256-teamid) `getTeamName(uint256 teamId)` Looks up the name for a given `teamId`. The standard query range is `1`\-`48` (listed teams). If `teamId = 49`, it represents `"others"`; if `teamId = 50`, it represents `"draw"`. Unknown IDs return an empty string. * * * [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#reference-data) Reference data --------------------------------------------------------------------------------------------------- ### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#team-id-mapping) Team ID mapping teamId Team 1 Mexico 2 South Africa 3 South Korea 4 Czechia 5 Canada 6 Bosnia and Herzegovina 7 Qatar 8 Switzerland 9 Brazil 10 Morocco 11 Haiti 12 Scotland 13 USA 14 Paraguay 15 Australia 16 Türkiye 17 Germany 18 Curaçao 19 Ivory Coast 20 Ecuador 21 Netherlands 22 Japan 23 Sweden 24 Tunisia 25 Belgium 26 Egypt 27 Iran 28 New Zealand 29 Spain 30 Cape Verde 31 Saudi Arabia 32 Uruguay 33 France 34 Senegal 35 Iraq 36 Norway 37 Argentina 38 Algeria 39 Austria 40 Jordan 41 Portugal 42 DR Congo 43 Uzbekistan 44 Colombia 45 England 46 Croatia 47 Ghana 48 Panama 49 Others (any unlisted team) 50 Draw * * * ### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-id-reference) Match ID reference More matches will be added to this document as the tournament progresses. #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#chapter-1-winner-match-1) Chapter 1: winner (Match 1) #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-1-2026-fifa-world-cup-winner) Match 1 — 2026 FIFA World Cup Winner Do **not** use `getMatchResult(1)` to determine the 2026 FIFA World Cup champion. Instead, always use `getWorldCupWinner()`, which fully resolves the tournament winner even if the oracle data merges several teams under "Others". Using `getMatchResult(1)` returns the raw oracle grouping and may not identify the actual team; `getWorldCupWinner()` always provides the true winner team ID and name. Resolves to the team that wins the entire 2026 FIFA World Cup. This match tracks all individually listed teams plus an "Others" catch-all for any team not given their own oracle question. teamId Team Oracle question title 29 Spain Will Spain win the 2026 FIFA World Cup? 33 France Will France win the 2026 FIFA World Cup? 45 England Will England win the 2026 FIFA World Cup? 37 Argentina Will Argentina win the 2026 FIFA World Cup? 9 Brazil Will Brazil win the 2026 FIFA World Cup? 41 Portugal Will Portugal win the 2026 FIFA World Cup? 17 Germany Will Germany win the 2026 FIFA World Cup? 21 Netherlands Will Netherlands win the 2026 FIFA World Cup? 36 Norway Will Norway win the 2026 FIFA World Cup? 25 Belgium Will Belgium win the 2026 FIFA World Cup? 13 USA Will USA win the 2026 FIFA World Cup? 10 Morocco Will Morocco win the 2026 FIFA World Cup? 44 Colombia Will Colombia win the 2026 FIFA World Cup? 22 Japan Will Japan win the 2026 FIFA World Cup? 32 Uruguay Will Uruguay win the 2026 FIFA World Cup? 46 Croatia Will Croatia win the 2026 FIFA World Cup? 1 Mexico Will Mexico win the 2026 FIFA World Cup? 8 Switzerland Will Switzerland win the 2026 FIFA World Cup? 20 Ecuador Will Ecuador win the 2026 FIFA World Cup? 34 Senegal Will Senegal win the 2026 FIFA World Cup? 15 Australia Will Australia win the 2026 FIFA World Cup? 5 Canada Will Canada win the 2026 FIFA World Cup? 12 Scotland Will Scotland win the 2026 FIFA World Cup? 3 South Korea Will South Korea win the 2026 FIFA World Cup? 14 Paraguay Will Paraguay win the 2026 FIFA World Cup? 19 Ivory Coast Will Ivory Coast win the 2026 FIFA World Cup? 26 Egypt Will Egypt win the 2026 FIFA World Cup? 27 Iran Will Iran win the 2026 FIFA World Cup? 47 Ghana Will Ghana win the 2026 FIFA World Cup? 38 Algeria Will Algeria win the 2026 FIFA World Cup? 24 Tunisia Will Tunisia win the 2026 FIFA World Cup? 39 Austria Will Austria win the 2026 FIFA World Cup? 28 New Zealand Will New Zealand win the 2026 FIFA World Cup? 11 Haiti Will Haiti win the 2026 FIFA World Cup? 40 Jordan Will Jordan win the 2026 FIFA World Cup? 18 Curaçao Will Curaçao win the 2026 FIFA World Cup? 43 Uzbekistan Will Uzbekistan win the 2026 FIFA World Cup? 2 South Africa Will South Africa win the 2026 FIFA World Cup? 30 Cape Verde Will Cape Verde win the 2026 FIFA World Cup? 7 Qatar Will Qatar win the 2026 FIFA World Cup? 31 Saudi Arabia Will Saudi Arabia win the 2026 FIFA World Cup? — Others Will none of the listed teams win the 2026 FIFA World Cup? * * * For all group winner matches (Match 2–Match 13), you can use either `getMatchResult(matchId)` or `getGroupMatchWinners(matchId)`. Both will return the same result. #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#chapter-2-group-match-winners-match-2-13) Chapter 2: group match winners (Match 2-13) #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-2-group-a-winner) Match 2 — Group A Winner Resolves to the team that wins Group A in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 1 Mexico Will Mexico win Group A in the 2026 FIFA World Cup? 2 South Africa Will South Africa win Group A in the 2026 FIFA World Cup? 3 South Korea Will South Korea win Group A in the 2026 FIFA World Cup? 4 Czechia Will the winner of the Czechia/Denmark/North Macedonia/Republic of Ireland playoff win Group A in the 2026 FIFA World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-3-group-b-winner) Match 3 — Group B Winner Resolves to the team that wins Group B in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 5 Canada Will Canada win Group B in the 2026 World Cup? 6 Bosnia and Herzegovina Will the winner of the Bosnia and Herzegovina/Italy/Northern Ireland/Wales playoff win Group B in the 2026 World Cup? 7 Qatar Will Qatar win Group B in the 2026 World Cup? 8 Switzerland Will Switzerland win Group B in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-4-group-c-winner) Match 4 — Group C Winner Resolves to the team that wins Group C in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 9 Brazil Will Brazil win Group C in the 2026 World Cup? 10 Morocco Will Morocco win Group C in the 2026 World Cup? 11 Haiti Will Haiti win Group C in the 2026 World Cup? 12 Scotland Will Scotland win Group C in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-5-group-d-winner) Match 5 — Group D Winner Resolves to the team that wins Group D in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 13 USA Will USA win Group D in the 2026 World Cup? 14 Paraguay Will Paraguay win Group D in the 2026 World Cup? 15 Australia Will Australia win Group D in the 2026 World Cup? 16 Türkiye Will the winner of the Kosovo/Romania/Slovakia/Türkiye playoff win Group D in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-6-group-e-winner) Match 6 — Group E Winner Resolves to the team that wins Group E in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 17 Germany Will Germany win Group E in the 2026 World Cup? 18 Curaçao Will Curaçao win Group E in the 2026 World Cup? 19 Ivory Coast Will Ivory Coast win Group E in the 2026 World Cup? 20 Ecuador Will Ecuador win Group E in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-7-group-f-winner) Match 7 — Group F Winner Resolves to the team that wins Group F in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 21 Netherlands Will Netherlands win Group F in the 2026 World Cup? 22 Japan Will Japan win Group F in the 2026 World Cup? 23 Sweden Will the winner of the Albania/Poland/Sweden/Ukraine playoff win Group F in the 2026 World Cup? 24 Tunisia Will Tunisia win Group F in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-8-group-g-winner) Match 8 — Group G Winner Resolves to the team that wins Group G in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 25 Belgium Will Belgium win Group G in the 2026 World Cup? 26 Egypt Will Egypt win Group G in the 2026 World Cup? 27 Iran Will Iran win Group G in the 2026 World Cup? 28 New Zealand Will New Zealand win Group G in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-9-group-h-winner) Match 9 — Group H Winner Resolves to the team that wins Group H in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 29 Spain Will Spain win Group H in the 2026 World Cup? 30 Cape Verde Will Cape Verde win Group H in the 2026 World Cup? 31 Saudi Arabia Will Saudi Arabia win Group H in the 2026 World Cup? 32 Uruguay Will Uruguay win Group H in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-10-group-i-winner) Match 10 — Group I Winner Resolves to the team that wins Group I in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 33 France Will France win Group I in the 2026 World Cup? 34 Senegal Will Senegal win Group I in the 2026 World Cup? 35 Iraq Will the winner of the Bolivia/Iraq/Suriname playoff win Group I in the 2026 World Cup? 36 Norway Will Norway win Group I in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-11-group-j-winner) Match 11 — Group J Winner Resolves to the team that wins Group J in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 37 Argentina Will Argentina win Group J in the 2026 World Cup? 38 Algeria Will Algeria win Group J in the 2026 World Cup? 39 Austria Will Austria win Group J in the 2026 World Cup? 40 Jordan Will Jordan win Group J in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-12-group-k-winner) Match 12 — Group K Winner Resolves to the team that wins Group K in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 41 Portugal Will Portugal win Group K in the 2026 World Cup? 42 DR Congo Will the winner of the Democratic Republic of Congo/Jamaica/New Caledonia playoff win Group K in the 2026 World Cup? 43 Uzbekistan Will Uzbekistan win Group K in the 2026 World Cup? 44 Colombia Will Colombia win Group K in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-13-group-l-winner) Match 13 — Group L Winner Resolves to the team that wins Group L in the 2026 FIFA World Cup group stage. teamId Team Oracle question title 45 England Will England win Group L in the 2026 World Cup? 46 Croatia Will Croatia win Group L in the 2026 World Cup? 47 Ghana Will Ghana win Group L in the 2026 World Cup? 48 Panama Will Panama win Group L in the 2026 World Cup? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#chapter-3-group-matches-match-14-85) Chapter 3: group matches (Match 14-85) #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-14-mexico-vs.-south-africa) Match 14 — Mexico vs. South Africa Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 1 Mexico Will Mexico win on 2026-06-11 in Group A of the 2026 FIFA World Cup group stage? 50 draw Will Mexico vs. South Africa end in a draw in Group A of the 2026 FIFA World Cup group stage? 2 South Africa Will South Africa win on 2026-06-11 in Group A of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-15-south-korea-vs.-czechia) Match 15 — South Korea vs. Czechia Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 3 South Korea Will Korea Republic win on 2026-06-11 in Group A of the 2026 FIFA World Cup group stage? 50 draw Will Korea Republic vs. Czechia end in a draw in Group A of the 2026 FIFA World Cup group stage? 4 Czechia Will Czechia win on 2026-06-11 in Group A of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-16-canada-vs.-bosnia-and-herzegovina) Match 16 — Canada vs. Bosnia and Herzegovina Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 5 Canada Will Canada win on 2026-06-12 in Group B of the 2026 FIFA World Cup group stage? 50 draw Will Canada vs. Bosnia and Herzegovina end in a draw in Group B of the 2026 FIFA World Cup group stage? 6 Bosnia and Herzegovina Will Bosnia and Herzegovina win on 2026-06-12 in Group B of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-17-usa-vs.-paraguay) Match 17 — USA vs. Paraguay Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 13 USA Will United States win on 2026-06-12 in Group D of the 2026 FIFA World Cup group stage? 50 draw Will United States vs. Paraguay end in a draw in Group D of the 2026 FIFA World Cup group stage? 14 Paraguay Will Paraguay win on 2026-06-12 in Group D of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-18-qatar-vs.-switzerland) Match 18 — Qatar vs. Switzerland Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 7 Qatar Will Qatar win on 2026-06-13 in Group B of the 2026 FIFA World Cup group stage? 50 draw Will Qatar vs. Switzerland end in a draw in Group B of the 2026 FIFA World Cup group stage? 8 Switzerland Will Switzerland win on 2026-06-13 in Group B of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-19-brazil-vs.-morocco) Match 19 — Brazil vs. Morocco Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 9 Brazil Will Brazil win on 2026-06-13 in Group C of the 2026 FIFA World Cup group stage? 50 draw Will Brazil vs. Morocco end in a draw in Group C of the 2026 FIFA World Cup group stage? 10 Morocco Will Morocco win on 2026-06-13 in Group C of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-20-haiti-vs.-scotland) Match 20 — Haiti vs. Scotland Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 11 Haiti Will Haiti win on 2026-06-13 in Group C of the 2026 FIFA World Cup group stage? 50 draw Will Haiti vs. Scotland end in a draw in Group C of the 2026 FIFA World Cup group stage? 12 Scotland Will Scotland win on 2026-06-13 in Group C of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-21-australia-vs.-turkiye) Match 21 — Australia vs. Türkiye Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 15 Australia Will Australia win on 2026-06-14 in Group D of the 2026 FIFA World Cup group stage? 50 draw Will Australia vs. Türkiye end in a draw in Group D of the 2026 FIFA World Cup group stage? 16 Türkiye Will Türkiye win on 2026-06-14 in Group D of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-22-germany-vs.-curacao) Match 22 — Germany vs. Curaçao Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 17 Germany Will Germany win on 2026-06-14 in Group E of the 2026 FIFA World Cup group stage? 50 draw Will Germany vs. Curaçao end in a draw in Group E of the 2026 FIFA World Cup group stage? 18 Curaçao Will Curaçao win on 2026-06-14 in Group E of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-23-netherlands-vs.-japan) Match 23 — Netherlands vs. Japan Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 21 Netherlands Will Netherlands win on 2026-06-14 in Group F of the 2026 FIFA World Cup group stage? 50 draw Will Netherlands vs. Japan end in a draw in Group F of the 2026 FIFA World Cup group stage? 22 Japan Will Japan win on 2026-06-14 in Group F of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-24-ivory-coast-vs.-ecuador) Match 24 — Ivory Coast vs. Ecuador Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 19 Ivory Coast Will Côte d'Ivoire win on 2026-06-14 in Group E of the 2026 FIFA World Cup group stage? 50 draw Will Côte d'Ivoire vs. Ecuador end in a draw in Group E of the 2026 FIFA World Cup group stage? 20 Ecuador Will Ecuador win on 2026-06-14 in Group E of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-25-sweden-vs.-tunisia) Match 25 — Sweden vs. Tunisia Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 23 Sweden Will Sweden win on 2026-06-14 in Group F of the 2026 FIFA World Cup group stage? 50 draw Will Sweden vs. Tunisia end in a draw in Group F of the 2026 FIFA World Cup group stage? 24 Tunisia Will Tunisia win on 2026-06-14 in Group F of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-26-spain-vs.-cape-verde) Match 26 — Spain vs. Cape Verde Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 29 Spain Will Spain win on 2026-06-15 in Group H of the 2026 FIFA World Cup group stage? 50 draw Will Spain vs. Cabo Verde end in a draw in Group H of the 2026 FIFA World Cup group stage? 30 Cape Verde Will Cabo Verde win on 2026-06-15 in Group H of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-27-belgium-vs.-egypt) Match 27 — Belgium vs. Egypt Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 25 Belgium Will Belgium win on 2026-06-15 in Group G of the 2026 FIFA World Cup group stage? 50 draw Will Belgium vs. Egypt end in a draw in Group G of the 2026 FIFA World Cup group stage? 26 Egypt Will Egypt win on 2026-06-15 in Group G of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-28-saudi-arabia-vs.-uruguay) Match 28 — Saudi Arabia vs. Uruguay Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 31 Saudi Arabia Will Saudi Arabia win on 2026-06-15 in Group H of the 2026 FIFA World Cup group stage? 50 draw Will Saudi Arabia vs. Uruguay end in a draw in Group H of the 2026 FIFA World Cup group stage? 32 Uruguay Will Uruguay win on 2026-06-15 in Group H of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-29-iran-vs.-new-zealand) Match 29 — Iran vs. New Zealand Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 27 Iran Will IR Iran win on 2026-06-15 in Group G of the 2026 FIFA World Cup group stage? 50 draw Will IR Iran vs. New Zealand end in a draw in Group G of the 2026 FIFA World Cup group stage? 28 New Zealand Will New Zealand win on 2026-06-15 in Group G of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-30-france-vs.-senegal) Match 30 — France vs. Senegal Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 33 France Will France win on 2026-06-16 in Group I of the 2026 FIFA World Cup group stage? 50 draw Will France vs. Senegal end in a draw in Group I of the 2026 FIFA World Cup group stage? 34 Senegal Will Senegal win on 2026-06-16 in Group I of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-31-iraq-vs.-norway) Match 31 — Iraq vs. Norway Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 35 Iraq Will Iraq win on 2026-06-16 in Group I of the 2026 FIFA World Cup group stage? 50 draw Will Iraq vs. Norway end in a draw in Group I of the 2026 FIFA World Cup group stage? 36 Norway Will Norway win on 2026-06-16 in Group I of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-32-argentina-vs.-algeria) Match 32 — Argentina vs. Algeria Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 37 Argentina Will Argentina win on 2026-06-16 in Group J of the 2026 FIFA World Cup group stage? 50 draw Will Argentina vs. Algeria end in a draw in Group J of the 2026 FIFA World Cup group stage? 38 Algeria Will Algeria win on 2026-06-16 in Group J of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-33-austria-vs.-jordan) Match 33 — Austria vs. Jordan Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 39 Austria Will Austria win on 2026-06-17 in Group J of the 2026 FIFA World Cup group stage? 50 draw Will Austria vs. Jordan end in a draw in Group J of the 2026 FIFA World Cup group stage? 40 Jordan Will Jordan win on 2026-06-17 in Group J of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-34-portugal-vs.-dr-congo) Match 34 — Portugal vs. DR Congo Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 41 Portugal Will Portugal win on 2026-06-17 in Group K of the 2026 FIFA World Cup group stage? 50 draw Will Portugal vs. DR Congo end in a draw in Group K of the 2026 FIFA World Cup group stage? 42 DR Congo Will DR Congo win on 2026-06-17 in Group K of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-35-england-vs.-croatia) Match 35 — England vs. Croatia Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 45 England Will England win on 2026-06-17 in Group L of the 2026 FIFA World Cup group stage? 50 draw Will England vs. Croatia end in a draw in Group L of the 2026 FIFA World Cup group stage? 46 Croatia Will Croatia win on 2026-06-17 in Group L of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-36-ghana-vs.-panama) Match 36 — Ghana vs. Panama Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 47 Ghana Will Ghana win on 2026-06-17 in Group L of the 2026 FIFA World Cup group stage? 50 draw Will Ghana vs. Panama end in a draw in Group L of the 2026 FIFA World Cup group stage? 48 Panama Will Panama win on 2026-06-17 in Group L of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-37-uzbekistan-vs.-colombia) Match 37 — Uzbekistan vs. Colombia Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 43 Uzbekistan Will Uzbekistan win on 2026-06-17 in Group K of the 2026 FIFA World Cup group stage? 50 draw Will Uzbekistan vs. Colombia end in a draw in Group K of the 2026 FIFA World Cup group stage? 44 Colombia Will Colombia win on 2026-06-17 in Group K of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-38-czechia-vs.-south-africa) Match 38 — Czechia vs. South Africa Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 4 Czechia Will Czechia win on 2026-06-18 in Group A of the 2026 FIFA World Cup group stage? 50 draw Will Czechia vs. South Africa end in a draw in Group A of the 2026 FIFA World Cup group stage? 2 South Africa Will South Africa win on 2026-06-18 in Group A of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-39-switzerland-vs.-bosnia-and-herzegovina) Match 39 — Switzerland vs. Bosnia and Herzegovina Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 8 Switzerland Will Switzerland win on 2026-06-18 in Group B of the 2026 FIFA World Cup group stage? 50 draw Will Switzerland vs. Bosnia and Herzegovina end in a draw in Group B of the 2026 FIFA World Cup group stage? 6 Bosnia and Herzegovina Will Bosnia and Herzegovina win on 2026-06-18 in Group B of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-40-canada-vs.-qatar) Match 40 — Canada vs. Qatar Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 5 Canada Will Canada win on 2026-06-18 in Group B of the 2026 FIFA World Cup group stage? 50 draw Will Canada vs. Qatar end in a draw in Group B of the 2026 FIFA World Cup group stage? 7 Qatar Will Qatar win on 2026-06-18 in Group B of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-41-mexico-vs.-south-korea) Match 41 — Mexico vs. South Korea Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 1 Mexico Will Mexico win on 2026-06-18 in Group A of the 2026 FIFA World Cup group stage? 50 draw Will Mexico vs. Korea Republic end in a draw in Group A of the 2026 FIFA World Cup group stage? 3 South Korea Will Korea Republic win on 2026-06-18 in Group A of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-42-usa-vs.-australia) Match 42 — USA vs. Australia Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 13 USA Will United States win on 2026-06-19 in Group D of the 2026 FIFA World Cup group stage? 50 draw Will United States vs. Australia end in a draw in Group D of the 2026 FIFA World Cup group stage? 15 Australia Will Australia win on 2026-06-19 in Group D of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-43-scotland-vs.-morocco) Match 43 — Scotland vs. Morocco Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 12 Scotland Will Scotland win on 2026-06-19 in Group C of the 2026 FIFA World Cup group stage? 50 draw Will Scotland vs. Morocco end in a draw in Group C of the 2026 FIFA World Cup group stage? 10 Morocco Will Morocco win on 2026-06-19 in Group C of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-44-brazil-vs.-haiti) Match 44 — Brazil vs. Haiti Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 9 Brazil Will Brazil win on 2026-06-19 in Group C of the 2026 FIFA World Cup group stage? 50 draw Will Brazil vs. Haiti end in a draw in Group C of the 2026 FIFA World Cup group stage? 11 Haiti Will Haiti win on 2026-06-19 in Group C of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-45-turkiye-vs.-paraguay) Match 45 — Türkiye vs. Paraguay Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 16 Türkiye Will Türkiye win on 2026-06-19 in Group D of the 2026 FIFA World Cup group stage? 50 draw Will Türkiye vs. Paraguay end in a draw in Group D of the 2026 FIFA World Cup group stage? 14 Paraguay Will Paraguay win on 2026-06-19 in Group D of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-46-netherlands-vs.-sweden) Match 46 — Netherlands vs. Sweden Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 21 Netherlands Will Netherlands win on 2026-06-20 in Group F of the 2026 FIFA World Cup group stage? 50 draw Will Netherlands vs. Sweden end in a draw in Group F of the 2026 FIFA World Cup group stage? 23 Sweden Will Sweden win on 2026-06-20 in Group F of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-47-germany-vs.-ivory-coast) Match 47 — Germany vs. Ivory Coast Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 17 Germany Will Germany win on 2026-06-20 in Group E of the 2026 FIFA World Cup group stage? 50 draw Will Germany vs. Côte d'Ivoire end in a draw in Group E of the 2026 FIFA World Cup group stage? 19 Ivory Coast Will Côte d'Ivoire win on 2026-06-20 in Group E of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-48-ecuador-vs.-curacao) Match 48 — Ecuador vs. Curaçao Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 20 Ecuador Will Ecuador win on 2026-06-20 in Group E of the 2026 FIFA World Cup group stage? 50 draw Will Ecuador vs. Curaçao end in a draw in Group E of the 2026 FIFA World Cup group stage? 18 Curaçao Will Curaçao win on 2026-06-20 in Group E of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-49-tunisia-vs.-japan) Match 49 — Tunisia vs. Japan Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 24 Tunisia Will Tunisia win on 2026-06-21 in Group F of the 2026 FIFA World Cup group stage? 50 draw Will Tunisia vs. Japan end in a draw in Group F of the 2026 FIFA World Cup group stage? 22 Japan Will Japan win on 2026-06-21 in Group F of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-50-spain-vs.-saudi-arabia) Match 50 — Spain vs. Saudi Arabia Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 29 Spain Will Spain win on 2026-06-21 in Group H of the 2026 FIFA World Cup group stage? 50 draw Will Spain vs. Saudi Arabia end in a draw in Group H of the 2026 FIFA World Cup group stage? 31 Saudi Arabia Will Saudi Arabia win on 2026-06-21 in Group H of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-51-belgium-vs.-iran) Match 51 — Belgium vs. Iran Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 25 Belgium Will Belgium win on 2026-06-21 in Group G of the 2026 FIFA World Cup group stage? 50 draw Will Belgium vs. IR Iran end in a draw in Group G of the 2026 FIFA World Cup group stage? 27 Iran Will IR Iran win on 2026-06-21 in Group G of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-52-uruguay-vs.-cape-verde) Match 52 — Uruguay vs. Cape Verde Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 32 Uruguay Will Uruguay win on 2026-06-21 in Group H of the 2026 FIFA World Cup group stage? 50 draw Will Uruguay vs. Cabo Verde end in a draw in Group H of the 2026 FIFA World Cup group stage? 30 Cape Verde Will Cabo Verde win on 2026-06-21 in Group H of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-53-new-zealand-vs.-egypt) Match 53 — New Zealand vs. Egypt Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 28 New Zealand Will New Zealand win on 2026-06-21 in Group G of the 2026 FIFA World Cup group stage? 50 draw Will New Zealand vs. Egypt end in a draw in Group G of the 2026 FIFA World Cup group stage? 26 Egypt Will Egypt win on 2026-06-21 in Group G of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-54-argentina-vs.-austria) Match 54 — Argentina vs. Austria Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 37 Argentina Will Argentina win on 2026-06-22 in Group J of the 2026 FIFA World Cup group stage? 50 draw Will Argentina vs. Austria end in a draw in Group J of the 2026 FIFA World Cup group stage? 39 Austria Will Austria win on 2026-06-22 in Group J of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-55-france-vs.-iraq) Match 55 — France vs. Iraq Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 33 France Will France win on 2026-06-22 in Group I of the 2026 FIFA World Cup group stage? 50 draw Will France vs. Iraq end in a draw in Group I of the 2026 FIFA World Cup group stage? 35 Iraq Will Iraq win on 2026-06-22 in Group I of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-56-norway-vs.-senegal) Match 56 — Norway vs. Senegal Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 36 Norway Will Norway win on 2026-06-22 in Group I of the 2026 FIFA World Cup group stage? 50 draw Will Norway vs. Senegal end in a draw in Group I of the 2026 FIFA World Cup group stage? 34 Senegal Will Senegal win on 2026-06-22 in Group I of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-57-jordan-vs.-algeria) Match 57 — Jordan vs. Algeria Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 40 Jordan Will Jordan win on 2026-06-22 in Group J of the 2026 FIFA World Cup group stage? 50 draw Will Jordan vs. Algeria end in a draw in Group J of the 2026 FIFA World Cup group stage? 38 Algeria Will Algeria win on 2026-06-22 in Group J of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-58-portugal-vs.-uzbekistan) Match 58 — Portugal vs. Uzbekistan Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 41 Portugal Will Portugal win on 2026-06-23 in Group K of the 2026 FIFA World Cup group stage? 50 draw Will Portugal vs. Uzbekistan end in a draw in Group K of the 2026 FIFA World Cup group stage? 43 Uzbekistan Will Uzbekistan win on 2026-06-23 in Group K of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-59-england-vs.-ghana) Match 59 — England vs. Ghana Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 45 England Will England win on 2026-06-23 in Group L of the 2026 FIFA World Cup group stage? 50 draw Will England vs. Ghana end in a draw in Group L of the 2026 FIFA World Cup group stage? 47 Ghana Will Ghana win on 2026-06-23 in Group L of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-60-panama-vs.-croatia) Match 60 — Panama vs. Croatia Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 48 Panama Will Panama win on 2026-06-23 in Group L of the 2026 FIFA World Cup group stage? 50 draw Will Panama vs. Croatia end in a draw in Group L of the 2026 FIFA World Cup group stage? 46 Croatia Will Croatia win on 2026-06-23 in Group L of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-61-colombia-vs.-dr-congo) Match 61 — Colombia vs. DR Congo Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 44 Colombia Will Colombia win on 2026-06-23 in Group K of the 2026 FIFA World Cup group stage? 50 draw Will Colombia vs. DR Congo end in a draw in Group K of the 2026 FIFA World Cup group stage? 42 DR Congo Will DR Congo win on 2026-06-23 in Group K of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-62-bosnia-and-herzegovina-vs.-qatar) Match 62 — Bosnia and Herzegovina vs. Qatar Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 6 Bosnia and Herzegovina Will Bosnia and Herzegovina win on 2026-06-24 in Group B of the 2026 FIFA World Cup group stage? 50 draw Will Bosnia and Herzegovina vs. Qatar end in a draw in Group B of the 2026 FIFA World Cup group stage? 7 Qatar Will Qatar win on 2026-06-24 in Group B of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-63-switzerland-vs.-canada) Match 63 — Switzerland vs. Canada Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 8 Switzerland Will Switzerland win on 2026-06-24 in Group B of the 2026 FIFA World Cup group stage? 50 draw Will Switzerland vs. Canada end in a draw in Group B of the 2026 FIFA World Cup group stage? 5 Canada Will Canada win on 2026-06-24 in Group B of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-64-morocco-vs.-haiti) Match 64 — Morocco vs. Haiti Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 10 Morocco Will Morocco win on 2026-06-24 in Group C of the 2026 FIFA World Cup group stage? 50 draw Will Morocco vs. Haiti end in a draw in Group C of the 2026 FIFA World Cup group stage? 11 Haiti Will Haiti win on 2026-06-24 in Group C of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-65-scotland-vs.-brazil) Match 65 — Scotland vs. Brazil Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 12 Scotland Will Scotland win on 2026-06-24 in Group C of the 2026 FIFA World Cup group stage? 50 draw Will Scotland vs. Brazil end in a draw in Group C of the 2026 FIFA World Cup group stage? 9 Brazil Will Brazil win on 2026-06-24 in Group C of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-66-czechia-vs.-mexico) Match 66 — Czechia vs. Mexico Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 4 Czechia Will Czechia win on 2026-06-24 in Group A of the 2026 FIFA World Cup group stage? 50 draw Will Czechia vs. Mexico end in a draw in Group A of the 2026 FIFA World Cup group stage? 1 Mexico Will Mexico win on 2026-06-24 in Group A of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-67-south-africa-vs.-south-korea) Match 67 — South Africa vs. South Korea Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 2 South Africa Will South Africa win on 2026-06-24 in Group A of the 2026 FIFA World Cup group stage? 50 draw Will South Africa vs. Korea Republic end in a draw in Group A of the 2026 FIFA World Cup group stage? 3 South Korea Will Korea Republic win on 2026-06-24 in Group A of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-68-curacao-vs.-ivory-coast) Match 68 — Curaçao vs. Ivory Coast Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 18 Curaçao Will Curaçao win on 2026-06-25 in Group E of the 2026 FIFA World Cup group stage? 50 draw Will Curaçao vs. Côte d'Ivoire end in a draw in Group E of the 2026 FIFA World Cup group stage? 19 Ivory Coast Will Côte d'Ivoire win on 2026-06-25 in Group E of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-69-ecuador-vs.-germany) Match 69 — Ecuador vs. Germany Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 20 Ecuador Will Ecuador win on 2026-06-25 in Group E of the 2026 FIFA World Cup group stage? 50 draw Will Ecuador vs. Germany end in a draw in Group E of the 2026 FIFA World Cup group stage? 17 Germany Will Germany win on 2026-06-25 in Group E of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-70-japan-vs.-sweden) Match 70 — Japan vs. Sweden Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 22 Japan Will Japan win on 2026-06-25 in Group F of the 2026 FIFA World Cup group stage? 50 draw Will Japan vs. Sweden end in a draw in Group F of the 2026 FIFA World Cup group stage? 23 Sweden Will Sweden win on 2026-06-25 in Group F of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-71-tunisia-vs.-netherlands) Match 71 — Tunisia vs. Netherlands Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 24 Tunisia Will Tunisia win on 2026-06-25 in Group F of the 2026 FIFA World Cup group stage? 50 draw Will Tunisia vs. Netherlands end in a draw in Group F of the 2026 FIFA World Cup group stage? 21 Netherlands Will Netherlands win on 2026-06-25 in Group F of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-72-turkiye-vs.-usa) Match 72 — Türkiye vs. USA Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 16 Türkiye Will Türkiye win on 2026-06-25 in Group D of the 2026 FIFA World Cup group stage? 50 draw Will Türkiye vs. United States end in a draw in Group D of the 2026 FIFA World Cup group stage? 13 USA Will United States win on 2026-06-25 in Group D of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-73-paraguay-vs.-australia) Match 73 — Paraguay vs. Australia Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 14 Paraguay Will Paraguay win on 2026-06-25 in Group D of the 2026 FIFA World Cup group stage? 50 draw Will Paraguay vs. Australia end in a draw in Group D of the 2026 FIFA World Cup group stage? 15 Australia Will Australia win on 2026-06-25 in Group D of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-74-senegal-vs.-iraq) Match 74 — Senegal vs. Iraq Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 34 Senegal Will Senegal win on 2026-06-26 in Group I of the 2026 FIFA World Cup group stage? 50 draw Will Senegal vs. Iraq end in a draw in Group I of the 2026 FIFA World Cup group stage? 35 Iraq Will Iraq win on 2026-06-26 in Group I of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-75-norway-vs.-france) Match 75 — Norway vs. France Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 36 Norway Will Norway win on 2026-06-26 in Group I of the 2026 FIFA World Cup group stage? 50 draw Will Norway vs. France end in a draw in Group I of the 2026 FIFA World Cup group stage? 33 France Will France win on 2026-06-26 in Group I of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-76-cape-verde-vs.-saudi-arabia) Match 76 — Cape Verde vs. Saudi Arabia Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 30 Cape Verde Will Cabo Verde win on 2026-06-26 in Group H of the 2026 FIFA World Cup group stage? 50 draw Will Cabo Verde vs. Saudi Arabia end in a draw in Group H of the 2026 FIFA World Cup group stage? 31 Saudi Arabia Will Saudi Arabia win on 2026-06-26 in Group H of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-77-uruguay-vs.-spain) Match 77 — Uruguay vs. Spain Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 32 Uruguay Will Uruguay win on 2026-06-26 in Group H of the 2026 FIFA World Cup group stage? 50 draw Will Uruguay vs. Spain end in a draw in Group H of the 2026 FIFA World Cup group stage? 29 Spain Will Spain win on 2026-06-26 in Group H of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-78-egypt-vs.-iran) Match 78 — Egypt vs. Iran Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 26 Egypt Will Egypt win on 2026-06-26 in Group G of the 2026 FIFA World Cup group stage? 50 draw Will Egypt vs. IR Iran end in a draw in Group G of the 2026 FIFA World Cup group stage? 27 Iran Will IR Iran win on 2026-06-26 in Group G of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-79-new-zealand-vs.-belgium) Match 79 — New Zealand vs. Belgium Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 28 New Zealand Will New Zealand win on 2026-06-26 in Group G of the 2026 FIFA World Cup group stage? 50 draw Will New Zealand vs. Belgium end in a draw in Group G of the 2026 FIFA World Cup group stage? 25 Belgium Will Belgium win on 2026-06-26 in Group G of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-80-croatia-vs.-ghana) Match 80 — Croatia vs. Ghana Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 46 Croatia Will Croatia win on 2026-06-27 in Group L of the 2026 FIFA World Cup group stage? 50 draw Will Croatia vs. Ghana end in a draw in Group L of the 2026 FIFA World Cup group stage? 47 Ghana Will Ghana win on 2026-06-27 in Group L of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-81-panama-vs.-england) Match 81 — Panama vs. England Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 48 Panama Will Panama win on 2026-06-27 in Group L of the 2026 FIFA World Cup group stage? 50 draw Will Panama vs. England end in a draw in Group L of the 2026 FIFA World Cup group stage? 45 England Will England win on 2026-06-27 in Group L of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-82-colombia-vs.-portugal) Match 82 — Colombia vs. Portugal Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 44 Colombia Will Colombia win on 2026-06-27 in Group K of the 2026 FIFA World Cup group stage? 50 draw Will Colombia vs. Portugal end in a draw in Group K of the 2026 FIFA World Cup group stage? 41 Portugal Will Portugal win on 2026-06-27 in Group K of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-83-dr-congo-vs.-uzbekistan) Match 83 — DR Congo vs. Uzbekistan Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 42 DR Congo Will DR Congo win on 2026-06-27 in Group K of the 2026 FIFA World Cup group stage? 50 draw Will DR Congo vs. Uzbekistan end in a draw in Group K of the 2026 FIFA World Cup group stage? 43 Uzbekistan Will Uzbekistan win on 2026-06-27 in Group K of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-84-jordan-vs.-argentina) Match 84 — Jordan vs. Argentina Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 40 Jordan Will Jordan win on 2026-06-27 in Group J of the 2026 FIFA World Cup group stage? 50 draw Will Jordan vs. Argentina end in a draw in Group J of the 2026 FIFA World Cup group stage? 37 Argentina Will Argentina win on 2026-06-27 in Group J of the 2026 FIFA World Cup group stage? * * * #### [](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-85-algeria-vs.-austria) Match 85 — Algeria vs. Austria Resolves to the winner of this match in the 2026 FIFA World Cup. teamId Team Oracle question title 38 Algeria Will Algeria win on 2026-06-27 in Group J of the 2026 FIFA World Cup group stage? 50 draw Will Algeria vs. Austria end in a draw in Group J of the 2026 FIFA World Cup group stage? 39 Austria Will Austria win on 2026-06-27 in Group J of the 2026 FIFA World Cup group stage? * * * [PreviousWorld Cup Resolver](https://docs.flap.sh/flap/developers/preview/worldcup-resolver) [NextAudit Reports](https://docs.flap.sh/flap/audit-reports) Last updated 1 month ago * [Overview](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#overview) * [Deployed address](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#deployed-address) * [Methods](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#methods) * [getWorldCupWinner()](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#getworldcupwinner) * [getGroupMatchWinners(uint256 matchId)](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#getgroupmatchwinners-uint256-matchid) * [getMatchResult(uint256 matchId)](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#getmatchresult-uint256-matchid) * [getTeamName(uint256 teamId)](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#getteamname-uint256-teamid) * [Reference data](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#reference-data) * [Team ID mapping](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#team-id-mapping) * [Match ID reference](https://docs.flap.sh/flap/developers/preview/worldcup-viewer#match-id-reference) Copy function getWorldCupWinner() external view returns (MatchViewResult memory); Copy function getGroupMatchWinners(uint256 matchId) external view returns (MatchViewResult memory); Copy function getMatchResult(uint256 matchId) external view returns (MatchViewResult memory); Copy function getTeamName(uint256 teamId) external view returns (string memory); --- # Unknown \# Flap ## Flap - \[Brief Overview of Flap\](https://docs.flap.sh/flap/readme.md): The Ultimate Decentralized Token Creation Platform - \[Flap Official Links\](https://docs.flap.sh/flap/flap-official-links.md) - \[Developers\](https://docs.flap.sh/flap/developers.md) - \[Deployed Contract Addresses\](https://docs.flap.sh/flap/developers/deployed-contract-addresses.md) - \[Basic & Mechanism\](https://docs.flap.sh/flap/developers/basic-and-mechanism.md) - \[Bonding Curve\](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve.md) - \[Migrated To DEX\](https://docs.flap.sh/flap/developers/basic-and-mechanism/list-on-dex.md) - \[Portal vs VaultPortal\](https://docs.flap.sh/flap/developers/basic-and-mechanism/portal-vs-vaultportal.md) - \[Flap Tax Token\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token.md) - \[PreBond Tax\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax.md) - \[Tax Token V1\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1.md) - \[Tax Token V2\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2.md) - \[Tax Token V3\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3.md) - \[Wallet & Terminal & Bot Developers\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers.md) - \[A Quick Start For Wallet & Terminal & Bot Developers\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/a-quick-start-for-wallet-terminal-bot-developers.md) - \[Deployed Contract Addresses\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/deployed-contract-addresses.md) - \[Index Token Created Events\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events.md) - \[Bonding Curve In Developers' Perspective\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective.md) - \[Inspect A Token\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token.md) - \[Parse Token Meta\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta.md) - \[Token Version Specification\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification.md) - \[Inspect A Tax Token\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token.md) - \[Trade Tokens\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens.md) - \[Token Migration\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-migration.md) - \[Token Launcher Developers\](https://docs.flap.sh/flap/developers/token-launcher-developers.md) - \[Quick start for token launcher developers\](https://docs.flap.sh/flap/developers/token-launcher-developers/quick-start-token-launcher-developers.md) - \[Deployed Contract Addresses\](https://docs.flap.sh/flap/developers/token-launcher-developers/deployed-contract-addresses.md) - \[Launch token through Portal\](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal.md) - \[Launch token through VaultPortal\](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal.md) - \[Registered vaults\](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults.md) - \[Vault Developers\](https://docs.flap.sh/flap/developers/vault-developers.md) - \[Quick start for Vault Developers\](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers.md) - \[Deployed Contract Addresses\](https://docs.flap.sh/flap/developers/vault-developers/deployed-contract-addresses.md) - \[Vault & VaultFactory Specification\](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification.md) - \[Flap Tax Vault\](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault.md) - \[Gift Vault (FlapXVault)\](https://docs.flap.sh/flap/developers/vault-developers/gift-vault.md) - \[Gift Vault V2\](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2.md) - \[Building Upgradeable Vaults — Beacon Proxy Pattern\](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault.md) - \[Tutorials\](https://docs.flap.sh/flap/developers/vault-developers/tutorials.md) - \[Using an LP Token or Child Token as the Dividend Token\](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend.md) - \[Preview\](https://docs.flap.sh/flap/developers/preview.md) - \[Beta: PVE Curve\](https://docs.flap.sh/flap/developers/preview/beta-pve-curve.md) - \[Flap AI Oracle\](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle.md) - \[Flap Candy Box\](https://docs.flap.sh/flap/developers/preview/flap-candy-box.md) - \[Flap Trigger Service\](https://docs.flap.sh/flap/developers/preview/flap-trigger-service.md) - \[World Cup Resolver\](https://docs.flap.sh/flap/developers/preview/worldcup-resolver.md) - \[World Cup Viewer\](https://docs.flap.sh/flap/developers/preview/worldcup-viewer.md) - \[Audit Reports\](https://docs.flap.sh/flap/audit-reports.md) - \[Media Kit\](https://docs.flap.sh/flap/media-kit.md) - \[OpenClaw Skills\](https://docs.flap.sh/flap/skills.md) --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/readme.md). # Brief Overview of Flap ![](https://docs.flap.sh/files/xTO4XxGzvOvaDPNdLUaY) Flap is an on-chain token launch protocol on EVM, born out of an ETHGlobal hackathon victory. Flap's mission is to empower projects and creators with decentralized, on-chain tools to launch tokens and design customized token standards seamlessly. #### Rich Terminal & Wallets Support Tokens launched via Flap are supported by a broad ecosystem of wallets, trading platforms, and bots, including \*\*OKX Wallet, Binance Wallet, GMGN, Debot, Ave.ai, Dragun\*\*, \*\*Axiom\*\* and more—ensuring strong discoverability and liquidity from day one. #### Non-Tax & Tax Token Support Flap supports both \*\*non-tax tokens\*\* and \*\*tax tokens\*\*. For tax tokens, creators can customize trading tax fee rates at \*\*1%, 3%, 5%, or 10%\*\*, enabling flexible use cases such as community incentives, marketing, treasury funding, or charitable contributions. ### Flap Tax Vaults Flap offers the ability for developers to create custom smart contracts for tax token management. By adhering to the provided vault specifications, enhanced insights and details can be accessed via our website and supported terminals. Developers can also build and deploy their own vault templates (Vault Factory), offering a monetization opportunity when users opt to utilize their templates. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers.md). # Developers This guide is structured to introduce you to the different facets of developing with Flap. It's recommended to first familiarize yourself with the \*\*basic mechanisms of Flap\*\*. Once you're comfortable with the fundamentals, proceed to the section that suits your specific role: \* \*\*Wallets & Terminal & Bot Developers:\*\* For developers who want to build an indexer for Flap and trade tokens launched from Flap \* \*\*Launcher Developers:\*\* For applications leveraging Flap's infrastructure to launch tokens. \* \*\*Vault Developers:\*\* For those interested in building upon Flap's Vault system. Please read the section corresponding to your role for detailed instructions and guidance. \* \[Basic & Mechanism\](/flap/developers/basic-and-mechanism.md) \* \[Wallets & Terminal & Bot Developers\](/flap/developers/wallet-and-terminal-and-bot-developers.md) \* \[Launcher Developers\](/flap/developers/token-launcher-developers.md) \* \[Vault Developers\](/flap/developers/vault-developers.md) --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/flap-official-links.md). # Flap Official Links Flap Website:\[ \](https://www.gamesverse.io/) Flap X (Formally Twitter):\[ \](https://twitter.com/0xGameVerse) Flap Telegram: Flap Discord: Flap Farcaster: --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/deployed-contract-addresses.md). # Deployed Contract Addresses This page lists all deployed contract addresses across supported chains. Each chain has a table with the contract name, address, and any relevant notes. ## Mainnet ### BNB Chain | Name | Address | Notes | | ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------------- | | Portal | \`0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0\` | v5.8.6 | | Standard Token Impl | \`0x8b4329947e34b6d56d71a3385cac122bade7d78d\` | Vanity suffix: 8888 | | Tax Token V1 Impl | \`0x29e6383F0ce68507b5A72a53c2B118a118332aA8\` | Vanity suffix: 7777 | | Tax Token V2 Impl | \`0xae562c6A05b798499507c6276C6Ed796027807BA\` | | | Tax Token V3 Impl | \`0x024f18294970B5c76c0691b87f138A0317156422\` | | | Standard Token V3 Impl | \`0x88881b6f03090462a969eC7f48385744Eeb63333\` | Vanity suffix: 8888 | | VaultPortal | \`0x90497450f2a706f1951b5bdda52B4E5d16f34C06\` | | | Trigger Service | \`0xcf4EE25035CF883895110f367F5BA8172416a7F9\` | See \[Flap Trigger Service\](/flap/developers/preview/flap-trigger-service.md) | | AI Oracle | \`0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39\` | Preview. See \[Flap AI Oracle\](/flap/developers/preview/flap-ai-oracle.md) | | Candy Box | \`0x6255fbd731272a517022e99f6CaCf6A5De9414Ee\` | Preview. See \[Flap Candy Box\](/flap/developers/preview/flap-candy-box.md) | | Tax Token Helper | \`0x53841c73217735F37BC1775538b03b23feFD8346\` | | ### Robinhood | Name | Address | Notes | | ----------------- | -------------------------------------------- | ------------------- | | Portal | \`0x26605f322f7fF986f381bB9A6e3f5DAb0bEaEb09\` | v5.14.15 | | Tax Token V3 Impl | \`0x7777C8743C88B3aff3cf262135beF2c8b2e83333\` | Vanity suffix: 7777 | | VaultPortal | \`0xe9F7AB7DE8FB8756acbB6a1cd13316a43308197B\` | | | Tax Token Helper | \`0xb10bD2672aE63735d677164A54B573a016f0203C\` | | ### Toshimart | Name | Address | Notes | | ------------------- | -------------------------------------------- | ------------------- | | Portal | \`0x1ea172Fb88C24DFC21Ff6Fa38762511C123bA948\` | v4.11.0 | | Standard Token Impl | \`0xF3c514E04f83166E80718f29f0d34F206be40A0A\` | Vanity suffix: 8453 | ### X Layer | Name | Address | Notes | | ------------------- | -------------------------------------------- | ------------------- | | Portal | \`0xb30D8c4216E1f21F27444D2FfAee3ad577808678\` | v4.12.1 | | Standard Token Impl | \`0x12Dc83157Bf1cfCB8Db5952b3ba5bb56Cc38f8C9\` | Vanity suffix: 1111 | | Tax Token V1 Impl | \`0xa9918579C9eD0899eCc7e449B9c59916Fb89bAF1\` | Vanity suffix: 7777 | ### Muffun | Name | Address | Notes | | ------------------- | -------------------------------------------- | ------------------- | | Portal | \`0x4267F317adee7C6478a5EE92985c2BD5D855E274\` | v4.13.2 | | Standard Token Impl | \`0x2a770E952bB2700393238199B5889013693A8271\` | Vanity suffix: 8888 | ### Monad | Name | Address | Notes | | ------------------- | -------------------------------------------- | ------------------- | | Portal | \`0x30e8ee7b5881bf2E158A0514f2150aabe2c68b23\` | v5.1.2 | | Standard Token Impl | \`0xB88189aA1162850D75A1c1e16F837b7979994184\` | Vanity suffix: 8888 | | Tax Token V1 Impl | \`0x1C8847736521f5cD725dFB8f33c7c610826e7C42\` | Vanity suffix: 1111 | ## Testnet ### BNB Testnet | Name | Address | Notes | | ------------------- | -------------------------------------------- | ---------------------------------------------------------------------------- | | Portal | \`0x5bEacaF7ABCbB3aB280e80D007FD31fcE26510e9\` | v5.8.5 | | Standard Token Impl | \`0x87D5f292ba33011997641C7a7Bd2b17799aaA814\` | Vanity suffix: 8888 | | Tax Token V1 Impl | \`0x87d8D03d0c3E064ACdb48E42fecbE8a8538dE6Fc\` | Vanity suffix: 7777 | | Tax Token V2 Impl | \`0x2486e3ff5502bac48D2D86457e7c24B2bB0dDDb5\` | | | Tax Token V3 Impl | \`0xE6Ff967a887084c16D0fD71548CF709542cc1557\` | | | VaultPortal | \`0x027e3704fC5C16522e9393d04C60A3ac5c0d775f\` | | | Trigger Service | \`0x560E9830926C9e0EB98a59c6b9902383Fc0D9Eb2\` | See \[Flap Trigger Service\](/flap/developers/preview/flap-trigger-service.md) | | AI Oracle | \`0xFfddcE44e8cFf7703Fd85118524bfC8B2f70b744\` | Preview. See \[Flap AI Oracle\](/flap/developers/preview/flap-ai-oracle.md) | | Candy Box | \`0x8e6C16Bf07022a7Da9398B543f38846E9355Bd70\` | Preview. See \[Flap Candy Box\](/flap/developers/preview/flap-candy-box.md) | | Tax Token Helper | \`0xD64441e5FcD02D342B8cf6eBA10Ef6E40d0dA90f\` | | --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/basic-and-mechanism.md). # Basic & Mechanism This chapter explains the core mechanisms behind Flap and the foundational components developers need to understand before integrating. It covers bonding curve behavior, the DEX migration flow, Portal vs VaultPortal differences, and the Flap Tax Token model. ## In this chapter \* \[📈 Bonding Curve\](/flap/developers/basic-and-mechanism/bonding-curve.md) \* \[⚖️ Migrated To DEX\](/flap/developers/basic-and-mechanism/list-on-dex.md) \* \[Portal vs VaultPortal\](/flap/developers/basic-and-mechanism/portal-vs-vaultportal.md) \* Flap Tax Token \* \[PreBond Tax\](/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax.md) \* \[Tax Token V1\](/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1.md) \* \[Tax Token V2\](/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2.md) --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token.md). # Flap Tax Token - \[PreBond Tax\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax.md) - \[Tax Token V1\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1.md) - \[Tax Token V2\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2.md) - \[Tax Token V3\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3.md) --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/basic-and-mechanism/portal-vs-vaultportal.md). # Portal vs VaultPortal ## Overview The \`Portal\` is the entry point of the Flap bonding curve protocol. It launches standard tokens and tax tokens and wires them to the bonding curve lifecycle. For each tax token launched, if the "funds recipient wallet" percentage is set to non-zero, a portion or all tax revenue is sent to that wallet. And when that wallet is a smart contract and implements Vault interface, it is called a \`Vault\`. The \`VaultPortal\` builds on top of the \`Portal\` to add Vault functionality for tax tokens. It launches a tax token and sets up a Vault in one flow. For Vault concepts and behavior, see \[developers/vault-developers/flap-tax-vault.md\](/flap/developers/vault-developers/flap-tax-vault.md). ## When to use which \* Use \`Portal\` when you only need to launch a standard token or a tax token without Vault features or your custom Vault implementation. \* Use \`VaultPortal\` when you want Vault-backed tax tokens and using a vault factory. (You can use any Vault Factory, this is permissionless.) ## Relationship diagram !\[Vault vs VaultPortal relationship\](/files/Votv94P9JAYdTWniksf2) --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/basic-and-mechanism/list-on-dex.md). # Migrated To DEX ## Overview Each token launched on flap has a maximum supply of 1 Billion. When a token reaches the milestone to be listed on the DEX, the remaining supply (non circulating) tokens and all the reserve (i.e., the quote token, e.g., ETH/BNB) will be added as liquidity to DEX. For different deployment, the milestone to be listed on DEX is different. In previous section ( \[Bonding Curve\](/flap/developers/basic-and-mechanism/bonding-curve.md) ), we know that the reserve of a token can be found by its circulating supply. So we can represent the milestone for migrating to DEX in either circulating supply or reserve. We list both of them in the following table for BNB chain when quote is BNB:
DeploymentCirculating SupplyReserve
BSC (Legacy before block #42042177 )800M30.1 BNB
BSC (Legacy since block #42042177)800M16 BNB
BSC (Legacy since block 47855189 and before 51314696)800M8 ETH
BSC ( After 51314696)800M16 ETH / 10000USD1
Let's take a look at the BSC Legacy deployment as an example. The purple point is the milestone when the token can be listed on the DEX, which is a circulating supply of 800M, or a reserve of 16 BNB or a market cap of 100 BNB. When the token reaches the milestone, it will be listed on DEX. If we are adding this token to Uniswap V3 (or its fork, e.g, PancakeSwap V3), the reserve (16 BNB) and the remaining token (200M) will be added as liquidity. ~~Besides, the Flap protocol is flexible and allows more token to be sold on the bonding curve. For example (the red curve on the following graph), if the last buyer buys a lot of token on the bonding curve, the resulting circulating supply would be 900M , and the reserve will be 36 BNB giving a market cap of 400 BNB. The token will still be listed on DEX with the remaining 100M token and 36 BNB reserve.~~ (CHANGES: You can not buy more than 800M no matter it is an old token or new one.) {% hint style="info" %} When a token is a tax token, it can only be migrated to Uniswap V2 or its forks. {% endhint %} For non-tax token, we will first try to add liquidity to Uniswap V3 (or its fork). If it is not viable, we will fallback to Uniswap V2 (or its fork). ## Adding Liquidity To Uniswap V3 (or its fork) When adding liquidity to Uniswap V3, we only add liquidity to a concentrated range of prices, giving the token a better depth. The lowest price is the initial price on the bonding curve (i.e., the price when no one has bought the token yet), the current price is the last price on the bonding curve before the token is listed on DEX, and the max price is 10000 times of the current price.
--- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax.md). # PreBond Tax Flap supports Tax tokens. Since V5.4.0 (on BNB Chain & XLayer) , tax is not only applied to migrated tax tokens but also the ones that are still on the bonding curve. When a tax token has already migrated DEX, it will charges a tax on every trade through the main pool and accumulate the tax in the token first. Once the accumulated token amount reaches a specific liquidation threshold, an automatic liquidation happens to sell the token for quote and sent the the beneficiary through the Tax Splitter. (Check below to learn more detaills) When a tax token is still on the bonding curve, we don’t implement it as a “real tax” like a migrated one. To make it simple and easy to do an off-chain quote, we implement it as an \`extra fee\` added upon the current bonding curve fee. For example: \* For a non-tax token, the current bonding curve fee on BNB chain is 1%. \* For a token with a tax of 3%, the effective trading fee becomes: 1% + 3% = 4% \* For a token with a tax of 1%, the effective trading fee is 2% --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v2.md). # Tax Token V2 ## Overview The V2 tax token let you split the tax for different usage:
{% hint style="warning" %} When the "Funds Recipient Wallet" section is 100%, a v1 tax token will be created instead {% endhint %} ### Tax Processor & Dividend
For every Tax Token V2 launch, the following contracts are deployed: 1. \*\*Tax Processor\*\* - Always deployed 2. \*\*Dividend Contract\*\* - Optionally deployed if dividend distribution is enabled (when \`dividendBps > 0\`) When the token is still trading on the bonding curve (before DEX migration): \`\`\` User buys/sells on Portal bonding curve ↓ Portal collects extra trading fee as tax in quote tokens (WETH/BNB/USDT/etc.) ↓ Portal sends extra fee to TaxProcessor.processBondingCurveTax(quoteAmount) ↓ Tax Processor accumulates quote tokens for later distribution \`\`\` While the token is migrated to DEX and when users trade the Flap Tax Token V2 on DEX: \`\`\` User trades token on DEX ↓ Tax Token V2 applies tax (e.g., 5%) on transfer ↓ Tax tokens accumulate in Token contract ↓ When threshold reached → Triggers tax liquidation ↓ Token contract calls TaxProcessor.processTaxTokens(taxAmount) \`\`\` \*\*Anyone\*\* can call \`TaxProcessor.dispatch()\` to distribute accumulated funds: \`\`\` Anyone calls dispatch() ↓ 1. Send feeQuoteBalance → Funds Recipient Wallet 2. Send marketQuoteBalance → Market Address 3. Send dividendQuoteBalance → Dividend Contract 4. Process preBondBurnFunds (the quote accumuated on bonding curve phase for the burn): - If token on bonding curve: Use Portal to buyback & burn - If token on DEX: Swap quote tokens for tax tokens & burn 5. Process burn: - Send token to flapBlackHole or Burn address \`\`\` \* Flap Tax Trigger Bot monitors balances and calls \`dispatch()\` when economically viable \* Bot pays gas fees to ensure timely distribution \* Anyone can call dispatch() if they want to pay the gas If dividend contract is deployed (\`dividendBps > 0\`): \`\`\` Token holder balance changes (buy/sell/transfer) ↓ Token contract calls Dividend.setShare(user, newBalance) ↓ Dividend contract updates user's share ↓ Settles any pending rewards before share change \`\`\` \*\*Excluded from dividends:\*\* \* DEX pool addresses \* Token contract itself \* Dividend contract \* Dead address (0x000...dEaD) \* FlapBlackHole \* Zero address \* Addresses with balance below \`minimumShareBalance\` \*\*Manual Claiming:\*\* \`\`\` Token holder calls withdrawDividends() ↓ Dividend contract calculates withdrawable amount ↓ If dividendToken == WETH: Unwraps to ETH and sends If dividendToken == other: Sends ERC20 tokens ↓ Updates withdrawnDividends tracking \`\`\` \*\*Automated Claiming (by Bot):\*\* \`\`\` Flap Tax Trigger Bot monitors pending dividends ↓ When holder's pending > threshold: Bot calls distributeDividend(\[holder1, holder2, ...\]) ↓ Dividend contract sends rewards to holders ↓ Bot pays gas fees \`\`\` \*\*Anyone Can Trigger:\*\* \`\`\` Any external caller can call: - withdrawDividendsFor(user) - claim for specific user - distributeDividend(\[users\]) - batch claim for multiple users - Caller pays gas fees \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers.md). # Wallet & Terminal & Bot Developers - \[A Quick Start For Wallet & Terminal & Bot Developers\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/a-quick-start-for-wallet-terminal-bot-developers.md) - \[Deployed Contract Addresses\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/deployed-contract-addresses.md) - \[Index Token Created Events\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events.md) - \[Bonding Curve In Developers' Perspective\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective.md) - \[Inspect A Token\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token.md) - \[Parse Token Meta\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta.md) - \[Token Version Specification\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification.md) - \[Inspect A Tax Token\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token.md) - \[Trade Tokens\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens.md) - \[Token Migration\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-migration.md) --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/a-quick-start-for-wallet-terminal-bot-developers.md). # A Quick Start For Wallet & Terminal & Bot Developers This page outlines the minimal steps to integrate with Flap for wallets, terminals and bots. Each step links to the detailed documentation. 1. \*\*Find the \`Portal\` entrypoint.\*\* Our main protocol has a single entrypoint contract called \`Portal\`. Use the deployed addresses listed in \[Deployed Contract Addresses\](/flap/developers/wallet-and-terminal-and-bot-developers/deployed-contract-addresses.md). 2. \*\*Index tokens (recommended).\*\* To index tokens launched on Flap, listen to \`TokenCreated\` and related events emitted by \`Portal\`. See \[Index Token Created Events\](/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events.md) for the full event list and indexing strategy. The \`TokenCreated\` event only contains the IPFS CID of the metadata. To parse token metadata, follow \[Parse Token Meta\](/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta.md). 3. \*\*Skip indexing and query on demand (alternative).\*\* If you do not want to index, you can query token info directly from \`Portal\` with the \`getTokenV\*\` methods. See \[Inspect A Token\](/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token.md) for details. 4. \*\*Tax tokens: query dynamic info on-chain.\*\* Tax tokens have dynamic fields (e.g., accumulated stats) that are best retrieved on-chain. Use the Tax Token Helper as described in \[Inspect A Tax Token\](/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token.md). For vault-specific data, see \[how to inspect a tax token's vault\](/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token.md#how-to-inspect-a-tax-tokens-vault). 5. \*\*Trade tokens.\*\* You can quote on-chain using \`quoteExactInput\`, or compute quotes off-chain and submit swaps using \`swapExactInput\`. See \[Trade Tokens\](/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens.md) for details. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v1.md). # Tax Token V1 {% hint style="info" %} Tax Token V1 is still used and maintained. A V1 Tax token is created when you are using the vault mode or when 100% tax to Funds Recipient Wallet. {% endhint %} ## Overview In V1 tax token, all the tax will be sent to a single beneficiary. However, you can build a smart contract as the beneficiary to receive the tax, where you can implement your own logics. We have a recommended interface for such contracts (check \[https://github.com/flap-sh/gitbook/blob/main/developers/basic-and-mechanism/flap-tax-token/flap-tax-vault.md\](https://github.com/flap-sh/gitbook/blob/main/developers/basic-and-mechanism/flap-tax-token/flap-tax-vault.md "mention") for more details) ## Tax Splitter To avoid DOS, we do not liquidate and distribute the Tax in the tax token directly. For each v1 tax token, a "Tax Splitter" contract will be deployed on creation. Both the liquidated Tax (when token is on DEX) and the Tax on the bonding curve will be directly sent to the "Tax Splitter" first. Then anyone could trigger the tax distribution by calling the "Tax Splitter" contract's \`split\` method. However, for most of the time you don't need to manually call "Tax Splitter" yourself , Flap will run a bot to automatically trigger the distribution when needed.
--- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/deployed-contract-addresses.md). # Deployed Contract Addresses See \[Deployed Contract Addresses\](/flap/developers/deployed-contract-addresses.md). --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3.md). # Tax Token V3 {% hint style="info" %} \`FlapTaxTokenV3\` (\`TOKEN\_TAXED\_V3\`) is the recommended token type for all new integrations. It is launched via the \`newTokenV6\` entry point on Portal v5.9.0+. {% endhint %} ## Overview Tax Token V3 introduces four major capabilities over V1/V2: 1. \*\*Asymmetric buy/sell tax rates\*\* — independent rates for buys and sells. 2. \*\*Commission receiver\*\* — a permanent, protocol-enforced fee share for third-party launchpad integrators. 3. \*\*Unified launcher (\`newTokenV6\`)\*\* — single entry point for all current token types. 4. \*\*Bidirectional dynamic liquidation threshold\*\* — self-correcting threshold that prevents permanent drift in either direction. \*\*\* ## 1. Asymmetric buy / sell tax rates \`FlapTaxTokenV3\` supports independent buy and sell tax rates, enabling configurations such as a low buy tax (to encourage accumulation) and a higher sell tax (to discourage dumping), or the reverse. ### Reading tax rates on-chain \`\`\`solidity IFlapTaxTokenV3 token = IFlapTaxTokenV3(tokenAddress); uint16 buy = token.buyTaxRate(); // e.g. 300 (3%) uint16 sell = token.sellTaxRate(); // e.g. 1000 (10%) uint16 eff = token.taxRate(); // max(buy, sell) — backward-compatible single rate \`\`\` \`taxRate()\` always returns \`max(buyTaxRate, sellTaxRate)\`. This preserves compatibility with existing off-chain systems and aggregators that only understand a single tax rate: they will always see the worst-case value and will never under-report the tax. ### Indexing tax rates from events Two events are emitted at launch for every V3 tax token: | Event | Contents | Purpose | | --------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------ | | \`FlapTokenTaxSet(address token, uint256 tax)\` | \`max(buyTax, sellTax)\` | Backward-compatible single-rate event; existing indexers continue to work unchanged. | | \`FlapTokenAsymmetricTaxSet(address token, uint256 buyTax, uint256 sellTax)\` | Both rates | New event for systems that support asymmetric rates. | For symmetric V1/V2 tokens the two events carry the same value. For V3 tokens, always prefer \`FlapTokenAsymmetricTaxSet\` when available. \*\*Example log output (buy=300, sell=1000):\*\* \`\`\` FlapTokenTaxSet(token, 1000) // max of buy and sell FlapTokenAsymmetricTaxSet(token, 300, 1000) // full asymmetric detail \`\`\` {% hint style="info" %} Old indexers that only listen to \`FlapTokenTaxSet\` remain fully functional — they will display the effective (worst-case) rate. {% endhint %} ### Inspecting a token with \`getTokenV8\` The \`getTokenV8\` helper returns the full token state including separate buy and sell tax rates: \`\`\`solidity IPortalLens.TokenStateV8 memory state = portal.getTokenV8(tokenAddress); state.buyTaxRate // e.g. 300 (3%) state.sellTaxRate // e.g. 1000 (10%) state.status // TokenStatus enum (BondingCurve, DEX, …) state.price // current bonding curve price state.progress // progress toward DEX graduation (0–1e18) \`\`\` \`getTokenV8\` is the recommended state query endpoint for any system that needs to display asymmetric tax information. It is a view function and can be called by any EOA or contract. \*\*\* ## 2. Commission receiver ### What it is The commission receiver feature allows any third-party launchpad operator or integrator to earn a \*\*permanent, protocol-enforced fee share\*\* from every tax token they deploy through Flap. Tokens launched with a commission receiver will be visible on Flap and the commission accrues automatically on every taxable transaction (bonding curve buy, DEX buy, and DEX sell), for the entire lifetime of the tax. ### How to enable it Set \`commissionReceiver\` to any non-zero address in \`NewTokenV6Params\`. Only \`TOKEN\_TAXED\_V3\` tokens support commission — passing a non-zero receiver for a non-tax token reverts. \`\`\`solidity IPortalTypes.NewTokenV6Params memory params = IPortalTypes.NewTokenV6Params({ // … other fields … tokenVersion: IPortalTypes.TokenVersion.TOKEN\_TAXED\_V3, commissionReceiver: 0xYourLaunchpadAddress, // receives commission // NOTE: commissionBps is NOT set here — the protocol calculates it }); \`\`\` ### How the commission rate is determined The commission rate is \*\*calculated entirely by the protocol\*\* at token creation time via \`\_commissionForTax(effectiveTaxRate)\`. The integrator cannot specify it. The formula is: \`\`\` effectiveTaxRate = max(buyTaxRate, sellTaxRate) if effectiveTaxRate <= 100 bps (1%): commissionBps = 600 (6% of the post-fee remainder) else: commissionBps = floor(10000 \* 6 / effectiveTaxRate) \`\`\` This means commission is inversely proportional to the tax rate: | Effective tax rate | Commission bps | Commission % | | ------------------ | -------------- | ------------ | | 1% (100 bps) | 600 | 6.0% | | 2% (200 bps) | 300 | 3.0% | | 3% (300 bps) | 200 | 2.0% | | 5% (500 bps) | 120 | 1.2% | | 10% (1000 bps) | 60 | 0.6% | {% hint style="info" %} The maximum possible commission is \*\*6%\*\* of the post-protocol-fee tax remainder, achievable only when the effective tax rate is ≤ 1%. {% endhint %} ### Reading commission configuration \`\`\`solidity ITaxProcessor processor = ITaxProcessor(IFlapTaxTokenV3(token).taxProcessor()); address receiver = processor.commissionReceiver(); // address(0) if disabled uint16 bps = processor.commissionBps(); // rate the protocol set uint256 pending = processor.commissionQuoteBalance(); // accrued, not yet dispatched \`\`\` Call \`processor.dispatch()\` to push accumulated balances (fee, commission, marketing, dividends) to their respective receivers. \*\*\* ## 3. \`newTokenV6\` — unified token launcher \`newTokenV6\` is the single entry point for creating \*\*all\*\* current token types. The specific token implementation is selected via the \`tokenVersion\` field: | \`tokenVersion\` | Enum value | Token type | Allowed tax rates | Commission | | ----------------- | ---------- | --------------------------- | ------------------------------------------ | ----------- | | \`TOKEN\_V2\_PERMIT\` | 1 | Standard ERC-20 with permit | none (must be 0) | not allowed | | \`TOKEN\_TAXED\` | 2 | FlapTaxToken V1 | symmetric only; \`mktBps\` must be 10000 | not allowed | | \`TOKEN\_TAXED\_V2\` | 4 | FlapTaxTokenV2 | symmetric only; \`mktBps\` must not be 10000 | not allowed | | \`TOKEN\_TAXED\_V3\` | \*\*6\*\* | FlapTaxTokenV3 | asymmetric allowed; any valid distribution | supported | {% hint style="warning" %} \`TOKEN\_TAXED\` and \`TOKEN\_TAXED\_V2\` are deprecated and will be removed in a future release. New integrations should always use \`TOKEN\_TAXED\_V3\`. {% endhint %} ### Salt computation When computing the vanity salt off-chain, always use the \*\*\`tokenImplTaxedV3\` implementation address\*\* as the clone base, regardless of the \`mktBps\` or distribution parameters you intend to use: \`\`\`solidity // Always use tokenImplTaxedV3 for TOKEN\_TAXED\_V3 salt vanity search bytes32 salt = \_findVanitySalt(VanityType.VANITY\_7777, tokenImplTaxedV3, portalAddress); \`\`\` ### Launching with a vault: \`IVaultPortal.newTokenV6WithVault\` \`IVaultPortal\` exposes \`newTokenV6WithVault\` for integrators that want to attach a revenue vault to a \`TOKEN\_TAXED\_V3\` token in the same transaction. Only \`TOKEN\_TAXED\_V3\` is accepted — any other \`tokenVersion\` reverts with \`OnlyV3TaxTokenAllowed\`. \`\`\`solidity IVaultPortalTypes.NewTokenV6WithVaultParams memory params = IVaultPortalTypes.NewTokenV6WithVaultParams({ // … same fields as NewTokenV6Params (without beneficiary) … tokenVersion: IPortalTypes.TokenVersion.TOKEN\_TAXED\_V3, vaultFactory: 0xYourVaultFactory, // any factory; unregistered → UNVERIFIED vaultData: abi.encode(/\* vault-specific init data \*/) }); address token = vaultPortal.newTokenV6WithVault(params); \`\`\` The vault address can be retrieved after creation via \`IVaultPortal.getVault(token)\`. ### Dividend token \`FlapTaxTokenV3\` always deploys a Dividend contract, regardless of the \`dividendBps\` value. This means you can launch a token with \`dividendBps == 0\` and still have a fully functional share-tracking contract attached. #### Supported dividend token modes There are three modes for the \`dividendToken\` field: | Mode | \`dividendToken\` value | Description | | ---------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | \*\*Case 1 — Launching Token\*\* | \`0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd\` (\`MAGIC\_DIVIDEND\_SELF\`) | The tax token itself is distributed as dividend. Holders earn more of the same token. | | \*\*Case 2 — Quote Token\*\* | \`address(0)\` (native) or \`quoteToken\` address | The quote token (e.g. WBNB, USDT) is distributed as dividend. This is the simplest and most common setup. | | \*\*Case 3 — Any ERC-20\*\* | Any ERC-20 contract address | Any token with sufficient DEX liquidity can be used. The protocol swaps the collected quote token into the dividend token automatically on every dispatch via \`SwapRegistry\`. | {% hint style="info" %} \*\*\`MAGIC\_DIVIDEND\_SELF\`\*\* (\`0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd\`) is a sentinel address recognised by the Portal. When you pass this value, the protocol resolves it to the actual tax token address after the token is created. {% endhint %} #### Case 3 eligibility — SwapRegistry For Case 3, the protocol checks at launch time that a valid swap path exists from the quote token to the chosen dividend token. If the pair is not supported, the transaction reverts with \`DividendSwapNotSupported\`. You can check eligibility before launching: \`\`\`solidity ISwapRegistry registry = ISwapRegistry(swapRegistryAddress); // Simple boolean check bool supported = registry.isSwapSupported(quoteToken, dividendToken); // Detailed check — returns (supported, errorCode, errorMessage) (bool ok, uint8 code, string memory reason) = registry.isSwapSupportedWithDetailedErrors(quoteToken, dividendToken); \`\`\` \*\*Trust status\*\* — the \`SwapRegistry\` assigns a trust level to every dividend token: | Value | Meaning | | ------ | ----------------------------------------------------------------------------------------------------------- | | \`0x00\` | Unknown — token is not vetted; the Flap UI shows a warning to users | | \`0x01\` | Whitelisted — token is audited/vetted by Flap | | \`0x02\` | Blacklisted — token is known-malicious; dividend conversion is silently skipped (no revert during dispatch) | \`\`\`solidity uint8 trust = registry.getTrustStatus(dividendToken); bool blacklisted = registry.isBlacklisted(dividendToken); \`\`\` #### Tracker-only mode (\`dividendBps == 0\`) When \`dividendBps == 0\`, no tax revenue is automatically routed to the Dividend contract, but the contract is still deployed and tracks holder share balances. This is useful for off-chain reward systems that read share data on-chain but push rewards via their own pipeline. In tracker-only mode, \`dividendToken\` may be \`MAGIC\_DIVIDEND\_SELF\` (\`0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd\`) or any ERC-20 — the swap-path check is skipped because no automatic conversion occurs. Integrators can call \`dividend.deposit(token, amount)\` manually at any time to distribute any ERC-20 to current share holders. \*\*\`dividendBps\` summary:\*\* | \`dividendBps\` | Behaviour | | ------------- | ----------------------------------------------------------------------------------------------------------------------------- | | \`> 0\` | Tax revenue is automatically routed to the Dividend contract. \`dividendToken\` must be Case 1, 2, or an eligible Case 3 token. | | \`== 0\` | No automatic payout. Dividend contract exists and tracks shares only. Any \`dividendToken\` value is accepted. | #### Retrieving the dividend contract \`\`\`solidity address dividendContract = IFlapTaxTokenV3(token).dividendContract(); address sameAddress = ITaxProcessor(IFlapTaxTokenV3(token).taxProcessor()).dividendAddress(); // dividendContract == sameAddress always holds; both are non-zero for all V3 tokens \`\`\` #### Code examples \`\`\`solidity // Case 1 — tax token itself as dividend params.quoteToken = address(0); // BNB params.dividendBps = 500; // 5% params.dividendToken = 0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd; // MAGIC\_DIVIDEND\_SELF // Case 2 — native quote token (address(0) resolves to WBNB) params.quoteToken = address(0); params.dividendBps = 500; params.dividendToken = address(0); // OK — same as quoteToken // Case 2 — ERC-20 quote token params.quoteToken = USDT; params.dividendBps = 500; params.dividendToken = USDT; // must match quoteToken for Case 2 // Case 3 — any ERC-20 (must be supported by SwapRegistry) params.quoteToken = address(0); // BNB params.dividendBps = 500; params.dividendToken = CAKE; // any ERC-20 with a registered swap path // Tracker-only — any dividendToken accepted, no swap-path check params.dividendBps = 0; params.dividendToken = MY\_TOKEN; // no payout, just share tracking \`\`\` #### SwapRegistry deployed addresses | Network | SwapRegistry Address | | --------------------- | -------------------------------------------- | | \*\*BNB Chain Mainnet\*\* | \`0x644A8f560138418bAD4EdEFC7c17878a3c2fBEB6\` | | \*\*BNB Chain Testnet\*\* | \`0xEd62Df92e52b89C8100F8EC3B8Eefea073df3408\` | \*\*\* ## 4. Bidirectional dynamic liquidation threshold ### Background Tax tokens accumulate their own tokens from buy/sell taxes and periodically liquidate (sell) the accumulated balance to the DEX to convert it to the quote token. The \*\*liquidation threshold\*\* governs how many tokens must accumulate before a liquidation is triggered. ### The V1 problem \`FlapTaxTokenV1\` had a one-directional threshold: it could only decrease (making liquidations more frequent) but could never recover upward. In adverse market conditions this caused the threshold to drift to its minimum floor permanently, resulting in constant small liquidations that negatively impacted price. ### V3 fix: bidirectional adjustment \`FlapTaxTokenV3\` introduces a \*\*fully bidirectional\*\* threshold that self-corrects in both directions after every liquidation. The \`TaxProcessor\` records a reference expected output amount (\`liqExpectedOutputAmount\`) at initialization and compares actual DEX swap output against it after each liquidation, returning an \`int8 liqThresholdDirection\` to the token: | \`liqThresholdDirection\` | Market condition | Threshold action | | ----------------------- | ------------------------------------------------- | ----------------------------------------------------------------------- | | \`> 0\` (positive) | Swap output \*\*below\*\* reference — price is weak | Increase threshold by 1% (wait for more tokens before next liquidation) | | \`< 0\` (negative) | Swap output \*\*above\*\* reference — price is strong | Decrease threshold by 1% (liquidate smaller amounts more frequently) | | \`== 0\` | Output matched reference | No change | The threshold is bounded between two implementation-level immutables shared by all clones: \`\`\`solidity FlapTaxTokenV3.MIN\_LIQ\_THRESHOLD // hard floor — threshold never goes below this FlapTaxTokenV3.START\_LIQ\_THRESHOLD // starting value and hard ceiling for recovery \`\`\` An individual token also retains \`initialLiquidationThreshold\` (set to \`START\_LIQ\_THRESHOLD\` at creation), which acts as the recovery ceiling. The threshold gradually climbs back toward \`initialLiquidationThreshold\` as market conditions improve, up to +1% per liquidation event. ### Inspecting the current threshold \`\`\`solidity uint256 threshold = IFlapTaxTokenV3(token).liquidationThreshold(); uint256 initial = IFlapTaxTokenV3(token).initialLiquidationThreshold(); uint256 minLimit = IFlapTaxTokenV3(token).MIN\_LIQ\_THRESHOLD(); uint256 startLimit = IFlapTaxTokenV3(token).START\_LIQ\_THRESHOLD(); \`\`\` \*\*\* ## 5. Deployed contract addresses ### \`FlapTaxTokenV3\` implementation (clone base) Use this address when computing vanity salts off-chain. | Network | Address | | --------------------- | -------------------------------------------- | | \*\*BNB Chain Mainnet\*\* | \`0x024f18294970B5c76c0691b87f138A0317156422\` | | \*\*BNB Chain Testnet\*\* | \`0xE6Ff967a887084c16D0fD71548CF709542cc1557\` | ### Example: launching a \`TOKEN\_TAXED\_V3\` token \`\`\`solidity address tokenImplTaxedV3 = 0x024f18294970B5c76c0691b87f138A0317156422; // BSC mainnet bytes32 salt = \_findVanitySalt(VanityType.VANITY\_7777, tokenImplTaxedV3, address(portal)); IPortalTypes.NewTokenV6Params memory params = IPortalTypes.NewTokenV6Params({ name: "My Token", symbol: "MTK", meta: "ipfs://...", dexThresh: IPortalCommonTypes.DexThreshType.FOUR\_FIFTHS, salt: salt, migratorType: IPortalTypes.MigratorType.V2\_MIGRATOR, quoteToken: address(0), // BNB quoteAmt: 0, beneficiary: msg.sender, permitData: "", extensionID: bytes32(0), extensionData: "", dexId: IPortalTypes.DEXId.DEX0, lpFeeProfile: IPortalTypes.V3LPFeeProfile.LP\_FEE\_PROFILE\_STANDARD, buyTaxRate: 300, // 3% buy tax sellTaxRate: 1000, // 10% sell tax (asymmetric) taxDuration: 365 days, antiFarmerDuration: 1 days, mktBps: 10000, // 100% of remainder → beneficiary deflationBps: 0, dividendBps: 0, lpBps: 0, minimumShareBalance: 0, dividendToken: address(0), // native → resolved to WETH commissionReceiver: 0xYourAddress, // enable commission tokenVersion: IPortalTypes.TokenVersion.TOKEN\_TAXED\_V3 }); address token = portal.newTokenV6(params); \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/basic-and-mechanism/bonding-curve.md). # Bonding Curve {% hint style="info" %} Since v2.4.0, Flap supports multiple bonding curves, but only one is active for a quote token. {% endhint %} ### What is a Bonding Curve Before migrating to DEX, each token launched on flap is bonded to a bonding curve. When you buy tokens from the bonding curve, you send your ETH ( or other quote token: BNB, USDT etc) to the bonding curve as a reserve, and the bonding curve mints tokens to your address. Or if you sell your tokens to the bonding curve, the bonding curve will burn your selling tokens and send the ETH back to you. {% hint style="info" %} Under the hood, we don't implement it as burn & mint. Initially, all the tokens are in our bonding curve contract until the user buys 80% of the total supply and the remaining token will be added to DEX as liquidity. {% endhint %}
A bonding curve defines the relationship between the trading token's supply and the reserve (i.e. Quote tokens like ETH or BNB). The change of the reserve respect to the supply is the price. Our bonding curve is based on a constant product equation. You may have heard about this equation, which Uniswap popularized. You may even wonder what is the difference between a bonding curve token launching platform like flap and Unsiwap? The answer is the liquidity. The liquidity does not change on the bonding curve. However, anyone can add liquidity to the Uniswap pools. ### Flap's Bonding Curve V2 The token created on our platform has the same max supply of $$10^9$$ , with 18 decimals. For each token, the amounts of token and the Quote (ETH, BNB or USD\\\*) follow the following constant product equation: $$ (x+ h)(y + r) = K $$ $$r$$ ,$$h$$ and $$K$$ are constant parameters dependent on the target chain ( check \[#bonding-curves-on-different-chains\](#bonding-curves-on-different-chains "mention") for more details). $$ (x+ h)(y + r) = K $$ \* $$x$$ is the amount of token in the bonding curve (i.e: token reserve), initially, it is $$10^9$$, which means all the tokens are in the bonding curve in the beginning. \* $$y$$ is the amount of quote token (i.e: quote reserve) , it is 0 in the beginning. \* Our curve have 3 constant parameters: \* $$r$$ can be interpreted as the virtual reserve of quote token in the bonding curve \* $$h$$ can be interpreted as the virtual reserve of the launched token in the bonding curve \* $$K$$ is the square of the virtual liquidity ### Bonding Curves On Different Chains {% hint style="danger" %} The following table may be out of date.\\ \\ We don't recommend that you hardcode the bonding curve parameters in your application. Instead, you should use \`getTokenV5\` method from the Portal contract to get the parameters for each token. The parameters are immutable for each token, so you only need to fetch them once and cache them in your application. {% endhint %} For different and different quote token , the constants are different:
ChainrhK
BSC (Legacy before block #42042177 )15015 \\cdot 10^{9}
BSC (Legacy since block 42042177 and before 47855189 )404 \\cdot 10^{9}
BSC (Legacy since block 47855189 and before 51314696)202 \\cdot 10^{9}
BSC(before 61837444, BNB as payment Token)404 \\cdot 10^{9}
BSC(before 61837444, USD1/lisUSD as payment token)250002500 \\cdot 10^{9}
BSC(lastest, BNB as payment Token)6.141070367526797205657.28
BSC(latest, USD1/lisUSD as payment token)38371070367524247700017424
ToshiMart0.500.5 \\cdot 10^{9}
XLayer (Before block 31564005)28028 \\cdot 10^{9}
XLayer (Before block 32470187)21.25021.25 \\cdot 10^{9}
XLayer(Since block 32470187)28.2510800212631301060059
Monad (mainnet)5000010703675255351837600000
### Example Curve BNB {% embed url="" %} --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta.md). # Parse Token Meta Whenever a token is launched, a \`TokenCreated\` event will be emitted: \`\`\`solidity /// @notice emitted when a new token is created /// /// @param ts The timestamp of the event /// @param creator The address of the creator /// @param nonce The nonce of the token /// @param token The address of the token /// @param name The name of the token /// @param symbol The symbol of the token /// @param meta The meta URI of the token event TokenCreated( uint256 ts, address creator, uint256 nonce, address token, string name, string symbol, string meta ); \`\`\` The \`TokenCreated\` event contains the IPFS CID of the token’s metadata, which can be used to retrieve the metadata from IPFS. Alternatively, you can also get the metadata through the token’s contract by calling the \`metaURI\` method of the token contract. The \`meta\` method returns the IPFS CID of the token’s metadata. \`\`\`solidity interface IFlapToken { function metaURI() external view returns (string memory); } \`\`\` Let’s take the JACK token as an example to illustrate how to get the image: If you call the \`metaURI\` method of the 0x4122a43daecd13cfb2543ad339470fd87bc11111 \[token\](https://x.flap.sh/0x4122a43daecd13cfb2543ad339470fd87bc11111), you will get the following cid: \`bafkreieepx7lh6zcpqsneo2jdeqmcgxjg7facam34w5ezh4myajjauuc24\`. You can get the meta json from any ipfs gateway. However, it would be fast to use our ipfs gateway. Our gateway is located at . Such that, you can get the meta json here: \`https://flap.mypinata.cloud/ipfs/bafkreieepx7lh6zcpqsneo2jdeqmcgxjg7facam34w5ezh4myajjauuc24\`. The meta json is as follows: \`\`\`json { "buy": null, "creator": "0x7F403F924dDE32bFd4D4432E3996291aeFCe2f89", "description": "OKIE,OKIE", "image": "bafkreibwjuzzns6yf4wytfr5nk6vujb4yja4iejff2k76nshn2z5jkmiy4", "sell": null, "telegram": null, "twitter": null, "website": null } \`\`\` Note that the image field is also a cid, which is the cid of the image. Similarly, we can get the image here: \`https://flap.mypinata.cloud/ipfs/bafkreibwjuzzns6yf4wytfr5nk6vujb4yja4iejff2k76nshn2z5jkmiy4\`. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/index-token-created-events.md). # Index Token Created Events This page describes how to index newly launched tokens by consuming events emitted by the \`Portal\` contract. ## Overview For backward compatibility, a token launch can emit multiple events instead of a single one. Always index \`TokenCreated\`, then enrich the token record with any optional events that appear in the same transaction. {% hint style="info" %} \`TokenCreated\` only includes the IPFS CID of the metadata. To resolve and parse token metadata, see \[Parse Token Meta\](/flap/developers/wallet-and-terminal-and-bot-developers/parse-token-meta.md). {% endhint %} ## Events to index \*\*Required\*\* \* \`TokenCreated\`: emitted for every token launch. \*\*Optional (apply defaults if missing)\*\* \* \`TokenCurveSet\`: if missing, curve defaults to the first item in \`CurveType\` (legacy curve, \`curveParameter = 16 ether\`). \* \`TokenCurveSetV2\`: starting from v4.7.0, always emitted even for legacy curve. \* \`TokenDexSupplyThreshSet\`: if missing, defaults to the first item in \`DexThreshType\` (6.67e8 ether). \* \`TokenQuoteSet\`: if missing, defaults to native gas token (zero address). \* \`TokenMigratorSet\`: if missing, defaults to \`V3\_MIGRATOR\`. \* \`TokenVersionSet\`: if missing, defaults to legacy token version (see \[Token version specification\](/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification.md)). \* \`FlapTokenTaxSet\`: if missing, tax is 0 (non-tax token). \* \`FlapTokenStaged\`: emitted when a token is staged but not yet created (two-step token launch). \* \`TokenExtensionEnabled\`: emitted when an extension is enabled for a token. \* \`TokenDexPreferenceSet\`: if missing, defaults to DEX0 with STANDARD fee profile. \* \`FlapTokenAsymmetricTaxSet\`: emitted alongside \`FlapTokenTaxSet\` for Tax Token V3 launches; carries separate buy and sell tax rates. If missing, treat buy and sell rates as equal to the value from \`FlapTokenTaxSet\`. ## Event reference (arguments and meaning) ### \`TokenCreated\` Emitted for every token launch. \* \`ts\`: block timestamp when the token is created. \* \`creator\`: address that initiated the token creation. \* \`nonce\`: portal nonce for this creation (unique per \`Portal\`). \* \`token\`: deployed token address. \* \`name\`: token name. \* \`symbol\`: token symbol. \* \`meta\`: IPFS CID of the token metadata JSON. ### \`FlapTokenStaged\` Emitted when a token is staged (two-step launch) but not yet created. \* \`ts\`: block timestamp when staging happens. \* \`creator\`: address that staged the token. \* \`token\`: predetermined token address (not yet deployed). ### \`TokenCurveSet\` Emitted when the bonding curve configuration is set for legacy curve format. \* \`token\`: token address. \* \`curve\`: curve contract address. \* \`curveParameter\`: curve parameter for the legacy curve (defaults to \`16 ether\` if missing). ### \`TokenCurveSetV2\` Emitted when the bonding curve parameters are set in the newer format. \* \`token\`: token address. \* \`r\`: virtual ETH reserve parameter. \* \`h\`: virtual token reserve parameter. \* \`k\`: square of virtual liquidity parameter. ### \`TokenDexSupplyThreshSet\` Emitted when the DEX listing supply threshold is set. \* \`token\`: token address. \* \`dexSupplyThresh\`: circulating supply threshold for DEX listing (defaults to the first \`DexThreshType\` if missing). ### \`TokenQuoteSet\` Emitted when the quote token is set. \* \`token\`: token address. \* \`quoteToken\`: quote token address (zero address means native gas token). ### \`TokenMigratorSet\` Emitted when the migrator type is set. \* \`token\`: token address. \* \`migratorType\`: migrator enum value (\`V3\_MIGRATOR\` or \`V2\_MIGRATOR\`). ### \`TokenVersionSet\` Emitted when the token implementation version is set. \* \`token\`: token address. \* \`version\`: token version enum value (see \[Token version specification\](/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification.md)). ### \`FlapTokenTaxSet\` Emitted when a tax is set for a token. \* \`token\`: token address. \* \`tax\`: tax rate in basis points (0 means non-tax token). ### \`TokenExtensionEnabled\` Emitted when an extension is enabled for a token. \* \`token\`: token address. \* \`extensionID\`: extension identifier (bytes32). \* \`extensionAddress\`: extension contract address. \* \`version\`: extension interface version. ### \`TokenDexPreferenceSet\` Emitted when DEX preference and fee profile are set. \* \`token\`: token address. \* \`dexId\`: preferred DEX ID (\`DEX0\`, \`DEX1\`, \`DEX2\`). \* \`lpFeeProfile\`: preferred V3 LP fee profile (\`STANDARD\`, \`LOW\`, \`HIGH\`). ### \`FlapTokenAsymmetricTaxSet\` Emitted for Tax Token V3 (\`TOKEN\_TAXED\_V3\`) launches alongside \`FlapTokenTaxSet\`. Carries the full asymmetric buy and sell tax rates. \* \`token\`: token address. \* \`buyTax\`: buy tax rate in basis points. \* \`sellTax\`: sell tax rate in basis points. {% hint style="info" %} \`FlapTokenTaxSet\` is always emitted for all tax tokens and carries \`max(buyTax, sellTax)\` for backward compatibility. \`FlapTokenAsymmetricTaxSet\` is only emitted for V3 tokens. Prefer \`FlapTokenAsymmetricTaxSet\` when available to get the full asymmetric rate detail. {% endhint %} ## Suggested indexing flow 1. Listen to \`TokenCreated\` events on \`Portal\`. 2. In the same transaction, collect optional events for the same token address. 3. Apply defaults for any missing optional events. 4. Persist the token record and metadata CID. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/bonding-curve-in-developers-perspective.md). # Bonding Curve In Developers' Perspective ## How to find the reserve/price/fdv from the circulating supply of a token? ### Option1: Read from the Chain You can directly read the information about a token through the \`getTokenV2\` call to the on chain contract. \`\`\`solidity /// @notice Get token state (V5) /// @param token The address of the token /// @return state The state of the token (V5) with all curve parameters (r, h, k) function getTokenV5(address token) external view returns (TokenStateV5 memory state); /// @dev Token version /// Which token implementation is used enum TokenVersion { TOKEN\_LEGACY\_MINT\_NO\_PERMIT, TOKEN\_LEGACY\_MINT\_NO\_PERMIT\_DUPLICATE, // for historical reasons, both 0 and 1 are the same: TOKEN\_LEGACY\_MINT\_NO\_PERMIT TOKEN\_V2\_PERMIT, TOKEN\_GOPLUS } /// @notice the status of a token /// The token has 4 statuses: // - Tradable: The token can be traded(buy/sell) // - InDuel: (obsolete) The token is in a battle, it can only be bought but not sold. // - Killed: (obsolete) The token is killed, it can not be traded anymore. Can only be redeemed for another token. // - DEX: The token has been added to the DEX enum TokenStatus { Invalid, // The token does not exist Tradable, InDuel, // obsolete Killed, // obsolete DEX } /// @notice the state of a token (with all V4 fields plus all curve parameters) struct TokenStateV5 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; } \`\`\` ### Option 2: Derive price/fdv/reserve from the current circulating supply {% hint style="warning" %} Check \[Bonding Curve\](/flap/developers/basic-and-mechanism/bonding-curve.md#bonding-curves-on-different-chains)for the r of each deployment\\ \\ We don't recomend that you hardcode the bonding curve parameters in your application. Instead, you should use \`getTokenV5\` method from the Portal contract to get the parameters for each token. The parameters are immutable for each token, so you only need to fetch them once and cache them in your application. {% endhint %} Whenever a user buys or sells token on the bonding curve, the following event would be emitted: \`\`\`solidity /// @notice emitted when the circulating supply of a token changes /// @param token The address of the token /// @param newSupply The new circulating supply event FlapTokenCirculatingSupplyChanged(address token, uint256 newSupply); \`\`\` You can simply index this event to get the latest circulating supply of any token. With the circulating supply of the token, you can get the reserve, price and FDV with the following \`typescript\` snippet: \`\`\`typescript import { Decimal } from "decimal.js"; const BILLION: Decimal = new Decimal("1000000000"); // The latest curve is CDPV2 export class CDPV2 { // the initial virtual reserve private r: number; private h: number; private k: number; static defaultDexSupplyThreshold() { return new Decimal(8e8); } static getCurve(r: number, h?: number, k?: number): CDPV2 { if (h == null) { return new CDPV2(r, 0, 1e9 \* r); } return new CDPV2(r, h, k); } constructor(r: number, h: number = 0, k: number = 0) { this.r = r; this.h = h; this.k = k; } estimateSupply(reserve: string): Decimal { // s = 1e9 + h - k/(r + eth) if (!reserve) return new Decimal(0); return new Decimal(BILLION).add(this.h).sub( new Decimal(this.k).div(new Decimal(reserve).add(this.r)) ); } estimateReserve(amount: string): Decimal { // eth = k/(h + 1e9 - s) - r if (!amount) return new Decimal(0); return new Decimal(this.k) .div(new Decimal(BILLION).add(this.h).sub(new Decimal(amount))) .sub(this.r); } mc(reserve: string): Decimal { return this.fdv(this.totalSupply(reserve).toString()); } price(supply: string): Decimal { // Price: k/(h + 1e9 - s)^2 const denominator = new Decimal(BILLION).add(this.h).sub(new Decimal(supply || 0)); return new Decimal(this.k).div(denominator.pow(2)); } fdv(supply: string): Decimal { return this.price(supply).mul(new Decimal(BILLION)); } } \`\`\` ## How to calculate the progress of a token The progress is defined as the ratio of the current reserve to the required reserve for the token to migrate. You need the following information to calculate the progress: \* \`Circulating Supply\`: The current circulating supply of the token. You can either index the \`FlapTokenCirculatingSupplyChanged\` event, or use the \`getTokenV5\` method to get the \`circulatingSupply\` of the token, or simply use the \`balanceOf(portal)\` to estimate the circulating supply (This estimation is accurate as long as no one directly sends token to our contract). \* \`Curve Parameters\`: You can use the \`getTokenV5\` to get the curve parameters: \`r\`, \`h\` and \`k\`. Or you can index the \`TokenCurveSetV2\` event. The curve parameters are immutable for a token, you only need to get them once. \* \`Dex Threshold\`: You can use the \`getTokenV5\` to get the \`dexThreshold\` of the token, or index the \`TokenDexThresholdSet\` event. The dex threshold is immutable, you only need to get it once. It represents the circulating supply at which the token will be migrated. The latter two variables are immutable for a token, you only need to get them once. We highly recommend that you to use the the \`getTokenV5\` method to get all the parameters at once. With all the above information, you can calculate the prorgress of the token: \* We first construct a \`Curve\` instance using the curve parameters. Note the curve parameteres are in 18 decimals, so we need to divide them by \`1e18\` to get the actual values (or use formatEther from viem). \* We first calculate the \`required reserve\` for the token to migrate \* Then we derive the \`current reserve\` \* The progress is the ratio betweeen \`current reserve\` and \`required reserve\` \`\`\`typescript // assume we have all the following parameters from getTokenV5 // Note: all parameters are in 18 decimals // curve parameters let r,h,k: bigint; // circulating supply let circulatingSupply: bigint; // dex threshold let dexThreshold: bigint; const curve = CDPV2.getCurve( Number( formatEther(r) ), Number( formatEther(h) ), Number( formatEther(k) ) ); // The reserve required at the dex threshold const reserveRequired = curve.estimateReserve( formatEther(dexThreshold) ); // The current reserve at the current circulating supply const currentReserve = curve.estimateReserve( formatEther(circulatingSupply) ); // The progress can be calculated as: const progress = currentReserve / reserveRequired; \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-token.md). # Inspect A Token ## Inspect A Token We have multiple methods for querying the state of a token. The latest ones are \`getTokenV8\` and \`getTokenV8Safe\` (currently only available on BNB mainnet and BNB testnet). For other networks, use \`getTokenV7\`. To avoid the broken code problem, we still reserve the legacy methods \`getTokenV2\` and \`getTokenV3\`. We always recommend using the newest available method for your network. \`\`\`solidity /// @notice the state of a token (with dex related fields) struct TokenStateV2 { TokenStatus status; // the status of the token uint256 reserve; // the reserve of the token uint256 circulatingSupply; // the circulatingSupply of the token uint256 price; // the price of the token TokenVersion tokenVersion; // the version of the token implementation this token is using uint256 r; // the r of the curve of the token uint256 dexSupplyThresh; // the cirtulating supply threshold for adding the token to the DEX } /// @notice the state of a token (with all V2 fields plus quoteTokenAddress and nativeToQuoteSwapEnabled) struct TokenStateV3 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; } /// @notice the state of a token (with all V3 fields plus extensionID and 'r' curve parameter only) struct TokenStateV4 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; } /// @notice the state of a token (with all V4 fields plus all curve parameters) struct TokenStateV5 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; } /// @notice the state of a token (with all V5 fields plus taxRate, pool, and progress) struct TokenStateV6 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The tax rate in basis points (0 if not a tax token) uint256 taxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; } /// @notice the state of a token (with all V6 fields plus lpFeeProfile) struct TokenStateV7 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The tax rate in basis points (0 if not a tax token) uint256 taxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; /// The V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; /// The Dex Id DEXId dexId; } struct TokenStateV8 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The buy tax rate in basis points (0 if not a tax token) uint256 buyTaxRate; /// The sell tax rate in basis points (0 if not a tax token) uint256 sellTaxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; /// The V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; /// The Dex Id DEXId dexId; } /// @notice A forward-compatible version of TokenStateV8, where enum-typed fields are returned /// as uint8 instead of their Solidity enum types. struct TokenStateV8Safe { uint8 status; uint256 reserve; uint256 circulatingSupply; uint256 price; uint8 tokenVersion; uint256 r; uint256 h; uint256 k; uint256 dexSupplyThresh; address quoteTokenAddress; bool nativeToQuoteSwapEnabled; bytes32 extensionID; uint256 buyTaxRate; uint256 sellTaxRate; address pool; uint256 progress; uint8 lpFeeProfile; uint8 dexId; } /// @notice Get token state /// @param token The address of the token /// @return state The state of the token function getTokenV2(address token) external view returns (TokenStateV2 memory state); /// @notice Get token state (V3) /// @param token The address of the token /// @return state The state of the token (V3) function getTokenV3(address token) external view returns (TokenStateV3 memory state); /// @notice Get token state (V4) /// @param token The address of the token /// @return state The state of the token (V4) with only 'r' curve parameter function getTokenV4(address token) external view returns (TokenStateV4 memory state); /// @notice Get token state (V5) /// @param token The address of the token /// @return state The state of the token (V5) with all curve parameters (r, h, k) function getTokenV5(address token) external view returns (TokenStateV5 memory state); /// @notice Get token state (V6) /// @param token The address of the token /// @return state The state of the token (V6) with all V5 fields plus taxRate, pool, and progress function getTokenV6(address token) external view returns (TokenStateV6 memory state); /// @notice Get token state (V7) /// @param token The address of the token /// @return state The state of the token (V7) with all V6 fields plus lpFeeProfile function getTokenV7(address token) external view returns (TokenStateV7 memory state); /// @notice Get token state (V8) /// @param token The address of the token /// @return state The state of the token (V8) with asymmetric buyTaxRate and sellTaxRate function getTokenV8(address token) external view returns (TokenStateV8 memory state); /// @notice Get token state (V8Safe) /// @dev Returns enum-typed fields (TokenStatus, TokenVersion, V3LPFeeProfile, DEXId) as uint8 /// instead of their Solidity enum types, preventing ABI-decoding reverts when new enum /// variants are introduced. Use this method when you need forward/backward compatibility /// with future contract upgrades that may add new enum values. /// @param token The address of the token /// @return state The state of the token with enum fields encoded as uint8 function getTokenV8Safe(address token) external view returns (TokenStateV8Safe memory state); /// @dev Token version /// Which token implementation is used enum TokenVersion { TOKEN\_LEGACY\_MINT\_NO\_PERMIT, TOKEN\_LEGACY\_MINT\_NO\_PERMIT\_DUPLICATE, // for historical reasons, both 0 and 1 are the same: TOKEN\_LEGACY\_MINT\_NO\_PERMIT TOKEN\_V2\_PERMIT, // 2 TOKEN\_GOPLUS, // 3 TOKEN\_TAXED, // 4: The original tax token (FlapTaxToken) TOKEN\_TAXED\_V2 // 5: The new advanced tax token (FlapTaxTokenV2) } /// @dev the quote token, i.e, the token as the reserve enum QuoteTokenType { NATIVE\_GAS\_TOKEN, // The native gas token ERC20\_TOKEN\_WITH\_PERMIT, // The ERC20 token with permit ERC20\_TOKEN\_WITHOUT\_PERMIT // The ERC20 token without permit } /// @notice the status of a token /// The token has 5 statuses: // - Tradable: The token can be traded(buy/sell) // - InDuel: (obsolete) The token is in a battle, it can only be bought but not sold. // - Killed: (obsolete) The token is killed, it can not be traded anymore. Can only be redeemed for another token. // - DEX: The token has been added to the DEX // - Staged: The token is staged but not yet created (address is predetermined) enum TokenStatus { Invalid, // The token does not exist Tradable, InDuel, // obsolete Killed, // obsolete DEX, Staged // The token is staged (address determined, but not yet created) } /// @notice the V3 LP fee profile /// @dev determines the LP fee tier to use when migrating tokens to Uniswap V3 or Pancake V3 enum V3LPFeeProfile { LP\_FEE\_PROFILE\_STANDARD, // Standard fee tier: 0.25% on PancakeSwap, 0.3% on Uniswap LP\_FEE\_PROFILE\_LOW, // Low fee tier: typically, 0.01% on PancakeSwap, 0.05% on Uniswap LP\_FEE\_PROFILE\_HIGH // High fee tier (1% for exotic pairs) } /// @notice the DEX ID /// @dev determines the DEX we want to migrate to /// On BSC: /// - only DEX0 will be enabled, which is PancakeSwap /// On xLayer: /// - only DEX0 will be enabled, which is PotatoSwap /// On Monad: /// - DEX0 is Uniswap /// - DEX1 is PancakeSwap /// - DEX2 is Monday /// Note that, currently, we only support at most 3 DEXes /// We may add more DEXes in the future if needed enum DEXId { DEX0, DEX1, DEX2 } \`\`\` ## What is the difference between getTokenV2, getTokenV3, getTokenV4, getTokenV5, getTokenV6, getTokenV7, getTokenV8 and getTokenV8Safe? Each version of the \`getToken\` method returns progressively more information about a token's state. The protocol maintains backward compatibility by preserving all legacy methods while recommending the latest available version for new integrations. ### Version Comparison Table | Version | New Fields Added | Use Case | | ---------- | ------------------------------------------------ | ------------------------------------------------ | | \*\*V2\*\* | Base fields only | Legacy - basic token state | | \*\*V3\*\* | \`quoteTokenAddress\`, \`nativeToQuoteSwapEnabled\` | Multi-quote token support | | \*\*V4\*\* | \`extensionID\` | Extension/plugin support (e.g., Farcaster) | | \*\*V5\*\* | \`h\`, \`k\` | Complete bonding curve parameters | | \*\*V6\*\* | \`taxRate\`, \`pool\`, \`progress\` | Tax tokens, DEX pool info, progress tracking | | \*\*V7\*\* | \`lpFeeProfile\`, \`dexId\` | Multi-DEX support with custom fee tiers | | \*\*V8\*\* | \`buyTaxRate\`, \`sellTaxRate\` (replaces \`taxRate\`) | Asymmetric tax rates for Tax Token V3 | | \*\*V8Safe\*\* | Same as V8, but enum fields returned as \`uint8\` | Forward-compatible; safe against new enum values | ### Recommendation Use \`getTokenV8\` / \`getTokenV8Safe\` on BNB mainnet and BNB testnet. For other networks, use \`getTokenV7\`. \`getTokenV8Safe\` is recommended when you need forward/backward compatibility with future contract upgrades that may add new enum variants. \`getTokenV5\` is supported on all mainnets (BSC, xLayer, Monad, Morph). \`V6\` is available on all mainnets except Morph, while \`V7\` is available on all mainnets except Morph and Base. {% hint style="info" %} \`getTokenV8\` and \`getTokenV8Safe\` are currently only available on BNB mainnet and BNB testnet. {% endhint %} We will gradually make \`getTokenV8\` and \`getTokenV8Safe\` available on other mainnets in the future. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/token-launcher-developers.md). # Token Launcher Developers - \[Quick start for token launcher developers\](https://docs.flap.sh/flap/developers/token-launcher-developers/quick-start-token-launcher-developers.md) - \[Deployed Contract Addresses\](https://docs.flap.sh/flap/developers/token-launcher-developers/deployed-contract-addresses.md) - \[Launch token through Portal\](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal.md) - \[Launch token through VaultPortal\](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal.md) - \[Registered vaults\](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults.md) --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/token-launcher-developers/deployed-contract-addresses.md). # Deployed Contract Addresses See \[Deployed Contract Addresses\](/flap/developers/deployed-contract-addresses.md). --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/media-kit.md). # Media Kit {% file src="/files/OjipfsRyYhJSzS2Dzqpt" %} --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/vault-developers/tutorials.md). # Tutorials Step-by-step guides for building specific vault features. \* \[Using an LP Token or Child Token as the Dividend Token\](/flap/developers/vault-developers/tutorials/lp-token-as-dividend.md) --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/inspect-a-tax-token.md). # Inspect A Tax Token ## Overview You can get the tax info of any tax token using the Tax Token Helper contract. Our website uses this contract to get tax info for tokens and show them to users:
## Tax Token Helper | Chain | Tax Token Helper | | ----------------- | ------------------------------------------ | | BNB Mainnet | 0x53841c73217735F37BC1775538b03b23feFD8346 | | BNB Testnet | 0xD64441e5FcD02D342B8cf6eBA10Ef6E40d0dA90f | | Robinhood Mainnet | 0xb10bD2672aE63735d677164A54B573a016f0203C | \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity =0.8.24; /// @title ITaxTokenHelper /// @notice Interface for the TaxTokenHelper contract that provides utilities for interacting with tax tokens /// @dev This helper supports both legacy (TOKEN\_TAXED) and new (TOKEN\_TAXED\_V2) tax token versions interface ITaxTokenHelper { /// @notice Structure containing comprehensive tax token configuration and statistics /// @dev All BPS values are in basis points (1 BPS = 0.01%) struct TaxTokenInfo { /// @notice Marketing tax allocation in basis points (0-10000) uint16 marketBps; /// @notice Deflation (burn) tax allocation in basis points (0-10000) uint16 deflationBps; /// @notice Liquidity provision tax allocation in basis points (0-10000) uint16 lpBps; /// @notice Dividend distribution tax allocation in basis points (0-10000) uint16 dividendBps; /// @notice Total tax rate applied to transfers in basis points (sum of all allocations) uint16 taxRate; /// @notice Cumulative amount of tokens burnt (sent to black hole and dead addresses) uint256 burntTokenAmount; /// @notice Cumulative amount of quote tokens distributed to dividend holders uint256 totalQuoteSentToDividend; /// @notice Cumulative amount of quote tokens added to liquidity pools uint256 totalQuoteAddedToLiquidity; /// @notice Cumulative amount of tax tokens added to liquidity pools uint256 totalTokenAddedToLiquidity; /// @notice Cumulative amount of quote tokens sent to marketing wallet uint256 totalQuoteSentToMarketing; /// @notice Address receiving marketing tax allocations address marketingWallet; /// @notice Address of the quote token used for tax swaps (address(0) for native token) address quoteToken; /// @notice Minimum token balance required to receive dividend distributions uint256 minimumShareBalance; } /// @notice Information about the marketing wallet or vault associated with a tax token struct MarketingWalletInfo { /// @notice The marketing wallet address, which could be a plain EOA, a Flap Vault, or any smart contract address addr; /// @notice The vault factory address that created this vault. /// Zero if the marketing wallet is not a Flap Vault or the factory is unknown. address factory; /// @notice Risk level as assessed by Flap auditors. /// UNVERIFIED(0), LOW\_RISK(1), LOW\_MEDIUM\_RISK(2), MEDIUM\_RISK(3), HIGH\_RISK(4) uint8 riskLevel; /// @notice Whether the vault is an official Flap-endorsed vault bool isOfficialVault; /// @notice Whether the marketing wallet is a registered Flap Vault (via VaultPortal) bool isVault; /// @notice Whether the marketing wallet is an AI consumer. /// True when the vault category is TYPE\_AI\_ORACLE\_POWERED, or when the address /// is a non-EOA contract that has made at least one request via FlapAIProvider. bool isAIConsumer; } /// @notice Extended tax token info with vault and AI oracle metadata. /// Prefer this over TaxTokenInfo for new integrations. struct TaxTokenInfoV2 { /// @notice Marketing tax allocation in basis points (0-10000) uint16 marketBps; /// @notice Deflation (burn) tax allocation in basis points (0-10000) uint16 deflationBps; /// @notice Liquidity provision tax allocation in basis points (0-10000) uint16 lpBps; /// @notice Dividend distribution tax allocation in basis points (0-10000) uint16 dividendBps; /// @notice Tax rate applied on buys, in basis points (e.g., 500 = 5%) uint16 buyTaxRate; /// @notice Tax rate applied on sells, in basis points (e.g., 500 = 5%). /// Currently equal to buyTaxRate as the contract uses a single taxRate. uint16 sellTaxRate; /// @notice Cumulative amount of tokens burnt (sent to black hole and dead addresses) uint256 burntTokenAmount; /// @notice Cumulative amount of quote tokens distributed to dividend holders uint256 totalQuoteSentToDividend; /// @notice Cumulative amount of quote tokens added to liquidity pools uint256 totalQuoteAddedToLiquidity; /// @notice Cumulative amount of tax tokens added to liquidity pools uint256 totalTokenAddedToLiquidity; /// @notice Cumulative amount of quote tokens sent to marketing wallet uint256 totalQuoteSentToMarketing; /// @notice The token distributed as dividends to shareholders. /// address(0) means the native gas token (e.g., BNB or ETH); /// any other address is the ERC20 reward token. address dividendToken; /// @notice Address of the quote token used for tax swaps (address(0) for native token) address quoteToken; /// @notice Minimum token balance required to receive dividend distributions uint256 minimumShareBalance; /// @notice Information about the marketing wallet / vault that receives tax revenue MarketingWalletInfo vaultInfo; } /// @notice Returns the version of the TaxTokenHelper contract /// @return Version string in semantic versioning format function version() external pure returns (string memory); /// @notice Retrieves comprehensive information about a tax token's configuration and statistics /// @dev Returns zero/empty values for non-tax tokens (TOKEN\_V2\_PERMIT) /// @dev For legacy TOKEN\_TAXED tokens, only marketBps, taxRate, marketingWallet, quoteToken, and totalQuoteSentToMarketing are populated /// @dev For TOKEN\_TAXED\_V2 tokens, all fields are populated based on TaxProcessor state /// @param taxToken Address of the tax token to query /// @return info TaxTokenInfo struct containing all tax token configuration and statistics function getTaxTokenInfo(address taxToken) external view returns (TaxTokenInfo memory info); /// @notice Retrieves extended tax token info including vault and AI oracle metadata /// @dev Returns zero/empty values for non-tax tokens (TOKEN\_V2\_PERMIT) /// @dev For legacy TOKEN\_TAXED tokens, marketBps, buyTaxRate/sellTaxRate, vaultInfo.addr, quoteToken, and totalQuoteSentToMarketing are populated /// @dev For TOKEN\_TAXED\_V2 tokens, all fields including vaultInfo are populated /// @param taxToken Address of the tax token to query /// @return info TaxTokenInfoV2 struct containing extended tax token configuration, statistics, and vault metadata function getTaxTokenInfoV2(address taxToken) external view returns (TaxTokenInfoV2 memory info); /// @notice Retrieves dividend information for a specific user and tax token /// @dev Returns (0, 0) if the tax token has no dividend contract configured /// @param taxToken Address of the tax token /// @param user Address of the user to query dividend information for /// @return claimed Total amount of dividends already claimed by the user /// @return pending Amount of dividends currently available for withdrawal function getDividendInfo(address taxToken, address user) external view returns (uint256 claimed, uint256 pending); /// @notice Claims accumulated dividends for the caller (msg.sender) /// @dev Reverts if the tax token has no dividend contract configured /// @dev For WETH dividends, automatically unwraps to native token /// @param taxToken Address of the tax token to claim dividends from function claimDividend(address taxToken) external; /// @notice Claims accumulated dividends for the caller with optional native token unwrapping /// @dev Reverts if the tax token has no dividend contract configured /// @param taxToken Address of the tax token to claim dividends from /// @param unwrapWETH If true, unwraps WETH dividends to native token; if false, sends WETH function claimDividend(address taxToken, bool unwrapWETH) external; /// @notice Claims accumulated dividends on behalf of a specified user /// @dev Reverts if the tax token has no dividend contract configured /// @dev Can be called by anyone to trigger dividend withdrawal for any user /// @param taxToken Address of the tax token to claim dividends from /// @param unwrapWETH If true, unwraps WETH dividends to native token; if false, sends WETH /// @param user Address of the user to claim dividends for function claimDividendForUser(address taxToken, bool unwrapWETH, address user) external; } \`\`\` ## getTaxTokenInfo vs getTaxTokenInfoV2 The contract exposes two query functions. Use the table below to decide which one to call. | | \`getTaxTokenInfo\` | \`getTaxTokenInfoV2\` | | ------------------------- | --------------------------------- | ---------------------------------------------------- | | \*\*Return type\*\* | \`TaxTokenInfo\` | \`TaxTokenInfoV2\` | | \*\*Tax rate fields\*\* | Single \`taxRate\` | Separate \`buyTaxRate\` and \`sellTaxRate\` | | \*\*Marketing address\*\* | \`marketingWallet\` (plain address) | \`vaultInfo.addr\` + full \`MarketingWalletInfo\` | | \*\*Vault metadata\*\* | None | \`isVault\`, \`factory\`, \`riskLevel\`, \`isOfficialVault\` | | \*\*AI oracle flag\*\* | None | \`isAIConsumer\` | | \*\*Dividend reward token\*\* | Not available | \`dividendToken\` (\`address(0)\` = native gas token) | | \*\*Recommended for\*\* | Legacy integrations | All new integrations | ### Using getTaxTokenInfo \`getTaxTokenInfo\` is the original query function. Call it when you only need the core tax breakdown and cumulative statistics: \`\`\`solidity ITaxTokenHelper helper = ITaxTokenHelper(TAX\_TOKEN\_HELPER); ITaxTokenHelper.TaxTokenInfo memory info = helper.getTaxTokenInfo(taxToken); // Tax allocation pie (all in BPS, sum = taxRate) // info.marketBps — share going to marketing wallet // info.deflationBps — share burnt // info.lpBps — share added to liquidity // info.dividendBps — share distributed to dividend holders // Tax rate // info.taxRate — total tax charged on every transfer (BPS) // Cumulative statistics // info.burntTokenAmount — tokens sent to black hole + dead address // info.totalQuoteSentToDividend — quote tokens paid to dividend holders // info.totalQuoteAddedToLiquidity // info.totalTokenAddedToLiquidity // info.totalQuoteSentToMarketing // Addresses // info.marketingWallet — wallet receiving marketing revenue // info.quoteToken — address(0) means native gas token (BNB/ETH) // Dividend gating // info.minimumShareBalance — min token balance to receive dividends \`\`\` {% hint style="info" %} For V1 tax tokens (\`TOKEN\_TAXED\`), only \`marketBps\` (always \`10000\`), \`taxRate\`, \`marketingWallet\`, \`quoteToken\`, and \`totalQuoteSentToMarketing\` are populated. All other fields are zero. {% endhint %} ### Using getTaxTokenInfoV2 \`getTaxTokenInfoV2\` is the recommended function for all new integrations. It returns everything \`getTaxTokenInfo\` returns, plus richer metadata about the marketing wallet and dividend token. \`\`\`solidity ITaxTokenHelper helper = ITaxTokenHelper(TAX\_TOKEN\_HELPER); ITaxTokenHelper.TaxTokenInfoV2 memory info = helper.getTaxTokenInfoV2(taxToken); // Tax allocation — same BPS fields as TaxTokenInfo // info.marketBps / deflationBps / lpBps / dividendBps // Separate buy and sell tax rates (replaces the single taxRate field) // info.buyTaxRate — tax on buys (BPS) // info.sellTaxRate — tax on sells (BPS, currently equal to buyTaxRate) // Cumulative statistics — same as TaxTokenInfo // info.burntTokenAmount / totalQuoteSentToDividend / totalQuoteAddedToLiquidity // info.totalTokenAddedToLiquidity / totalQuoteSentToMarketing // Dividend reward token (new in V2) // info.dividendToken — address(0) = native gas token; otherwise the ERC20 reward token // info.quoteToken // info.minimumShareBalance // Marketing wallet / vault metadata (new in V2) // info.vaultInfo.addr — the marketing wallet or vault address // info.vaultInfo.isVault — true if registered via VaultPortal // info.vaultInfo.factory — vault factory that created it (zero if not a vault) // info.vaultInfo.riskLevel — 0 UNVERIFIED · 1 LOW · 2 LOW\_MEDIUM · 3 MEDIUM · 4 HIGH // info.vaultInfo.isOfficialVault — true if Flap-endorsed // info.vaultInfo.isAIConsumer — true if vault uses Flap AI Oracle \`\`\` #### Reading vault risk level \`riskLevel\` maps to the \`IVaultPortalTypes.RiskLevel\` enum: | Value | Meaning | | ----- | ----------------------------------- | | 0 | UNVERIFIED (default for non-vaults) | | 1 | LOW\\\_RISK | | 2 | LOW\\\_MEDIUM\\\_RISK | | 3 | MEDIUM\\\_RISK | | 4 | HIGH\\\_RISK | #### Detecting AI-powered vaults \`isAIConsumer\` is \`true\` when either: \* The vault's category in VaultPortal is \`TYPE\_AI\_ORACLE\_POWERED\`, \*\*or\*\* \* The marketing wallet is a smart contract (not an EOA or EIP-7702 delegate) that has submitted at least one request through \`FlapAIProvider\`. ### V1 tax token caveats For V1 tax tokens (\`TOKEN\_TAXED\`), both functions populate only a subset of fields: \`marketBps\` (always \`10000\`, meaning 100% goes to the marketing/funds-recipient wallet), \`buyTaxRate\`/\`sellTaxRate\` (or \`taxRate\`), \`vaultInfo.addr\`/\`marketingWallet\`, \`quoteToken\`, and \`totalQuoteSentToMarketing\`. The \`totalQuoteSentToMarketing\` field may be zero for older V1 tokens. If that is the case, we provide a backend to get the \`totalQuoteSentToMarketing\` for V1 Tax tokens: \`\`\` https://t3w1p53k7a.execute-api.eu-west-3.amazonaws.com/donation?token=0x36F2FD027F5f27C59B8C6d64dF64bcC8E8C97777 \`\`\` > Note: you only need this fallback when \`totalQuoteSentToMarketing\` returned by \`getTaxTokenInfo\` is zero for V1 Tax tokens. For V2 Tax tokens, the \`totalQuoteSentToMarketing\` is always accurate. ## how to inspect a tax token's vault When using \`getTaxTokenInfoV2\`, vault metadata is already included in the \`vaultInfo\` field of \`TaxTokenInfoV2\` — no separate VaultPortal call is needed for the basic case. For deeper, vault-type-specific data, you can still query VaultPortal directly. Use the VaultPortal \`tryGetVault\` method to get basic vault information; the returned \`description\` gives a human-readable summary of the vault. For details on the fields returned by \`tryGetVault\`, refer to \[Get Vault Info\](/flap/developers/vault-developers/flap-tax-vault.md#get-vault-info). If you want vault-type-specific data, compare the \`vaultFactory\` returned by \`tryGetVault\` against the known registered vault factories and then query the vault using its own interface. The list of registered vault factories and their interfaces is available at \[developers/token-launcher-developers/registered-vaults.md\](/flap/developers/token-launcher-developers/registered-vaults.md). --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/preview/beta-pve-curve.md). # Beta: PVE Curve {% hint style="warning" %} This feature is in \*\*beta preview\*\*. The curve parameters and behavior described here are subject to change before final release. Currently, the PVE curve is \*\*only available for non-tax tokens\*\* — i.e. tokens that will be migrated to \*\*PancakeSwap V4\*\* upon graduation. Tax tokens continue to use the original \`CURVE\_RH\_BNB\` curve. {% endhint %} ## Background: PVE vs PVP in the Bonding Curve In the context of Flap's bonding curve, we use two terms to describe the participant dynamics \*\*within the bonding curve phase itself\*\* (before the token ever reaches a DEX): \* \*\*PVP (Player vs Player)\*\* — participants are pitted against each other. The curve's pricing creates large profit asymmetries between early and late buyers within the bonding phase, enabling extractive behavior. \* \*\*PVE (Player vs Environment)\*\* — participants interact primarily with the curve's deterministic pricing. The profit asymmetry between early and late buyers is compressed, so participants cannot easily extract value from each other. The original BNB bonding curve (\`CURVE\_RH\_BNB\`) has a \*\*13× price ratio\*\* — the price at graduation (80% supply sold) is 13× higher than the price at launch. This makes the bonding curve phase highly PVP. ### The Problem: A 13× Bonding Curve is High-PVP On the original curve, the price starts \*\*very low\*\* and then \*\*increases 13 times\*\* by the end of the bonding curve. This extreme price ramp creates a dangerous dynamic: | | Original Curve (13×) | | ----------------------------- | -------------------- | | Launch FDV | \\~$4.3k | | Migration FDV (80%) | \\~$41k | | Price increase during bonding | \*\*13×\*\* | Because the token is extremely cheap at launch and rockets upward, early participants can use \*\*snipers, MEV bots, or automated scripts\*\* to buy a large amount of tokens at the very beginning of the bonding curve. They acquire tokens at a price that is up to 13× cheaper than what latecomers will pay. These early accumulators can then \*\*gradually dump their holdings onto the late entrants\*\* who are forced to buy near the top of the bonding curve. In other words, the bonding curve phase itself becomes a PVP arena: the steep 13× ascent guarantees that whoever gets in early — especially with automation — can extract value from those who arrive later. Late participants are effectively providing exit liquidity to the early snipers. ## The PVE Curve: \`CURVE\_RH\_BNB\_1X5\_32BNB\` The PVE curve redesigns the bonding curve to \*\*flatten the price increase\*\* during the bonding phase, turning it from a PVP arena back into a PVE process. Instead of a 13× price jump, the price increases by only \*\*1.5×\*\* (a 50% increase) from launch to graduation. ### Why 1.5× Eliminates the Sniper Advantage The key insight is about \*\*profit ceiling\*\*: \* \*\*13× curve\*\*: An early sniper can buy at the bottom and sell to a latecomer at up to 13× the price. This massive profit margin makes it worthwhile to deploy bots, snipers, and MEV infrastructure to抢购 tokens at launch. \* \*\*1.5× curve\*\*: The maximum price difference between the first bonding-curve buyer and the last is only \*\*50%\*\*. You simply \*\*cannot achieve very large profits\*\* by sniping the launch and dumping on latecomers — the curve doesn't give you the 13× multiplier to exploit. With the profit ceiling compressed to 1.5×, the incentive to deploy snipers and bots largely disappears. There is no longer a massive arbitrage between early and late bonding-curve buyers. Instead of racing to snipe the launch, participants are encouraged to \*\*take their time and build a position gradually\*\* during the bonding phase, because there is no steep cliff to front-run. ### Design Goals | Goal | How it's achieved | | ---------------------------- | ---------------------------------------------------------------------------------------------- | | \*\*Reduce bonding-phase PVP\*\* | Lower price ratio: 1.5× instead of 13× — no large profit for snipers | | \*\*Higher launch FDV\*\* | The curve starts at a higher initial price, so the token is never "too cheap" at launch | | \*\*Raise more reserve\*\* | 32 BNB net graduation (vs 16 BNB on the original), providing deeper DEX liquidity on migration | | \*\*Predictable pricing\*\* | Still a deterministic bonding curve — no liquidity manipulation possible | {% hint style="info" %} \*\*Scope:\*\* The PVE curve is currently applied only to \*\*non-tax tokens\*\* that migrate to \*\*PancakeSwap V4\*\*. Tax tokens (which use a different migrator and fee distribution path) are unaffected and continue to use the original \`CURVE\_RH\_BNB\`. {% endhint %} ### Curve Parameters The PVE curve uses the same constant-product family as all Flap bonding curves: $$ (x + h)(y + r) = k $$ | Parameter | Original (\`CURVE\_RH\_BNB\`) | PVE (\`CURVE\_RH\_BNB\_1X5\_32BNB\`) | | --------------------------------- | ------------------------- | ------------------------------ | | \`r\` (virtual quote reserve) | 6.14 | \*\*142.38367176\*\* | | \`h\` (virtual token reserve) | 107,036,752 | \*\*3,359,591,794\*\* | | \`k\` (invariant) | 6,797,205,657.28 | \*\*620,734,687,004.49\*\* | | Price ratio (graduation / launch) | 13× | \*\*1.5×\*\* | | Net BNB at graduation (80%) | 16 BNB | \*\*32 BNB\*\* | | Graduation threshold | 80% (800M / 1B) | 80% (800M / 1B) | \*\*Key design notes:\*\* \* The price ratio is determined solely by the virtual reserves: $\\text{ratio} = \\left(\\frac{S\\\_{max} + h}{S\\\_{max} + h - x\\\_0}\\right)^2$ where $x\\\_0 = 800{,}000{,}000$ (the graduation supply). Setting $h = 3{,}359{,}591{,}794$ yields exactly 1.5×. \* The net graduation reserve $R(x\\\_0) = 32$ BNB is achieved by calibrating $r$ and $k$ together. With $r = 142.38367176$ and $k = r \\times (S\\\_{max} + h)$, the curve satisfies $R(0) = 0$ (no reserve at launch) and $R(800M) = 32$ BNB exactly. \* The graduation threshold remains at 80% — the token migrates to DEX when 80% of the total supply has been purchased from the bonding curve. ## Behavior Comparison The following graph shows the original 13× curve alongside the new PVE 1.5× curve. All data comes from BSC mainnet fork simulation (not pure math), at a BNB price of $580.
PVE curve comparison: original 13x vs new 1.5x 32 BNB

Comparison of the original 13× bonding curve (red) and the new PVE 1.5× curve (green). Top: full range including DEX phase. Bottom: bonding curve phase only, with critical points marked.

### Simulation Results | Curve | BNB at Migration | Launch FDV | Migration FDV (80%) | | ---------------------------------- | ---------------- | ----------------- | ------------------- | | \*\*Original\*\* (\`CURVE\_RH\_BNB\`) | 16.0 BNB | 7.5 BNB / $4.3k | 70.8 BNB / $41.1k | | \*\*PVE\*\* (\`CURVE\_RH\_BNB\_1X5\_32BNB\`) | 32.0 BNB | 33.1 BNB / $19.2k | 48.8 BNB / $28.3k | ### How the PVE Curve Reduces Bonding-Phase PVP \*\*1. Compressed profit ceiling (1.5× instead of 13×)\*\* On the original curve, the price increases by 13× from the first bonding-curve buy to graduation. A buyer at step 1 pays 13× less than a buyer at step 16. This 13× asymmetry is the root cause of PVP: it makes sniping the launch extremely profitable, so participants deploy bots and MEV to抢购 tokens early and dump them on latecomers. On the PVE 1.5× curve, the price increases by only \*\*50%\*\* over the entire bonding phase. A buyer at step 1 pays only 1.5× the price of a buyer at step 32. The profit that an early sniper can extract from a late buyer is capped at 50% — not enough to justify the cost and risk of running snipers and bots. The PVP dynamic collapses. \*\*2. No more "cheap launch" sniping\*\* The original curve launches at a very low FDV (\\~$4.3k), making the token extremely cheap at launch — the perfect target for snipers. The PVE curve launches at a higher FDV (\\~$19.2k), establishing a healthier initial valuation that removes the "penny token" sniping opportunity from the start. \*\*3. Participants build positions gradually\*\* Because there is no steep price cliff to front-run, participants are no longer pressured to race into the bonding curve at launch. They can take their time, evaluate the token, and build a position gradually over the bonding phase. The bonding curve behaves more like an "environment" (PVE) — a predictable, fair pricing mechanism — rather than a PVP arena where speed and automation determine who extracts value from whom. \*\*4. Deeper DEX liquidity on migration\*\* The PVE curve raises 32 BNB net before graduation (vs 16 BNB on the original). This means the token migrates to DEX with \*\*twice the liquidity\*\*, reducing slippage and making post-graduation trading healthier for everyone. ## Summary | Property | Original (13×) | PVE (1.5×, 32 BNB) | | ------------------------------ | ---------------------------- | ------------------------------------ | | Price ratio during bonding | 13× | \*\*1.5×\*\* | | Bonding-phase PVP | High — sniping is profitable | \*\*Low — no profit cliff to exploit\*\* | | Launch FDV | \\~$4.3k | \\~$19.2k | | Migration FDV | \\~$41k | \\~$28k | | Net BNB raised (DEX liquidity) | 16 BNB | \*\*32 BNB\*\* | | Graduation threshold | 80% | 80% | The PVE curve transforms the bonding curve phase from a high-PVP arena — where snipers and bots front-run the 13× price ramp to extract value from latecomers — into a low-PVP environment where the 1.5× price ceiling makes sniping unprofitable. Participants are encouraged to build positions gradually rather than racing to snipe the launch, and the doubled reserve (32 BNB) provides healthier DEX liquidity on migration. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-vaultportal.md). # Launch token through VaultPortal \`VaultPortal\` builds on top of \`Portal\` to add Vault functionality for vault-backed token launches. It creates the token and its associated Vault in a single transaction. Use \`VaultPortal\` when you need a Vault-backed launch. For launches without a Vault, use \`Portal\` instead. For the \`VaultPortal\` contract address, see \[VaultPortal deployed addresses\](/flap/developers/vault-developers/flap-tax-vault.md#deployed-addresses). ## Key interfaces ### Recommended: \`newTokenV6WithVault\` The preferred entry point for new integrations is \`newTokenV6WithVault\`, which supports \`TOKEN\_TAXED\_V3\` (Tax Token V3) with a vault in a single transaction: \`\`\`solidity /// @notice Create a new tax token via Portal's newTokenV6 with an associated vault in a single transaction /// @dev Only TOKEN\_TAXED\_V3 is currently supported as tokenVersion; any other version reverts with FeatureDisabled. /// @param params The parameters for creating the tax token and vault (mirrors NewTokenV6Params, minus beneficiary) /// @return token The address of the newly created tax token function newTokenV6WithVault(NewTokenV6WithVaultParams calldata params) external payable returns (address token); \`\`\` \`NewTokenV6WithVaultParams\` mirrors \`NewTokenV6Params\` (without \`beneficiary\`) and adds vault-specific fields: \`\`\`solidity struct NewTokenV6WithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.DEXId dexId; IPortalTypes.V3LPFeeProfile lpFeeProfile; // V3 tax fields uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; uint64 antiFarmerDuration; uint16 mktBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; uint256 minimumShareBalance; address dividendToken; address commissionReceiver; IPortalTypes.TokenVersion tokenVersion; // must be TOKEN\_TAXED\_V3 // vault fields address vaultFactory; bytes vaultData; } \`\`\` {% hint style="info" %} \`newTokenV6WithVault\` only accepts \`TOKEN\_TAXED\_V3\`. Passing any other \`tokenVersion\` reverts with \`FeatureDisabled\`. For new integrations, always use Tax Token V3 with \`newTokenV6WithVault\`. {% endhint %} {% hint style="warning" %} Legacy tax token versions (\`TOKEN\_TAXED\`, \`TOKEN\_TAXED\_V2\`) are not supported by \`newTokenV6WithVault\` and are not recommended for new launches. The old \`newTaxTokenWithVault\` entry point is deprecated and should not be used for new or existing integrations. {% endhint %} ### New: \`newTokenV7WithVault\` \`newTokenV7WithVault\` is the VaultPortal wrapper around \`Portal.newTokenV7\`. It creates the vault first, then rewrites the active \`MARKETING\_OR\_VAULT\` fee receiver to that vault before forwarding the launch to \`Portal\`. \`\`\`solidity /// @notice Create a new token via a V7-style fee-config API with an associated vault. /// @dev Supports TOKEN\_V3\_PERMIT (non-tax) and TOKEN\_TAXED\_V3 (tax). The wrapper creates the vault first, /// then rewrites MARKETING\_OR\_VAULT receivers to that vault before calling Portal.newTokenV7. /// @param params The parameters for creating the token and vault (mirrors upstream newTokenV7 launch UX) /// @return token The address of the newly created token function newTokenV7WithVault(NewTokenV7WithVaultParams calldata params) external payable returns (address token); \`\`\` \`NewTokenV7WithVaultParams\` mirrors \`Portal\`'s \`NewTokenV7Params\` and adds vault-specific fields: \`\`\`solidity struct NewTokenV7WithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.TokenVersion tokenVersion; IPortalTypes.DEXId dexId; uint64 antiFarmerDuration; uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; address commissionReceiver; IPortalTypes.FeeConfig\[4\] feeConfigs; address vaultFactory; bytes vaultData; } \`\`\` ### Current \`newTokenV7WithVault\` behavior The interface comment describes both \`TOKEN\_V3\_PERMIT\` and \`TOKEN\_TAXED\_V3\`, but the current implementation in \`FlapTaxVaults/src/VaultPortalLaunch.sol\` is narrower: \* it currently reverts with \`FeatureDisabled()\` unless \`tokenVersion == TOKEN\_V3\_PERMIT\` \* \`quoteToken\` must be \`address(0)\` or the wrapper reverts with \`UnsupportedQuoteToken(...)\` \* exactly one active \`MARKETING\_OR\_VAULT\` slot is required \* active \`feeConfigs\` must sum to exactly \`10000\` bps \* after validation, the wrapper overwrites that single \`MARKETING\_OR\_VAULT\` slot's \`marketingAddress\` with the freshly created vault address \* the current source hard-codes an allowlist of supported vault factories for this path {% hint style="warning" %} The supplied interface and simulation script describe a broader V7-with-vault design, but the current source implementation is stricter. When integrating, follow the behavior enforced in \`FlapTaxVaults/src/VaultPortalLaunch.sol\` rather than assuming every upstream \`newTokenV7\` combination is already live through \`VaultPortal\`. {% endhint %} ### Deprecated: \`newTaxTokenWithVault\` \`newTaxTokenWithVault\` still exists in the ABI for compatibility, but the current \`VaultPortal\` implementation no longer supports it. The function always reverts and should be treated as deprecated. \`\`\`solidity /// @notice Deprecated. Use newTokenV6WithVault instead. /// @dev This function previously created a V1/V2 tax token via Portal's newTokenV5. /// It is no longer supported. Calling it will always revert. /// @param params Unused — kept for ABI compatibility only. function newTaxTokenWithVault(NewTaxTokenWithVaultParams calldata params) external payable returns (address token); \`\`\` {% hint style="danger" %} Do not integrate \`newTaxTokenWithVault\`. Use \`newTokenV6WithVault\` for \`TOKEN\_TAXED\_V3\`, or \`newTokenV7WithVault\` for the V7 vault-backed flow. {% endhint %} ## How to launch with \`newTokenV6WithVault\` 1. \*\*Choose a registered vault factory.\*\* Vault factories are registered in \`VaultPortal\`. Each factory defines its own \`vaultData\` schema. See \[Registered vaults\](/flap/developers/token-launcher-developers/registered-vaults.md). 2. \*\*Prepare metadata and core token parameters.\*\* Use the same metadata flow as \`Portal\` (see \[launch-token-through-portal.md\](/flap/developers/token-launcher-developers/launch-token-through-portal.md)). Set \`tokenVersion = TOKEN\_TAXED\_V3\` and both \`buyTaxRate\`/\`sellTaxRate\` > 0. 3. \*\*Encode \`vaultData\` for the selected factory.\*\* The \`vaultData\` bytes are factory-specific. Always follow the factory's schema (see \[Registered vaults\](/flap/developers/token-launcher-developers/registered-vaults.md)). 4. \*\*Call \`newTokenV6WithVault\`.\*\* The contract emits \`FlapTaxVaultTokenCreated\` with the token, vault, and vault factory addresses. Example: \`\`\`typescript import { encodeAbiParameters, parseEther, zeroAddress } from "viem"; const params = { name: "My Token", symbol: "MTK", meta: "", dexThresh: DexThreshType.TWO\_THIRDS, salt, // must produce 7777 vanity suffix migratorType: MigratorType.V2\_MIGRATOR, quoteToken: zeroAddress, quoteAmt: parseEther("0.01"), permitData: "0x", extensionID: "0x0000000000000000000000000000000000000000000000000000000000000000", extensionData: "0x", dexId: DEXId.DEX0, lpFeeProfile: V3LPFeeProfile.LP\_FEE\_PROFILE\_STANDARD, buyTaxRate: 300, sellTaxRate: 1000, taxDuration: 365n \* 24n \* 60n \* 60n, antiFarmerDuration: 60n \* 60n, mktBps: 10000, deflationBps: 0, dividendBps: 0, lpBps: 0, minimumShareBalance: 0n, dividendToken: zeroAddress, commissionReceiver: zeroAddress, tokenVersion: TokenVersion.TOKEN\_TAXED\_V3, vaultFactory: vaultFactoryAddress, vaultData: encodeAbiParameters( \[\ // replace with the selected vault factory's expected schema\ \], \[\] ), }; const hash = await walletClient.writeContract({ address: vaultPortalAddress, abi: vaultPortalAbi, functionName: "newTokenV6WithVault", args: \[params\], value: parseEther("0.01"), }); \`\`\` {% hint style="info" %} Each vault factory can define a different \`vaultData\` schema. Always validate the factory and its expected payload before encoding. {% endhint %} ## How to launch with \`newTokenV7WithVault\` 1. \*\*Choose a supported vault factory.\*\* The current implementation validates the V7-with-vault path against a hard-coded factory allowlist inside \`VaultPortalLaunch\`. Verify your factory is supported before integrating this path. 2. \*\*Prepare metadata and V7 token parameters.\*\* Use the same metadata flow as \`Portal\` (see \[launch-token-through-portal.md\](/flap/developers/token-launcher-developers/launch-token-through-portal.md)). 3. \*\*Configure \`feeConfigs\` with exactly one vault receiver slot.\*\* Provide exactly one active \`MARKETING\_OR\_VAULT\` slot. Its address is only a placeholder at input time — \`VaultPortal\` will replace it with the newly created vault address before forwarding to \`Portal.newTokenV7\`. 4. \*\*Encode \`vaultData\` for the selected factory.\*\* The schema is factory-specific. See \[Registered vaults\](/flap/developers/token-launcher-developers/registered-vaults.md) and the factory's own docs. 5. \*\*Call \`newTokenV7WithVault\`.\*\* On success, \`VaultPortal\` stores the launched token → vault mapping and emits \`FlapTaxVaultTokenCreated\`. Example based on \`FlapTaxVaults/script/testnets/bsc\_test/simulation/001-simulate-new-token-v7-with-flap-xvault.sol\`: \`\`\`solidity function launchV7WithVault(IVaultPortal vaultPortal, bytes32 salt, address vaultFactoryAddress) external payable returns (address token) { IVaultPortalTypes.NewTokenV7WithVaultParams memory params; params.name = "My V7 Vault Token"; params.symbol = "MV7"; params.meta = ""; params.dexThresh = IPortalTypes.DexThreshType.FOUR\_FIFTHS; params.salt = salt; // TOKEN\_V3\_PERMIT currently requires the 8888 suffix params.migratorType = IPortalTypes.MigratorType.PCS\_INFINITY\_CL\_MIGRATOR; params.quoteToken = address(0); params.quoteAmt = 0.1 ether; params.permitData = ""; params.extensionID = bytes32(0); params.extensionData = ""; params.tokenVersion = IPortalTypes.TokenVersion.TOKEN\_V3\_PERMIT; params.dexId = IPortalTypes.DEXId.DEX0; params.antiFarmerDuration = 0; params.buyTaxRate = 0; params.sellTaxRate = 0; params.taxDuration = 0; params.commissionReceiver = address(0); params.vaultFactory = vaultFactoryAddress; params.vaultData = abi.encode(/\* vault init data \*/); params.feeConfigs\[0\] = IPortalTypes.FeeConfig({ feeType: IPortalTypes.FeeType.MARKETING\_OR\_VAULT, bps: 10000, marketingAddress: msg.sender, // placeholder; replaced with the created vault address dividendToken: address(0), minimumShareBalance: 0 }); params.feeConfigs\[1\] = IPortalTypes.FeeConfig({ feeType: IPortalTypes.FeeType.NONE, bps: 0, marketingAddress: address(0), dividendToken: address(0), minimumShareBalance: 0 }); params.feeConfigs\[2\] = IPortalTypes.FeeConfig({ feeType: IPortalTypes.FeeType.NONE, bps: 0, marketingAddress: address(0), dividendToken: address(0), minimumShareBalance: 0 }); params.feeConfigs\[3\] = IPortalTypes.FeeConfig({ feeType: IPortalTypes.FeeType.NONE, bps: 0, marketingAddress: address(0), dividendToken: address(0), minimumShareBalance: 0 }); token = vaultPortal.newTokenV7WithVault{value: msg.value}(params); } \`\`\` Key notes: \* \`newTokenV7WithVault\` does not expose a standalone \`beneficiary\`; the vault-bound receiver comes from the single active \`MARKETING\_OR\_VAULT\` slot. \* If you configure zero or more than one active \`MARKETING\_OR\_VAULT\` slot, the wrapper reverts with \`InvalidFeeConfig()\`. \* The wrapper predicts the token address before vault creation, so your salt must already match the correct vanity suffix for the chosen token implementation. \* The current V7-with-vault implementation only accepts \`quoteToken = address(0)\`. ## Find the salt (vanity suffix) \`VaultPortal\` uses CREATE2 through \`Portal\`, so the \`salt\` must produce a token address with the required suffix. Follow the same salt-finding flow as \[launch-token-through-portal.md\](/flap/developers/token-launcher-developers/launch-token-through-portal.md), using the implementation that matches the launch path: \* \`newTokenV6WithVault\` with \`TOKEN\_TAXED\_V3\`: use \*\*Tax Token V3 Impl\*\* (\`tokenImplTaxedV3\`), suffix \`7777\` \* \`newTokenV7WithVault\` with \`TOKEN\_V3\_PERMIT\`: use \*\*Standard Token V3 Impl\*\* (\`tokenImplV3\` / Standard Token V3 Impl), suffix \`8888\` ## Salt locking Portal supports locking a vanity salt so that only you can use it to launch a token. This works seamlessly with \`VaultPortal\` — lock the salt directly on Portal, then launch through VaultPortal as normal. \`\`\`solidity function lockAndLaunch( IPortal portal, IVaultPortal vaultPortal, bytes32 mySalt, IPortalTypes.TokenVersion tokenVersion, IVaultPortalTypes.NewTokenV6WithVaultParams memory params, uint256 quoteAmt ) external payable returns (address token) { // 1. Lock the salt on Portal directly portal.lockSalt{value: msg.value}(mySalt, tokenVersion); // 2. Launch through VaultPortal — the lock is honoured automatically token = vaultPortal.newTokenV6WithVault{value: quoteAmt}(params); } \`\`\` No changes to \`NewTokenV6WithVaultParams\` are needed. VaultPortal communicates the real caller back to Portal automatically during the launch transaction. The same salt-lock behavior also applies to \`newTokenV7WithVault\`. ## Reading vault info You can query vault information after launch: \* \`getVault(taxToken)\` returns the full vault info and reverts if not found. \* \`tryGetVault(taxToken)\` returns \`(found, info)\` without reverting. ## IVaultPortal.sol Full interface reference for developers: \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {IPortalTypes} from "src/interfaces/IPortal.sol"; /// @title IVaultPortalTypes /// @notice Type definitions for the VaultPortal contract /// @dev Contains all structs and custom types used by VaultPortal interface IVaultPortalTypes { /// @notice Risk level classification for vault audits (8 bits) /// @dev Used to represent the security assessment of a vault or factory enum RiskLevel { UNVERIFIED, // 0 - Not yet verified/audited LOW\_RISK, // 1 - Low risk LOW\_MEDIUM\_RISK, // 2 - Low to medium risk MEDIUM\_RISK, // 3 - Medium risk HIGH\_RISK // 4 - High risk } /// @notice Category classification for vaults (8 bits) /// @dev Used to categorize vaults by their type or functionality enum VaultCategory { NONE, // 0 - Not in any category (default) TYPE\_AI\_ORACLE\_POWERED // 1 - AI Oracle powered vaults } /// @notice Information about a registered vault factory /// @dev Packed into a single storage slot for gas efficiency /// @param enabled Whether this factory can be used to create new vaults /// @param official Whether vaults created by this factory are marked as official/endorsed /// @param riskLevel The risk level classification from audit /// @param category The category classification for vaults from this factory (1 byte) /// @param reserved Reserved space for future upgrades (28 bytes) struct VaultFactoryInfo { bool enabled; bool official; RiskLevel riskLevel; VaultCategory category; bytes28 reserved; } /// @notice Packed vault information stored in contract storage /// @dev Uses three storage slots (64 bytes total) /// @param vault The address of the vault contract (20 bytes) /// @param vaultFactory The address of the vault factory that created this vault (20 bytes) /// @param adapter The address of the adapter contract for legacy vaults (20 bytes) /// @param isOfficial Whether this vault is marked as official/endorsed (1 byte) /// @param riskLevel The risk level classification from audit (1 byte) /// @param category The category classification for this vault (1 byte) struct VaultedTaxTokenInfo { // slot 0 address vault; bool isOfficial; RiskLevel riskLevel; VaultCategory category; bytes9 reserved0; // slot 1 address vaultFactory; bytes12 reserved1; // slot 2 address adapter; bytes12 reserved2; } /// @notice Audit report for a tax token /// @param auditor The address of the auditor who submitted this report /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report document struct AuditReport { address auditor; RiskLevel riskLevel; string ipfsCid; } /// @notice Complete vault information returned to external callers /// @dev This struct is used for view functions and includes human-readable description /// @param vault The address of the vault contract /// @param vaultFactory The address of the vault factory that created this vault (zero address if unknown) /// @param description Human-readable description of the vault's purpose and functionality /// @param isOfficial Whether this vault is marked as official/endorsed by the protocol /// @param riskLevel The risk level classification from audit struct VaultInfo { address vault; address vaultFactory; string description; bool isOfficial; RiskLevel riskLevel; } /// @notice Parameters required to create a new tax token with an associated vault /// @dev All parameters are passed as a single struct to avoid stack-too-deep errors /// @param name The name of the tax token (e.g., "MyToken") /// @param symbol The symbol of the tax token (e.g., "MTK") /// @param meta Metadata URI or string for additional token information /// @param dexThresh The DEX supply threshold type /// @param salt A unique salt for deterministic address generation (must produce vanity suffix) /// @param taxRate The tax rate in basis points (e.g., 100 = 1%, max 1000 = 10%) /// @param migratorType The migrator type (see MigratorType enum) /// @param quoteToken The token used for initial liquidity (address(0) for native token) /// @param quoteAmt The amount of quote token to provide as initial liquidity /// @param permitData The optional permit data for the quote token /// @param extensionID The ID of the extension to be used for the new token if not zero /// @param extensionData Additional extension specific data /// @param dexId The preferred DEX ID for the token /// @param lpFeeProfile The preferred V3 LP fee profile for the token /// @param taxDuration Tax duration in seconds (max: 100 years) /// @param antiFarmerDuration Anti-farmer duration in seconds (max: 1 year) /// @param mktBps Market allocation basis points (to beneficiary) /// @param deflationBps Deflation basis points (burned) /// @param dividendBps Dividend basis points (to dividend contract) /// @param lpBps Liquidity provision basis points (LP to dead address) /// @param minimumShareBalance Minimum balance for dividend eligibility /// @param vaultFactory The address of the vault factory to use for creating the vault (any factory is allowed; unregistered/disabled factories result in unverified vaults) /// @param vaultData Encoded data specific to the vault type being created struct NewTaxTokenWithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; uint16 taxRate; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.DEXId dexId; IPortalTypes.V3LPFeeProfile lpFeeProfile; uint64 taxDuration; uint64 antiFarmerDuration; uint16 mktBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; uint256 minimumShareBalance; address vaultFactory; bytes vaultData; } /// @notice Parameters required to create a new V3 tax token with an associated vault /// @dev tokenVersion must be TOKEN\_TAXED\_V3; if it is not, the implementation reverts with FeatureDisabled struct NewTokenV6WithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.DEXId dexId; IPortalTypes.V3LPFeeProfile lpFeeProfile; // V3 tax fields uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; uint64 antiFarmerDuration; uint16 mktBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; uint256 minimumShareBalance; address dividendToken; address commissionReceiver; /// The token version to use (must be TOKEN\_TAXED\_V3, otherwise reverts with FeatureDisabled) IPortalTypes.TokenVersion tokenVersion; // vault fields address vaultFactory; bytes vaultData; } /// @notice Parameters required to create a V7-style token with an associated vault. /// @dev Mirrors upstream \`newTokenV7\`, then swaps every MARKETING\_OR\_VAULT receiver to the /// freshly created vault before calling \`Portal.newTokenV7\`. struct NewTokenV7WithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.TokenVersion tokenVersion; IPortalTypes.DEXId dexId; uint64 antiFarmerDuration; uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; address commissionReceiver; IPortalTypes.FeeConfig\[4\] feeConfigs; address vaultFactory; bytes vaultData; } /\* ========== EVENTS ========== \*/ /// @notice Emitted when a new tax token with an associated vault is successfully created /// @param token The address of the newly deployed tax token /// @param vault The address of the newly created vault that will receive tax revenue /// @param vaultFactory The vault factory address that was used to create the vault event FlapTaxVaultTokenCreated(address indexed token, address indexed vault, address indexed vaultFactory); /// @notice Emitted when a vault factory is registered or its configuration is updated /// @param factory The address of the vault factory being registered/updated /// @param enabled Whether the factory is enabled for creating new vaults /// @param official Whether vaults from this factory should be marked as official /// @param riskLevel The risk level classification for vaults from this factory event FlapTaxVaultFactoryRegistered(address factory, bool enabled, bool official, RiskLevel riskLevel); /// @notice Emitted when a vault factory's category is set during registration or update /// @param factory The address of the vault factory /// @param category The category classification for vaults from this factory event FlapTaxVaultFactoryCategorySet(address factory, VaultCategory category); /// @notice Emitted when an adapter is registered for a legacy tax token /// @param taxToken The address of the tax token /// @param adapter The address of the adapter contract event AdapterRegistered(address indexed taxToken, address indexed adapter); /// @notice Emitted when a new audit report is submitted /// @param taxToken The address of the tax token being audited /// @param auditor The address of the auditor /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report event AuditReportSubmitted(address indexed taxToken, address indexed auditor, RiskLevel riskLevel, string ipfsCid); /// @notice Emitted when a new audit report is submitted for a vault factory /// @param factory The address of the vault factory being audited /// @param auditor The address of the auditor /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report event FactoryAuditReportSubmitted( address indexed factory, address indexed auditor, RiskLevel riskLevel, string ipfsCid ); /// @notice Emitted when a vault's category is updated /// @param taxToken The address of the tax token /// @param category The new category event VaultCategoryUpdated(address indexed taxToken, VaultCategory category); /// @notice Emitted when a factory's category is updated /// @param factory The address of the vault factory /// @param category The new category event FactoryCategoryUpdated(address indexed factory, VaultCategory category); /\* ========== ERRORS ========== \*/ /// @notice Thrown when the provided tax rate is invalid (0 or > 1000 basis points) /// @param taxRate The invalid tax rate that was provided error InvalidTaxRate(uint256 taxRate); /// @notice Thrown when mktBps is invalid (must be > 0) error InvalidMktBps(); /// @notice Thrown when V7-style fee configs are malformed. /// @dev Used for wrapper-level validation such as missing MARKETING\_OR\_VAULT buckets. error InvalidFeeConfig(); /// @notice Thrown when an unsupported quote token is specified /// @param quoteToken The address of the unsupported quote token error UnsupportedQuoteToken(address quoteToken); /// @notice Thrown when attempting to use a vault factory that is not registered /// @param factory The address of the unregistered vault factory error VaultFactoryNotRegistered(address factory); /// @notice Thrown when the predicted token address does not match the required vanity suffix /// @param predictedAddress The predicted address that failed the vanity check error InvalidVanity(address predictedAddress); /// @notice Thrown when trying to get vault info for a token that has no associated vault /// @param taxToken The address of the tax token with no vault error VaultNotFound(address taxToken); /// @notice Thrown when token address does not match predicted address error TokenAddressMismatch(); /// @notice Thrown when BNB transfer fails error BnbTransferFailed(); /// @notice Thrown when a non-V3 tax token version is specified (only TOKEN\_TAXED\_V3 is allowed) error OnlyV3TaxTokenAllowed(); /// @notice Thrown when a requested feature is not yet enabled error FeatureDisabled(); /// @notice Thrown when portal address is zero error ZeroPortalAddress(); /// @notice Thrown when token implementation address is zero error ZeroTokenImplAddress(); /// @notice Thrown when trying to perform an operation on a non-existent token /// @param taxToken The address of the non-existent tax token error TokenNotFound(address taxToken); /// @notice error when user is rate limited from creating tokens /// @param user The address that is rate limited /// @param lastCreationTime The timestamp of the user's last successful token creation error RateLimitExceeded(address user, uint256 lastCreationTime); } /// @title IVaultPortal /// @notice Interface for the VaultPortal contract that manages vault-backed token launches /// @dev VaultPortal acts as a registry and factory orchestrator for different vault types /// @dev It coordinates token launches with their associated revenue vaults interface IVaultPortal is IVaultPortalTypes { /\* ========== FUNCTIONS ========== \*/ /// @notice Get information about a registered vault factory /// @param factory The address of the vault factory /// @return enabled Whether this factory can be used to create new vaults /// @return official Whether vaults created by this factory are marked as official/endorsed /// @return riskLevel The risk level classification for vaults from this factory /// @return reserved Reserved space for future upgrades (29 bytes) function vaultFactories(address factory) external view returns (bool enabled, bool official, RiskLevel riskLevel, bytes29 reserved); /\* ========== FUNCTIONS ========== \*/ /// @notice Deprecated. Use newTokenV6WithVault instead. /// @dev This function previously created a V1/V2 tax token via Portal's newTokenV5. /// It is no longer supported. Calling it will always revert. /// @param params Unused — kept for ABI compatibility only. function newTaxTokenWithVault(NewTaxTokenWithVaultParams calldata params) external payable returns (address token); /// @notice Create a new tax token via Portal's newTokenV6 with an associated vault in a single transaction /// @dev Only TOKEN\_TAXED\_V3 is currently supported as tokenVersion; any other version reverts with FeatureDisabled. /// @param params The parameters for creating the tax token and vault (mirrors NewTokenV6Params, minus beneficiary) /// @return token The address of the newly created tax token function newTokenV6WithVault(NewTokenV6WithVaultParams calldata params) external payable returns (address token); /// @notice Create a new token via a V7-style fee-config API with an associated vault. /// @dev Supports TOKEN\_V3\_PERMIT (non-tax) and TOKEN\_TAXED\_V3 (tax). The wrapper creates the vault first, /// then rewrites MARKETING\_OR\_VAULT receivers to that vault before calling Portal.newTokenV7. /// @param params The parameters for creating the token and vault (mirrors upstream newTokenV7 launch UX) /// @return token The address of the newly created token function newTokenV7WithVault(NewTokenV7WithVaultParams calldata params) external payable returns (address token); /// @notice Predict the address of a tax token V1 given a salt /// @dev Uses CREATE2 deterministic address prediction /// @dev Useful for generating valid salts that produce the required vanity suffix /// @param salt The salt for deterministic deployment /// @return predictedAddress The predicted address of the tax token function predictTaxTokenV1Address(bytes32 salt) external view returns (address predictedAddress); /// @notice Get the complete vault information for a tax token /// @dev Reverts if no vault is found for the given tax token /// @dev Attempts to fetch the vault's description by calling its description() function /// @param taxToken The address of the tax token /// @return info The VaultInfo struct containing complete vault details function getVault(address taxToken) external view returns (VaultInfo memory info); /// @notice Attempt to get vault information for a tax token without reverting /// @dev First checks the internal mapping, then falls back to searching the Portal /// @dev For fallback results, isOfficial and isVerified will always be false /// @param taxToken The address of the tax token /// @return found Whether a vault was found for this tax token /// @return info The VaultInfo struct (empty if not found) function tryGetVault(address taxToken) external view returns (bool found, VaultInfo memory info); /// @notice Register a new vault factory or update an existing one (backward-compatible overload) /// @dev Only accounts with VAULT\_ADMIN\_ROLE can call this function /// @dev Allows enabling/disabling factories and updating their official/riskLevel status /// @dev Category defaults to VaultCategory.NONE /// @param factory The address of the vault factory to register/update /// @param enabled Whether the factory should be enabled for creating new vaults /// @param official Whether vaults from this factory should be marked as official /// @param riskLevel The risk level classification for vaults from this factory function registerVaultFactory(address factory, bool enabled, bool official, RiskLevel riskLevel) external; /// @notice Register a new vault factory or update an existing one with a specific category /// @dev Only accounts with VAULT\_ADMIN\_ROLE can call this function /// @dev Allows enabling/disabling factories and updating their official/riskLevel/category status /// @param factory The address of the vault factory to register/update /// @param enabled Whether the factory should be enabled for creating new vaults /// @param official Whether vaults from this factory should be marked as official /// @param riskLevel The risk level classification for vaults from this factory /// @param category The category classification for vaults from this factory function registerVaultFactory( address factory, bool enabled, bool official, RiskLevel riskLevel, VaultCategory category ) external; /// @notice Register an adapter for a legacy tax token vault /// @dev Only accounts with AUDITOR\_ROLE can call this function /// @dev The adapter must implement the VaultBase interface for compatibility /// @param taxToken The address of the tax token /// @param adapter The address of the adapter contract function registerAdapter(address taxToken, address adapter) external; /// @notice Submit a new audit report for a tax token /// @dev Only accounts with AUDITOR\_ROLE can call this function /// @dev The tax token must exist (have a vault or adapter registered) /// @dev This may change the risk level of the token /// @param taxToken The address of the tax token being audited /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report document function submitAuditReport(address taxToken, RiskLevel riskLevel, string calldata ipfsCid) external; /// @notice Submit a new audit report for a vault factory /// @dev Only accounts with VAULT\_ADMIN\_ROLE can call this function /// @dev The factory should be registered or have existing audit reports /// @param factory The address of the vault factory being audited /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report document function submitFactoryAuditReport(address factory, RiskLevel riskLevel, string calldata ipfsCid) external; /// @notice Get recent audit reports for a tax token with pagination /// @dev Returns reports starting from the most recent (end of array) /// @dev If the token has no audit reports, falls back to its factory's audit reports /// @param taxToken The address of the tax token /// @param offset The number of reports to skip from the end (0 = most recent) /// @param limit The maximum number of reports to return /// @return reports The array of audit reports /// @return total The total number of audit reports for this token function getAuditReports(address taxToken, uint256 offset, uint256 limit) external view returns (AuditReport\[\] memory reports, uint256 total); /// @notice Get the category of a vault /// @param taxToken The tax token address /// @return category The category of the vault function getVaultCategory(address taxToken) external view returns (VaultCategory category); /// @notice Get the category of a vault factory /// @param factory The vault factory address /// @return category The category of the factory function getFactoryCategory(address factory) external view returns (VaultCategory category); /// @notice Set the category of a vault /// @dev Only accounts with VAULT\_ADMIN\_ROLE can call this function /// @param taxToken The tax token address /// @param category The new category function setVaultCategory(address taxToken, VaultCategory category) external; /// @notice Set the category of a vault factory /// @dev Only accounts with VAULT\_ADMIN\_ROLE can call this function /// @param factory The vault factory address /// @param category The new category function setFactoryCategory(address factory, VaultCategory category) external; } \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/preview/flap-ai-oracle.md). # Flap AI Oracle ## 1. Overview & Roadmap ### Overview Three simple steps — build your prompt on-chain, let us handle the AI, and get back a verified decision. \*\*FlapAIProvider\*\* is a standardized on-chain AI oracle plugin built for the Flap ecosystem. It gives Vault contracts (and any smart contract) access to verifiable LLM reasoning via a \*\*commit-and-reveal\*\* scheme inspired by Chainlink VRF. FlapAIProvider separates concerns of AI-powered smart contracts cleanly: \* \*\*On-chain (commit):\*\* The consumer calls \`reason()\` with a prompt and pays a BNB fee. The contract records the request and emits an event. \* \*\*Off-chain:\*\* The oracle backend listens for the event, feeds the prompt to the selected LLM, and obtains a numeric choice. \* \*\*On-chain (reveal):\*\* The oracle calls \`fulfillReasoning()\` with the result and an IPFS CID linking to the full reasoning proof. The consumer's callback is invoked automatically. Every request is permanently auditable: the IPFS CID stored on-chain pins the raw LLM inputs, outputs, temperature, model version, and salt that produced the decision. ### Architecture !\[Flap AI Oracle\](/files/bFKz6LZ5DRLhHJBCtmjA) \`\`\`mermaid flowchart TD subgraph on-chain\["🔗 On-Chain"\] Vault(\["🏦 Your Vault"\]) Provider(\["⚙️ FlapAIProvider"\]) end subgraph off-chain\["☁️ Off-Chain"\] Oracle(\["🤖 Oracle Backend"\]) LLM(\["🧠 LLM Model"\]) Tools(\["🔧 Tools\\n(ave\_token\_tool, etc.)"\]) IPFS(\["📦 IPFS\\n(proof storage)"\]) end Vault -->|"① pay fee + send prompt"| Provider Provider -->|"② emit request event"| Oracle Oracle -->|"③ forward prompt"| LLM LLM -->|"④ request tool data"| Tools Tools -->|"⑤ return data"| LLM LLM -->|"⑥ return choice"| Oracle Oracle -->|"⑦ upload reasoning proof"| IPFS Oracle -->|"⑧ fulfill / refund"| Provider Provider -->|"⑨ callback with result\\n— or — BNB refund"| Vault \`\`\` ### Roadmap | Release | Features | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | \*\*v1 (current)\*\* | Commit-and-reveal reasoning, multi-model registry, IPFS proof anchoring, refund fallback, \`FlapAIConsumerBase\` integration base, \*\*tool calling\*\* support (currently \`ave\_token\_tool\` available) | | \*\*v2 (next)\*\* | Additional tools, multi-step reasoning workflows, and enhanced oracle features | > \*\*Note:\*\* The LLM can only return a single numeric choice in the range \`\[0, numOfChoices)\`.\ \ \*\*\*\ \ ## 2. Deployed Addresses & Supported Models\ \ ### Deployed Addresses\ \ | Network | Chain ID | Address | AI Explorer | Max Gas Limit |\ | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- | ------------- |\ | BSC Testnet | 97 | \[\`0xFfddcE44e8cFf7703Fd85118524bfC8B2f70b744\`\](https://testnet.bscscan.com/address/0xFfddcE44e8cFf7703Fd85118524bfC8B2f70b744) | \[AI Scan BNB\](https://flap.sh/ai/bnb-testnet/scan) | 2,000,000 |\ | BNB Mainnet | 56 | \[\`0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39\`\](https://bscscan.com/address/0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39) | \[AI Scan BNB\](https://flap.sh/ai/bnb/scan) | 2,000,000 |\ \ \*\*Max Gas Limit:\*\* The maximum gas limit supported for a \`fulfillReasoning\` callback.\ \ {% hint style="warning" %}\ \*\*Testnet note:\*\* Since AI inference is expensive, our testnet LLM backend operates with a limited budget. Fulfillment may not respond if funds are exhausted. If you'd like to test on testnet, feel free to reach out to us. Note that some tools may not be supported on testnet (e.g. \`ave\_token\_tool\`).\ {% endhint %}\ \ ### Supported Models\ \ The following models are available on both BSC Testnet and BNB Mainnet:\ \ | Model ID | Name | Price per Request |\ | -------- | ----------------------------- | ---------------------- |\ | \`0\` | \`google/gemini-3-flash\` | ~~0.01 BNB~~ 0.005 BNB |\ | \`1\` | \`anthropic/claude-sonnet-4.6\` | 0.05 BNB |\ | \`2\` | \`deepseek/deepseek-r1\` | 0.03 BNB |\ | \`3\` | \`deepseek/deepseek-v4-flash\` | 0.01 BNB |\ \ Query any model on-chain at any time:\ \ \`\`\`solidity\ IFlapAIProvider.Model memory m = IFlapAIProvider(flapAIProvider).getModel(modelId);\ // m.name, m.price, m.enabled\ \`\`\`\ \ \*\*\*\ \ ## 3. Tool Calling Support\ \ FlapAIProvider supports \*\*tool calling\*\*, allowing the LLM to fetch real-time external data before making a decision. The oracle backend automatically executes tool calls embedded in your prompt and injects the results into the LLM context.\ \ ### Available Tools\ \ Currently, the following tool is supported:\ \ #### \`ave\_token\_tool\`\ \ Fetches comprehensive market data for a Flap token, including:\ \ \* Current price and market cap\ \* Price change percentages\ \* Trading volume (24h, 7d)\ \* Holder count\ \* Liquidity status\ \ \*\*Usage example in prompt:\*\*\ \ \`\`\`solidity\ string memory prompt = string(abi.encodePacked(\ "I am managing a vault for token 0x1234...5678. ",\ "My main goal is to use my fund for market making. ",\ "Check the market data (use ave\_token\_tool) of this token and decide: ",\ "(0) buy tokens to support the floor, ",\ "(1) sell holdings to gain more funds for future market making, ",\ "(2) generate volumes only (buy and sell immediately)."\ ));\ \`\`\`\ \ The oracle will automatically detect the tool request, fetch the token data, and provide it to the LLM before it makes a choice.\ \ {% hint style="info" %}\ \*\*Need additional tools?\*\* If you need specific data sources or APIs to implement your vault strategy, feel free to reach out to us. We're actively expanding the tool library based on community needs.\ {% endhint %}\ \ \*\*\*\ \ ## 4. How to Integrate\ \ ### Step 1 — Extend \`FlapAIConsumerBase\`\ \ Your contract must inherit \`FlapAIConsumerBase\` and implement three members:\ \ \`\`\`solidity\ // SPDX-License-Identifier: MIT\ pragma solidity ^0.8.13;\ \ import {FlapAIConsumerBase, IFlapAIProvider} from "src/plugins/AIProvider/IFlapAIProvider.sol";\ \ contract MyVault is FlapAIConsumerBase {\ // Track the outstanding request.\ uint256 private \_lastRequestId;\ \ // Required: expose the pending request ID (0 = none).\ function lastRequestId() public view override returns (uint256) {\ return \_lastRequestId;\ }\ \ // Required: handle the LLM's decision.\ // At most 1M gas is forwarded, so keep it efficient and avoid complex logic or external calls.\ function \_fulfillReasoning(uint256 requestId, uint8 choice) internal override {\ require(requestId == \_lastRequestId, "unknown request");\ \_lastRequestId = 0;\ \ if (choice == 0) {\ \_executeBuy();\ } else if (choice == 1) {\ \_executeSell();\ } else {\ // noop\ }\ }\ \ // Required: handle a refund / failed request.\ function \_onFlapAIRequestRefunded(uint256 requestId) internal override {\ require(requestId == \_lastRequestId, "unknown request");\ \_lastRequestId = 0;\ // Re-enable any paused operations or retry logic here.\ }\ }\ \`\`\`\ \ ### Step 2 — Submit a Reasoning Request\ \ The vault triggers \`reason()\` inside \`receive()\`. An internal \`\_shouldReason()\` method encapsulates the go/no-go logic (balance threshold, cooldown, etc.) so the \`receive()\` body stays clean:\ \ \`\`\`solidity\ receive() external payable {\ // Only one request at a time.\ if (\_lastRequestId != 0) return;\ \ // Delegate the go/no-go decision to an internal helper.\ if (!\_shouldReason()) return;\ \ uint256 MODEL\_ID = 0; // google/gemini-3-flash\ uint8 NUM\_CHOICES = 3;\ \ // Build a self-contained prompt that defines all choices.\ string memory prompt = string(abi.encodePacked(\ "You are a DeFi vault manager. The vault holds 10 BNB. ",\ "Current price is $300. 7-day trend is +15%. ",\ "Choose one action:\\n",\ "0 = buy more BNB\\n",\ "1 = sell all BNB\\n",\ "2 = do nothing\\n",\ "Respond with only the number of your choice."\ ));\ \ // Fetch the required fee from the registry.\ IFlapAIProvider provider = IFlapAIProvider(\_getFlapAIProvider());\ uint256 fee = provider.getModel(MODEL\_ID).price;\ \ \_lastRequestId = provider.reason{value: fee}(MODEL\_ID, prompt, NUM\_CHOICES);\ }\ \ /// @dev Override to implement your go/no-go logic (e.g. balance threshold, cooldown).\ function \_shouldReason() internal view virtual returns (bool);\ \`\`\`\ \ > \*\*Cost note:\*\* \`reason()\` does \*\*not\*\* refund excess \`msg.value\`. Overpayment becomes protocol revenue. Always pass exactly \`model.price\` (or query it on-chain first).\ \ ### Step 3 — Verify a Fulfilled Request\ \ After the oracle calls back, the full reasoning proof is available on-chain forever:\ \ \`\`\`solidity\ string memory cid = IFlapAIProvider(\_getFlapAIProvider()).getReasoningCid(requestId);\ // Fetch from IPFS: https://ipfs.io/ipfs/\ \`\`\`\ \ ### Step 4 — Override \`\_getFlapAIProvider()\` for Testing\ \ In tests or on unsupported chains, override the address resolver:\ \ \`\`\`solidity\ function \_getFlapAIProvider() internal view override returns (address) {\ return address(mockProvider); // your test double\ }\ \`\`\`\ \ ### Full Example — Minimal Consumer\ \ \`\`\`solidity\ // SPDX-License-Identifier: MIT\ pragma solidity ^0.8.13;\ \ import {FlapAIConsumerBase, IFlapAIProvider} from "src/plugins/AIProvider/IFlapAIProvider.sol";\ import {VaultBase} from "src/interfaces/VaultBase.sol";\ \ /// @notice Minimal example consumer that lets an LLM decide between buy / sell / noop.\ contract MinimalVault is VaultBase, FlapAIConsumerBase {\ uint256 public lastRequestId;\ uint8 public lastChoice;\ \ uint256 constant MODEL\_ID = 0; // google/gemini-3-flash\ uint8 constant NUM\_CHOICES = 3;\ uint256 constant THRESHOLD = 1 ether; // only ask AI when vault holds > 1 BNB\ \ /// @notice Accept incoming tax revenue from the token.\ /// @dev Triggers AI reasoning when \_shouldReason() approves.\ receive() external payable {\ // Only send one request at a time; skip if one is already pending.\ if (lastRequestId != 0) return;\ \ // Delegate the go/no-go decision to an internal helper.\ if (!\_shouldReason()) return;\ \ IFlapAIProvider provider = IFlapAIProvider(\_getFlapAIProvider());\ uint256 fee = provider.getModel(MODEL\_ID).price;\ \ // Build a self-contained prompt that describes the vault state and\ // the meaning of every numbered choice.\ string memory prompt = string(abi.encodePacked(\ "You are a DeFi vault manager for a tax token on BNB Chain. ",\ "The vault currently holds ", \_uint2str(address(this).balance / 1 ether), " BNB. ",\ "The threshold to act is 1 BNB. ",\ "Decide what to do with the accumulated BNB:\\n",\ "0 = buyback tokens using all available BNB\\n",\ "1 = hold and accumulate more BNB\\n",\ "2 = distribute BNB to token holders\\n",\ "Respond with only the number of your choice."\ ));\ \ lastRequestId = provider.reason{value: fee}(MODEL\_ID, prompt, NUM\_CHOICES);\ }\ \ /// @dev Override to implement your trigger logic (e.g. balance threshold, cooldown).\ function \_shouldReason() internal view override returns (bool) {}\ \ function \_fulfillReasoning(uint256 requestId, uint8 choice) internal override {\ require(requestId == lastRequestId);\ lastRequestId = 0;\ lastChoice = choice;\ \ if (choice == 0) {\ // TODO: execute buyback via \_getPortal()\ } else if (choice == 1) {\ // hold — do nothing\ } else if (choice == 2) {\ // TODO: distribute BNB to token holders\ }\ }\ \ function \_onFlapAIRequestRefunded(uint256 requestId) internal override {\ require(requestId == lastRequestId);\ lastRequestId = 0;\ // Next receive() call will retry automatically.\ }\ \ /// @inheritdoc VaultBase\ function description() public view override returns (string memory) {\ return string(abi.encodePacked(\ "Minimal AI vault. Balance: ", \_uint2str(address(this).balance / 1 ether), " BNB. ",\ "Last choice: ", \_uint2str(lastChoice), "."\ ));\ }\ \ // ----------------------------------------------------------------\ // Internal helpers\ // ----------------------------------------------------------------\ \ function \_uint2str(uint256 v) internal pure returns (string memory) {\ if (v == 0) return "0";\ uint256 tmp = v;\ uint256 digits;\ while (tmp != 0) { digits++; tmp /= 10; }\ bytes memory buf = new bytes(digits);\ while (v != 0) { buf\[--digits\] = bytes1(uint8(48 + v % 10)); v /= 10; }\ return string(buf);\ }\ }\ \`\`\`\ \ \*\*\*\ \ ## 5. References\ \ ### \`IFlapAIProvider.sol\`\ \ \`\`\`solidity\ // SPDX-License-Identifier: MIT\ pragma solidity ^0.8.13;\ \ // ============================================================\ // IFlapAIProvider Interface\ // ============================================================\ \ /// @title IFlapAIProvider\ /// @notice Oracle interface for the FlapAIProvider commit-and-reveal AI reasoning service.\ /// @dev The FlapAIProvider operates as a commit-and-reveal oracle inspired by Chainlink VRF.\ /// Consumers (Vault contracts) submit a reasoning request on-chain by calling \`reason()\`,\ /// which emits an event. The off-chain oracle backend picks up the event, feeds the prompt\ /// to an LLM, and posts the result back via \`fulfillReasoning()\`. If the backend cannot\ /// process the request, it calls \`refundRequest()\` to return the BNB fee and notify the\ /// consumer. Consumers must extend \`FlapAIConsumerBase\` to receive callbacks securely.\ ///\ /// TOOL CALLING: The oracle backend supports tool calling, allowing the LLM to fetch\ /// real-time external data (e.g. token market data, prices) before producing its choice.\ /// Consumers embed tool invocations in the prompt using a structured format; the backend\ /// executes the tools and injects their results into the LLM context automatically.\ /// For the full list of supported tools, see:\ /// https://docs.flap.sh/flap/developers/preview/flap-ai-oracle\ ///\ /// Example — \`ave\_token\_info\` tool:\ /// "I am managing a vault for token 0x....7777, "\ /// "my main goal is to use my fund to market making for this token. "\ /// "Check the market data (use ave\_token\_info tool) of this token and then decide what to do: "\ /// "(0) buy tokens to support the floor "\ /// "(1) sell my holdings to gain more funds for future market making "\ /// "(2) generate volumes only (i.e: buy and then sell immediately)."\ interface IFlapAIProvider {\ // ----------------------------------------------------------------\ // Structs\ // ----------------------------------------------------------------\ \ /// @notice Represents a registered LLM model.\ /// @param name Human-readable name of the model (e.g. "gpt-4").\ /// @param price Per-request fee in native currency (BNB), in wei.\ /// @param enabled Whether the model currently accepts new requests.\ struct Model {\ string name;\ uint256 price;\ bool enabled;\ }\ \ /// @notice Lifecycle state of an AI reasoning request.\ /// @dev NONE = default, request was never created.\ /// PENDING = request submitted on-chain, awaiting oracle fulfillment.\ /// FULFILLED = oracle fulfilled the request and consumer callback succeeded.\ /// UNDELIVERED = oracle fulfilled but consumer callback reverted (result stored but not delivered).\ /// REFUNDED = oracle refunded the request fee to the consumer.\ enum RequestStatus {\ NONE,\ PENDING,\ FULFILLED,\ UNDELIVERED,\ REFUNDED\ }\ \ /// @notice Stores all data for a single AI reasoning request.\ /// @dev Tightly packed into two 32-byte storage slots:\ /// Slot 0 (immutable after \`reason()\`): consumer (160) + modelId (16) + numOfChoices (8) + timestamp (64) = 248 bits\ /// Slot 1 (written by oracle): feePaid (128) + status (8) + choice (8) + reserved (112) = 256 bits\ /// Separating immutable fields from mutable oracle results means \`fulfillReasoning\` and\ /// \`refundRequest\` only issue a single SSTORE to slot 1, leaving slot 0 untouched.\ /// @param consumer Address of the consuming contract that submitted the request.\ /// @param modelId ID of the LLM model used (max 65535 distinct models).\ /// @param numOfChoices Number of choices the LLM can pick from (valid range 0..numOfChoices-1).\ /// @param timestamp \`block.timestamp\` when the request was submitted via \`reason()\`.\ /// @param feePaid BNB amount paid with the request (full \`msg.value\`), capped at uint128.\ /// @param status Current lifecycle status of the request.\ /// @param choice The LLM's chosen action index; only valid when status is FULFILLED or UNDELIVERED.\ /// @param reserved Reserved for future upgrades.\ struct Request {\ // slot 0 — immutable after reason()\ address consumer; // 160 bits\ uint16 modelId; // 16 bits\ uint8 numOfChoices; // 8 bits\ uint64 timestamp; // 64 bits\ // slot 1 — written by fulfillReasoning() / refundRequest()\ uint128 feePaid; // 128 bits\ RequestStatus status; // 8 bits\ uint8 choice; // 8 bits\ uint112 reserved; // 112 bits\ }\ \ // ----------------------------------------------------------------\ // Custom Errors\ // ----------------------------------------------------------------\ \ /// @notice Read-only summary of a request, used exclusively by explorer view functions.\ /// @dev Avoids multiple RPC calls per request in list views by bundling all fields —\ /// including the IPFS CID — into a single return value.\ /// @param requestId The unique request ID.\ /// @param consumer Address of the consuming contract that submitted the request.\ /// @param modelId ID of the LLM model used.\ /// @param numOfChoices Number of choices the LLM could pick from.\ /// @param timestamp \`block.timestamp\` when the request was submitted.\ /// @param feePaid BNB amount paid with the request (in wei).\ /// @param status Current lifecycle status of the request.\ /// @param choice The LLM's chosen action index; only meaningful when status is FULFILLED or UNDELIVERED.\ /// @param reasoningCid IPFS CID of the reasoning proof; non-empty only when status is FULFILLED or UNDELIVERED.\ struct RequestView {\ uint256 requestId;\ address consumer;\ uint16 modelId;\ uint8 numOfChoices;\ uint64 timestamp;\ uint128 feePaid;\ RequestStatus status;\ uint8 choice;\ string reasoningCid;\ }\ \ /// @notice Reverts when the prompt byte length exceeds \`maxPromptLength\`.\ /// @param promptLength Actual length of the provided prompt in bytes.\ /// @param maxPromptLength Current maximum allowed prompt length.\ error FlapAIProviderPromptExceedsMaxLength(uint256 promptLength, uint256 maxPromptLength);\ \ /// @notice Reverts when \`numOfChoices\` is zero; there must be at least one choice.\ /// @param numOfChoices The invalid zero value that was passed.\ error FlapAIProviderInvalidNumOfChoices(uint8 numOfChoices);\ \ /// @notice Reverts when \`fulfillReasoning\` or \`refundRequest\` is called on a request that is not PENDING.\ /// @param requestId The request ID that is not in the PENDING state.\ error FlapAIProviderRequestNotPending(uint256 requestId);\ \ /// @notice Reverts when the oracle's \`choice\` is >= \`numOfChoices\`.\ /// @param choice The out-of-range choice returned by the oracle.\ /// @param numOfChoices The number of valid choices for the request.\ error FlapAIProviderChoiceOutOfRange(uint8 choice, uint8 numOfChoices);\ \ /// @notice Reverts when the BNB sent with \`reason()\` is less than the model's required price.\ /// @param sent Amount of BNB sent (in wei).\ /// @param required Minimum required fee for the selected model (in wei).\ error FlapAIProviderInsufficientFee(uint256 sent, uint256 required);\ \ /// @notice Reverts when \`reason()\` or \`getModel()\` is called with a \`modelId\` that was never registered.\ /// @param modelId The unregistered model ID.\ error FlapAIProviderModelNotRegistered(uint256 modelId);\ \ /// @notice Reverts when \`reason()\` is called with a registered but currently disabled model.\ /// @param modelId The disabled model ID.\ error FlapAIProviderModelNotEnabled(uint256 modelId);\ \ /// @notice Reverts when \`setCallbackGasLimit()\` is called with a value below the minimum of 1\_000\_000.\ /// @param provided The gas limit value that was passed.\ /// @param minimum The minimum allowed gas limit (1\_000\_000).\ error FlapAIProviderCallbackGasLimitTooLow(uint256 provided, uint256 minimum);\ \ // ----------------------------------------------------------------\ // Events\ // ----------------------------------------------------------------\ \ /// @notice Emitted when a new AI reasoning request is submitted.\ /// @param requestId Unique identifier for this request.\ /// @param consumer Address of the consuming contract.\ /// @param modelId ID of the LLM model selected.\ /// @param prompt The full prompt string sent to the oracle.\ /// @param numOfChoices Number of choices the LLM can return.\ /// @param feePaid BNB amount paid with the request (full msg.value).\ event FlapAIProviderRequestMade(\ uint256 requestId, address consumer, uint256 modelId, string prompt, uint8 numOfChoices, uint256 feePaid\ );\ \ /// @notice Emitted when the oracle successfully fulfills a request and the consumer callback succeeds.\ /// @param requestId The fulfilled request ID.\ /// @param consumer Address of the consuming contract.\ /// @param choice The choice returned by the LLM (0..numOfChoices-1).\ /// @param reasoningDetailsIpfsCid IPFS CID of the full reasoning proof stored on IPFS.\ event FlapAIProviderRequestFulfilled(\ uint256 requestId, address consumer, uint8 choice, string reasoningDetailsIpfsCid\ );\ \ /// @notice Emitted when the oracle fulfills a request but the consumer callback reverts.\ /// @param requestId The request ID whose consumer callback failed.\ /// @param consumer Address of the consuming contract.\ /// @param choice The choice returned by the LLM.\ /// @param reasoningDetailsIpfsCid IPFS CID of the reasoning proof (still stored on-chain).\ /// @param reason The revert data from the consumer callback.\ event FlapAIProviderRequestUndelivered(\ uint256 requestId, address consumer, uint8 choice, string reasoningDetailsIpfsCid, bytes reason\ );\ \ /// @notice Emitted when the oracle refunds a pending request.\ /// @param requestId The refunded request ID.\ /// @param consumer Address of the consuming contract that receives the refund.\ /// @param refundAmount BNB amount refunded (equals the original feePaid).\ event FlapAIProviderRequestRefunded(uint256 requestId, address consumer, uint256 refundAmount);\ \ /// @notice Emitted when the oracle marks a request as refunded but the consumer callback\ /// reverts or runs out of gas, leaving the BNB stranded in the provider.\ /// @param requestId The refunded request ID.\ /// @param consumer Address of the consuming contract whose callback failed.\ /// @param refundAmount BNB amount that could not be delivered (equals the original feePaid).\ /// @param reason The revert data from the consumer callback (empty on OOG).\ event FlapAIProviderRefundUndelivered(uint256 requestId, address consumer, uint256 refundAmount, bytes reason);\ \ /// @notice Emitted when the maximum prompt length is updated.\ /// @param oldMaxPromptLength Previous maximum prompt length in bytes.\ /// @param newMaxPromptLength New maximum prompt length in bytes.\ event FlapAIProviderMaxPromptLengthUpdated(uint256 oldMaxPromptLength, uint256 newMaxPromptLength);\ \ /// @notice Emitted when the consumer callback gas limit is updated.\ /// @param oldCallbackGasLimit Previous gas limit for consumer callbacks.\ /// @param newCallbackGasLimit New gas limit for consumer callbacks.\ event FlapAIProviderCallbackGasLimitUpdated(uint256 oldCallbackGasLimit, uint256 newCallbackGasLimit);\ \ /// @notice Emitted when a new LLM model is registered.\ /// @param modelId ID of the newly registered model.\ /// @param name Human-readable name of the model.\ /// @param price Per-request fee in native BNB (wei).\ event FlapAIProviderModelRegistered(uint256 modelId, string name, uint256 price);\ \ // ----------------------------------------------------------------\ // Functions\ // ----------------------------------------------------------------\ \ /// @notice Submit an AI reasoning request to the oracle.\ /// @dev Validates that:\ /// 1. \`modelId\` is a registered model (reverts FlapAIProviderModelNotRegistered).\ /// 2. The model is enabled (reverts FlapAIProviderModelNotEnabled).\ /// 3. \`numOfChoices > 0\` (reverts FlapAIProviderInvalidNumOfChoices).\ /// 4. \`bytes(prompt).length <= maxPromptLength\` (reverts FlapAIProviderPromptExceedsMaxLength).\ /// 5. \`msg.value >= model.price\` (reverts FlapAIProviderInsufficientFee).\ /// Any excess BNB beyond the model price is intentionally NOT refunded — it becomes protocol revenue.\ /// Emits {FlapAIProviderRequestMade}.\ ///\ /// TOOL CALLING: The oracle supports tool calling so the LLM can pull live data before\ /// reasoning. Declare the tools you want available inside the prompt string. The backend\ /// will execute any tool calls the LLM makes and feed the results back automatically.\ /// For the complete list of supported tools visit:\ /// https://docs.flap.sh/flap/developers/preview/flap-ai-oracle\ ///\ /// Example using \`ave\_token\_info\` to fetch live market data for a token:\ /// "I am managing a vault for token 0x....7777, "\ /// "my main goal is to use my fund to market making for this token. "\ /// "Check the market data (use ave\_token\_info tool) of this token and then decide what to do: "\ /// "(0) buy tokens to support the floor "\ /// "(1) sell my holdings to gain more funds for future market making "\ /// "(2) generate volumes only (i.e: buy and then sell immediately)."\ /// @param modelId ID of the LLM model to use for this request.\ /// @param prompt Combined system + user prompt. The consumer must embed the choice meanings\ /// (e.g., "0 = buy, 1 = sell") inside the prompt string. Tool descriptions\ /// may also be included here to enable tool calling (see @dev above).\ /// @param numOfChoices Number of choices the LLM may return (valid responses are 0..numOfChoices-1).\ /// @return requestId Unique uint256 identifier for this request. Store it to correlate with the callback.\ function reason(uint256 modelId, string calldata prompt, uint8 numOfChoices)\ external\ payable\ returns (uint256 requestId);\ \ /// @notice Return the full Model struct for a registered model.\ /// @dev Reverts with \`FlapAIProviderModelNotRegistered\` if the \`modelId\` was never registered.\ /// Does NOT revert for disabled models — callers can inspect the \`enabled\` field.\ /// @param modelId ID of the model to query.\ /// @return model The Model struct containing \`name\`, \`price\`, and \`enabled\`.\ function getModel(uint256 modelId) external view returns (Model memory model);\ \ /// @notice Fulfill a pending AI reasoning request.\ /// @dev Restricted to the oracle backend (FULFILLER\_ROLE). Validates:\ /// 1. Request status is PENDING (reverts FlapAIProviderRequestNotPending).\ /// 2. \`choice < request.numOfChoices\` (reverts FlapAIProviderChoiceOutOfRange).\ /// Stores \`reasoningDetailsIpfsCid\` on-chain, then calls \`fulfillReasoning(requestId, choice)\`\ /// on the consumer inside a try/catch:\ /// - On success: status → FULFILLED, emits {FlapAIProviderRequestFulfilled}.\ /// - On consumer revert: status → UNDELIVERED, emits {FlapAIProviderRequestUndelivered}.\ /// The IPFS CID is always stored regardless of callback outcome.\ /// @param requestId The ID of the pending request to fulfill.\ /// @param choice The LLM's chosen action (must be < numOfChoices).\ /// @param reasoningDetailsIpfsCid IPFS CID of the full reasoning proof document.\ function fulfillReasoning(uint256 requestId, uint8 choice, string calldata reasoningDetailsIpfsCid) external;\ \ /// @notice Refund a pending request when the oracle cannot process it.\ /// @dev Restricted to the oracle backend (FULFILLER\_ROLE). Validates that the request is PENDING.\ /// Sets status to REFUNDED, transfers the original \`feePaid\` BNB back to the consumer,\ /// then calls \`onFlapAIRequestRefunded{gas: 1\_000\_000}(requestId)\` on the consumer.\ /// Emits {FlapAIProviderRequestRefunded}.\ /// @param requestId The ID of the pending request to refund.\ function refundRequest(uint256 requestId) external;\ \ /// @notice Returns the current maximum allowed prompt length in bytes.\ /// @return The current \`\_maxPromptLength\` value (default: 6000).\ function maxPromptLength() external view returns (uint256);\ \ /// @notice Update the maximum allowed prompt length in bytes.\ /// @dev Restricted to DEFAULT\_ADMIN\_ROLE. Emits {FlapAIProviderMaxPromptLengthUpdated}.\ /// @param newMaxPromptLength The new maximum prompt length in bytes.\ function setMaxPromptLength(uint256 newMaxPromptLength) external;\ \ /// @notice Returns the gas limit forwarded to consumer callbacks (\`fulfillReasoning\` and \`onFlapAIRequestRefunded\`).\ /// @return The current \`\_maxCallbackGas\` value (default: 1\_000\_000).\ function callbackGasLimit() external view returns (uint256);\ \ /// @notice Update the gas limit forwarded to consumer callbacks.\ /// @dev Restricted to DEFAULT\_ADMIN\_ROLE. Reverts with \`FlapAIProviderCallbackGasLimitTooLow\` if\ /// \`newCallbackGasLimit\` is less than 1\_000\_000. Emits {FlapAIProviderCallbackGasLimitUpdated}.\ /// @param newCallbackGasLimit The new gas limit for consumer callbacks (must be >= 1\_000\_000).\ function setCallbackGasLimit(uint256 newCallbackGasLimit) external;\ \ // ----------------------------------------------------------------\ // Explorer View Functions\ // ----------------------------------------------------------------\ \ /// @notice Returns the total number of reasoning requests ever submitted.\ /// @dev Equivalent to \`\_nextRequestId - 1\`. Used by the explorer to compute pagination metadata.\ /// @return total The total request count.\ function getTotalRequests() external view returns (uint256 total);\ \ /// @notice Returns the total number of reasoning requests submitted by a specific consumer.\ /// @dev Used to compute per-consumer pagination metadata.\ /// @param consumer The consumer address to query.\ /// @return total The number of requests made by \`consumer\`.\ function getTotalRequestsByConsumer(address consumer) external view returns (uint256 total);\ \ /// @notice Returns a single \`RequestView\` for the given request ID.\ /// @dev Populates \`reasoningCid\` for all statuses (non-empty only when FULFILLED or UNDELIVERED).\ /// Returns a zero-valued struct if \`requestId\` was never created.\ /// @param requestId The request ID to look up.\ /// @return view\_ The populated \`RequestView\` for the given request.\ function getRequest(uint256 requestId) external view returns (RequestView memory view\_);\ \ /// @notice Returns up to \`limit\` requests in newest-first order, starting at \`offset\` from the end.\ /// @dev \`offset = 0, limit = 10\` returns the 10 most recent requests.\ /// Returns an empty array if \`offset >= getTotalRequests()\`.\ /// Clamps the result to available entries — never reverts out-of-bounds.\ /// \`reasoningCid\` is populated inline for each entry.\ /// @param offset Zero-based offset from the most recent request (0 = most recent).\ /// @param limit Maximum number of entries to return.\ /// @return views Array of \`RequestView\` in newest-first order.\ function getRecentRequests(uint256 offset, uint256 limit) external view returns (RequestView\[\] memory views);\ \ /// @notice Returns up to \`limit\` requests by a specific consumer in newest-first order.\ /// @dev Same pagination semantics as \`getRecentRequests\` but scoped to a single consumer.\ /// Returns an empty array if \`offset >= getTotalRequestsByConsumer(consumer)\`.\ /// @param consumer The consumer address to filter by.\ /// @param offset Zero-based offset from the consumer's most recent request.\ /// @param limit Maximum number of entries to return.\ /// @return views Array of \`RequestView\` in newest-first order for the given consumer.\ function getRequestsByConsumer(address consumer, uint256 offset, uint256 limit)\ external\ view\ returns (RequestView\[\] memory views);\ }\ \ // ============================================================\ // FlapAIConsumerBase Abstract Contract\ // ============================================================\ \ /// @title FlapAIConsumerBase\ /// @notice Base contract for contracts consuming FlapAIProvider responses.\ /// @dev Inheritors must override:\ /// - \`\_fulfillReasoning(uint256 requestId, uint8 choice)\`: executes the action chosen by the LLM.\ /// - \`\_onFlapAIRequestRefunded(uint256 requestId)\`: handles refund cleanup or retry logic.\ /// - \`lastRequestId()\`: returns the consumer's most recent pending request ID (0 if none).\ /// The \`onlyFlapAIProvider\` modifier is applied automatically to the external entry-points\ /// \`fulfillReasoning\` and \`onFlapAIRequestRefunded\`, ensuring that only the FlapAIProvider\ /// contract can invoke them. The provider address is resolved at runtime via\ /// \`\_getFlapAIProvider()\` using \`block.chainid\`, so no constructor parameter is needed.\ abstract contract FlapAIConsumerBase {\ // ----------------------------------------------------------------\ // Custom Errors\ // ----------------------------------------------------------------\ \ /// @notice Reverts when a function guarded by \`onlyFlapAIProvider\` is called by a non-provider address.\ error FlapAIConsumerOnlyProvider();\ \ /// @notice Reverts when \`\_getFlapAIProvider()\` is queried on an unsupported chain.\ /// @param chainId The unsupported chain ID.\ error FlapAIConsumerUnsupportedChain(uint256 chainId);\ \ // ----------------------------------------------------------------\ // Modifier\ // ----------------------------------------------------------------\ \ /// @dev Guards external entry-points so only the FlapAIProvider contract can call them.\ /// Reverts with \`FlapAIConsumerOnlyProvider\` if called by any other address.\ modifier onlyFlapAIProvider() {\ if (msg.sender != \_getFlapAIProvider()) revert FlapAIConsumerOnlyProvider();\ \_;\ }\ \ // ----------------------------------------------------------------\ // Provider Address Resolution\ // ----------------------------------------------------------------\ \ /// @notice Returns the FlapAIProvider proxy address for the current chain.\ /// @dev Uses \`block.chainid\` to resolve the stable proxy address:\ /// - Chain 56 (BSC Mainnet): returns \`address(0)\` — placeholder, update when deployed.\ /// - Chain 97 (BSC Testnet): returns \`address(0)\` — placeholder, update when deployed.\ /// - Any other chain: reverts with \`FlapAIConsumerUnsupportedChain\`.\ /// Subclasses may override this function to support additional chains or test environments.\ /// @return The address of the FlapAIProvider proxy on the current chain.\ function \_getFlapAIProvider() internal view virtual returns (address) {\ uint256 id = block.chainid;\ if (id == 56) {\ return 0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39;\ } else if (id == 97) {\ return 0xFfddcE44e8cFf7703Fd85118524bfC8B2f70b744;\ } else {\ revert FlapAIConsumerUnsupportedChain(id);\ }\ }\ \ // ----------------------------------------------------------------\ // Consumer State (abstract)\ // ----------------------------------------------------------------\ \ /// @notice Returns the request ID of the most recent AI reasoning request made by this consumer.\ /// @dev Must be overridden by the consuming contract. Returns \`0\` if no request has been made yet.\ /// This allows the provider backend and frontends to look up the consumer's outstanding request\ /// without iterating through the provider's full request history.\ /// @return The last submitted request ID, or 0 if none.\ function lastRequestId() public view virtual returns (uint256);\ \ // ----------------------------------------------------------------\ // External Entry-Points (access-controlled)\ // ----------------------------------------------------------------\ \ /// @notice Called by the FlapAIProvider to deliver the LLM's chosen action.\ /// @dev Only callable by the FlapAIProvider contract (enforced by \`onlyFlapAIProvider\`).\ /// Delegates to the internal virtual \`\_fulfillReasoning\` for override-able logic.\ /// This external/internal split prevents inheritors from inadvertently removing the\ /// access control check while still overriding the business logic.\ /// @param requestId The ID of the fulfilled request.\ /// @param choice The LLM's chosen action (index in 0..numOfChoices-1).\ function fulfillReasoning(uint256 requestId, uint8 choice) external onlyFlapAIProvider {\ \_fulfillReasoning(requestId, choice);\ }\ \ /// @notice Called by the FlapAIProvider when a request is refunded.\ /// @dev Only callable by the FlapAIProvider contract (enforced by \`onlyFlapAIProvider\`).\ /// Delegates to the internal virtual \`\_onFlapAIRequestRefunded\`.\ /// The provider calls this with \`{value: feePaid}\` so the BNB refund is delivered here\ /// rather than via a bare ETH transfer (which would trigger \`receive()\`/\`fallback()\`).\ /// The provider also applies an explicit gas cap to prevent unbounded gas consumption.\ /// @param requestId The ID of the refunded request.\ function onFlapAIRequestRefunded(uint256 requestId) external payable onlyFlapAIProvider {\ \_onFlapAIRequestRefunded(requestId);\ }\ \ // ----------------------------------------------------------------\ // Internal Virtual Hooks (to be overridden)\ // ----------------------------------------------------------------\ \ /// @notice Internal hook invoked when the FlapAIProvider delivers the LLM's choice.\ /// @dev Must be overridden by the consuming contract to execute the action corresponding to \`choice\`.\ /// The consumer contract is responsible for mapping each \`choice\` index to its intended action,\ /// consistent with the meanings encoded in the original prompt.\ /// @param requestId The ID of the fulfilled request.\ /// @param choice The LLM's chosen action index.\ function \_fulfillReasoning(uint256 requestId, uint8 choice) internal virtual;\ \ /// @notice Internal hook invoked when the FlapAIProvider refunds a request.\ /// @dev Must be overridden by the consuming contract to perform cleanup or retry logic\ /// (e.g., reset a pending-action flag, re-enable a paused operation).\ /// \`msg.value\` equals the original \`feePaid\` — the BNB refund is delivered with this call.\ /// @param requestId The ID of the refunded request.\ function \_onFlapAIRequestRefunded(uint256 requestId) internal virtual;\ }\ \ \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/vault-developers/gift-vault.md). # Gift Vault (FlapXVault) ## Overview The Gift Vault (also known as FlapXVault) allows you to assign an X (Twitter) account as the \*\*Gift Owner\*\*, who can direct the trading fees to any EVM address of their choice: 1. \*\*Assign Gift Owner\*\*: The X account you specify becomes the gift owner who controls where fees go. 2. \*\*Flexible Beneficiary\*\*: The gift owner can assign fees to any EVM address and change it anytime. 3. \*\*7-Day Grace Period\*\*: If the gift owner doesn't manage the fees at least once in the first 7 days, control is forfeited. 4. \*\*Fallback: Buyback & Burn\*\*: If control is forfeited, fees will automatically be used for buyback and burn. You can use it as a way to give tax fee to a X user or you can build more interesting use cases on top of it! We build the gift vault to make it easier for a web2 developer or an agent to manage the tax fee through social proof. Here are some interesting ideas: \* Create an X account for an agent and launch a tax token that assigns the agent as the gift owner. The agent can then route the tax fee to any EVM address. The Agent can manage his own fund by accepting the funding request through X: The requesters can tweet to the agent's X account and ask the agent to support them by routing the tax fee to their address for some duration. The agent can choose to accept or reject the request by managing the vault through tweeting. This could be a charity funds or a VC that supports new projects. \* A social game where players tweet to a game X account, and gets the tax fee routed to their address based on some social tasks or achievements. In the following sections, we introduce how to integrate with the Gift Vault through its API. By following this guide, you will be able to build a complete integration that allows users to manage their vaults using X tweets as social proof. ## Table of contents 1. \[Prerequisites\](#prerequisites) 2. \[Network Details\](#network-details) 3. \[Architecture overview\](#architecture-overview) 4. \[Step-by-step integration\](#step-by-step-integration) 5. \[Complete code example\](#complete-code-example) 6. \[API reference\](#api-reference) 7. \[Error handling\](#error-handling) ## Prerequisites \* \*\*Web3 Library\*\*: ethers.js v6, viem, or web3.js \* \*\*Vault Factory Contract Address\*\*: See \[Network Details\](#network-details) \* \*\*Tax Token Address\*\* \* \*\*X Handle\*\* (Twitter username of vault owner) \* \*\*Tweet\*\* (you must fetch tweet data yourself) ## Network Details ### BNB Smart Chain Mainnet \* \*\*Gift Vault Factory\*\*: \`0x025549F52B03cF36f9e1a337c02d3AA7Af66ab32\` \* \*\*Relayer API Endpoint\*\*: \`https://bnb-x-relayer.taxed.fun/submit\` ### BNB Smart Chain Testnet \* \*\*Gift Vault Factory\*\*: \`0xa02DA44D67DB6D692efa7f751b5952bd670d5326\` \* \*\*Relayer API Endpoint\*\*: \`https://bnbtest-x-relayer.taxed.fun/submit\` ## Architecture overview Flow Diagram: \`\`\` ┌──────────────────┐ │ Start Request │ └────────┬─────────┘ │ ▼ ┌─────────────────────────────────────┐ │ Step 1: Parse & Validate Tweet │ │ - Fetch tweet from Twitter API │ │ - Verify author matches xHandle │ │ - Parse tweet text format │ │ - Verify tax token matches │ └────────┬────────────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ ⚠️ Step 2: MANDATORY PREFLIGHT │ │ Call canManageVault() on-chain │ │ │ │ ⚠️ Backend is rate-limited (1/min) │ │ ⚠️ NEVER skip this step! │ └────────┬────────────────────────────┘ │ ▼ ┌────────┐ │ Pass? │ └───┬─┬──┘ Yes │ │ No → STOP! Do NOT call backend │ ▼ ┌─────────────────────────────────────┐ │ Step 3: Submit to Backend (1/min) │ │ POST /submit │ │ {tax\_token, tweet\_id} │ └────────┬────────────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ Step 4: Poll canManageVault() │ │ Every 15s for 60s max │ │ Success = errorMessage:"outdated" │ └─────────────────────────────────────┘ \`\`\` ## Step-by-step integration ### Step 1: Parse and validate tweet content Integrators must fetch the tweet themselves. The tweet format is \*\*strictly enforced\*\*: \*\*Tweet Format:\*\* \`gift the fee from \[tax\_token\_address\] to \[target\_address\] #FlapGift\` \*\*Implementation:\*\* \`\`\`typescript // Type definitions interface ParsedTweet { targetAddress: string; taxTokenAddress: string; } interface Tweet { \_\_typename: string; text?: string; user?: { screen\_name?: string; legacy?: { screen\_name?: string }; }; } // Parse tweet text using regex function parseTweetText(text: string): ParsedTweet | null { // Pattern: "gift the fee from to #FlapGift" const regex = /gift\\s+the\\s+fee\\s+from\\s+(0x\[a-fA-F0-9\]{40})\\s+to\\s+(0x\[a-fA-F0-9\]{40})\\s+#FlapGift/i; const match = text.match(regex); if (!match) { return null; } return { taxTokenAddress: match\[1\], // First captured group targetAddress: match\[2\] // Second captured group }; } // Validate tweet content function validateTweetContent( tweet: Tweet, expectedXHandle: string, expectedTaxToken: string ): ParsedTweet { // Check tweet exists and is valid if (!tweet || tweet.\_\_typename !== "Tweet") { throw new Error("Tweet is not valid (deleted, suspended, or not found)"); } // Check tweet has text if (!tweet.text) { throw new Error("Tweet has no text content"); } // Check tweet has user data if (!tweet.user) { throw new Error("Tweet has no user data"); } // Verify tweet author matches expected xHandle (case-insensitive) const tweetScreenName = tweet.user.screen\_name || tweet.user.legacy?.screen\_name; if (!tweetScreenName) { throw new Error("Tweet author screen\_name not found"); } if (tweetScreenName.toLowerCase() !== expectedXHandle.toLowerCase()) { throw new Error( \`Tweet author mismatch. Expected: @${expectedXHandle}, Found: @${tweetScreenName}\` ); } // Parse tweet text format const parsed = parseTweetText(tweet.text); if (!parsed) { throw new Error( "Tweet text does not match the required format. " + "Expected: 'gift the fee from \[tax token address\] to \[EVM address\] #FlapGift'" ); } // Verify tax token matches if (parsed.taxTokenAddress.toLowerCase() !== expectedTaxToken.toLowerCase()) { throw new Error( \`Tax token mismatch. Expected: ${expectedTaxToken}, Found: ${parsed.taxTokenAddress}\` ); } return parsed; } \`\`\` ### Step 2: ⚠️ MANDATORY PREFLIGHT CHECK \*\*ALWAYS call this before backend submission!\*\* \*\*Using ethers.js v6:\*\* \`\`\`typescript import { Contract, JsonRpcProvider } from 'ethers'; // Minimal ABI for canManageVault const VAULT\_FACTORY\_ABI = \[\ {\ "inputs": \[\ {"internalType": "address", "name": "taxToken", "type": "address"},\ {"internalType": "string", "name": "xHandle", "type": "string"},\ {"internalType": "uint128", "name": "tweetId", "type": "uint128"}\ \],\ "name": "canManageVault",\ "outputs": \[\ {"internalType": "bool", "name": "canManage", "type": "bool"},\ {"internalType": "string", "name": "errorMessage", "type": "string"}\ \],\ "stateMutability": "view",\ "type": "function"\ }\ \]; async function preflightCheck( provider: JsonRpcProvider, factoryAddress: string, taxToken: string, xHandle: string, tweetId: string ): Promise { console.log("⚠️ PREFLIGHT CHECK (Mandatory before backend call)"); console.log(" Prevents rate limit violations (1 req/min)"); const contract = new Contract(factoryAddress, VAULT\_FACTORY\_ABI, provider); // xHandle MUST be lowercase for contract compatibility const result = await contract.canManageVault( taxToken, xHandle.toLowerCase(), BigInt(tweetId) ); const canManage: boolean = result\[0\]; const errorMessage: string = result\[1\]; if (!canManage) { throw new Error( \`❌ PREFLIGHT CHECK FAILED: ${errorMessage}\\n\` + \` DO NOT call backend API - request will be rejected!\` ); } console.log("✅ Preflight check PASSED - safe to proceed"); } \`\`\` \*\*Using viem:\*\* \`\`\`typescript import { createPublicClient, http } from 'viem'; const VAULT\_FACTORY\_ABI = \[\ {\ inputs: \[\ { name: 'taxToken', type: 'address' },\ { name: 'xHandle', type: 'string' },\ { name: 'tweetId', type: 'uint128' }\ \],\ name: 'canManageVault',\ outputs: \[\ { name: 'canManage', type: 'bool' },\ { name: 'errorMessage', type: 'string' }\ \],\ stateMutability: 'view',\ type: 'function'\ }\ \] as const; async function preflightCheckViem( rpcUrl: string, factoryAddress: \`0x${string}\`, taxToken: \`0x${string}\`, xHandle: string, tweetId: bigint ): Promise { const client = createPublicClient({ transport: http(rpcUrl) }); const result = await client.readContract({ address: factoryAddress, abi: VAULT\_FACTORY\_ABI, functionName: 'canManageVault', args: \[\ taxToken,\ xHandle.toLowerCase(), // MUST be lowercase\ tweetId\ \] }); const \[canManage, errorMessage\] = result; if (!canManage) { throw new Error(\`Preflight failed: ${errorMessage}\`); } } \`\`\` {% hint style="warning" %} \*\*Important Notes:\*\* \* \*\*xHandle must be lowercase\*\* when calling the contract \* This is a \*\*view function\*\* - no gas required, instant response \* Returns \`\[bool canManage, string errorMessage\]\` \* \*\*ALWAYS check this before backend\*\* to avoid rate limit violations {% endhint %} ### Step 3: Submit to backend API \*\*⚠️ ONLY call if Step 2 passed!\*\* \*\*Rate Limits:\*\* \* \*\*1 request per minute per IP\*\* \* \*\*Violating may result in IP ban\*\* \*\*Endpoint:\*\* \`POST /submit\` \`\`\`typescript interface SubmitRequest { tax\_token: string; // Ethereum address (0x...) tweet\_id: string; // Twitter/X tweet ID as string } interface SubmitResponse { message: string; x\_proof: { target\_address: string; tax\_token: string; x\_handle: string; x\_id: string; tweet\_id: string; }; signature: string; // Oracle signature (hex) } async function submitToBackend( relayerEndpoint: string, taxToken: string, tweetId: string ): Promise { const url = \`${relayerEndpoint}submit\`; console.log("📡 Submitting to backend (rate-limited: 1/min)"); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ tax\_token: taxToken, tweet\_id: tweetId, }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(\`Backend error: ${errorData.error}\`); } const data: SubmitResponse = await response.json(); console.log("✅ Backend submission successful"); return data; } \`\`\` ### Step 4: Poll for on-chain confirmation The relayer processes transactions asynchronously. Poll \`canManageVault\` to confirm success. \*\*Why Poll?\*\* \* Relayer doesn't return transaction hash \* Transaction is queued for later processing \* Once processed, \`canManageVault\` returns \`false\` with error: "The tweet is outdated" \*\*Implementation:\*\* \`\`\`typescript async function pollForConfirmation( provider: JsonRpcProvider, factoryAddress: string, taxToken: string, xHandle: string, tweetId: string ): Promise { const startTime = Date.now(); const TIMEOUT = 60000; // 60 seconds const POLL\_INTERVAL = 15000; // 15 seconds const contract = new Contract(factoryAddress, VAULT\_FACTORY\_ABI, provider); return new Promise((resolve, reject) => { const interval = setInterval(async () => { const elapsed = Date.now() - startTime; // Timeout after 60 seconds if (elapsed >= TIMEOUT) { clearInterval(interval); reject(new Error("Polling timeout (60s) - transaction may still be processing")); return; } try { const result = await contract.canManageVault( taxToken, xHandle.toLowerCase(), BigInt(tweetId) ); const canManage: boolean = result\[0\]; const errorMessage: string = result\[1\]; // Success: tweet marked as "outdated" means proof was recorded if (!canManage && errorMessage === "The tweet is outdated") { clearInterval(interval); console.log("✅ Transaction confirmed on-chain!"); resolve(true); return; } console.log(\` Polling... (${Math.floor(elapsed / 1000)}s elapsed)\`); } catch (error) { console.error("Polling error:", error); } }, POLL\_INTERVAL); }); } \`\`\` ## Complete code example \`\`\`typescript import { Contract, JsonRpcProvider } from 'ethers'; // Configuration const FACTORY\_ADDRESS = "0xa02DA44D67DB6D692efa7f751b5952bd670d5326"; const RELAYER\_ENDPOINT = "https://xrelayer.taxed.fun/"; const RPC\_URL = "https://your-rpc-endpoint"; const VAULT\_FACTORY\_ABI = \[\ {\ "inputs": \[\ {"name": "taxToken", "type": "address"},\ {"name": "xHandle", "type": "string"},\ {"name": "tweetId", "type": "uint128"}\ \],\ "name": "canManageVault",\ "outputs": \[\ {"name": "canManage", "type": "bool"},\ {"name": "errorMessage", "type": "string"}\ \],\ "stateMutability": "view",\ "type": "function"\ }\ \]; // Type definitions interface ParsedTweet { targetAddress: string; taxTokenAddress: string; } interface Tweet { \_\_typename: string; text?: string; user?: { screen\_name?: string; legacy?: { screen\_name?: string }; }; } interface SubmitResponse { message: string; x\_proof: { target\_address: string; tax\_token: string; x\_handle: string; x\_id: string; tweet\_id: string; }; signature: string; } // Parse tweet text using regex function parseTweetText(text: string): ParsedTweet | null { const regex = /gift\\s+the\\s+fee\\s+from\\s+(0x\[a-fA-F0-9\]{40})\\s+to\\s+(0x\[a-fA-F0-9\]{40})\\s+#FlapGift/i; const match = text.match(regex); if (!match) { return null; } return { taxTokenAddress: match\[1\], targetAddress: match\[2\] }; } // Validate tweet content function validateTweetContent( tweet: Tweet, expectedXHandle: string, expectedTaxToken: string ): ParsedTweet { if (!tweet || tweet.\_\_typename !== "Tweet") { throw new Error("Tweet is not valid (deleted, suspended, or not found)"); } if (!tweet.text) { throw new Error("Tweet has no text content"); } if (!tweet.user) { throw new Error("Tweet has no user data"); } const tweetScreenName = tweet.user.screen\_name || tweet.user.legacy?.screen\_name; if (!tweetScreenName) { throw new Error("Tweet author screen\_name not found"); } if (tweetScreenName.toLowerCase() !== expectedXHandle.toLowerCase()) { throw new Error( \`Tweet author mismatch. Expected: @${expectedXHandle}, Found: @${tweetScreenName}\` ); } const parsed = parseTweetText(tweet.text); if (!parsed) { throw new Error( "Tweet text does not match the required format. " + "Expected: 'gift the fee from \[tax token address\] to \[EVM address\] #FlapGift'" ); } if (parsed.taxTokenAddress.toLowerCase() !== expectedTaxToken.toLowerCase()) { throw new Error( \`Tax token mismatch. Expected: ${expectedTaxToken}, Found: ${parsed.taxTokenAddress}\` ); } return parsed; } // Preflight check async function preflightCheck( provider: JsonRpcProvider, factoryAddress: string, taxToken: string, xHandle: string, tweetId: string ): Promise { console.log("⚠️ PREFLIGHT CHECK (Mandatory before backend call)"); const contract = new Contract(factoryAddress, VAULT\_FACTORY\_ABI, provider); const result = await contract.canManageVault( taxToken, xHandle.toLowerCase(), BigInt(tweetId) ); const canManage: boolean = result\[0\]; const errorMessage: string = result\[1\]; if (!canManage) { throw new Error( \`❌ PREFLIGHT CHECK FAILED: ${errorMessage}\\n\` + \` DO NOT call backend API - request will be rejected!\` ); } console.log("✅ Preflight check PASSED"); } // Submit to backend async function submitToBackend( relayerEndpoint: string, taxToken: string, tweetId: string ): Promise { const url = \`${relayerEndpoint}submit\`; console.log("📡 Submitting to backend (rate-limited: 1/min)"); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ tax\_token: taxToken, tweet\_id: tweetId }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(\`Backend error: ${errorData.error}\`); } const data: SubmitResponse = await response.json(); console.log("✅ Backend submission successful"); return data; } // Poll for confirmation async function pollForConfirmation( provider: JsonRpcProvider, factoryAddress: string, taxToken: string, xHandle: string, tweetId: string ): Promise { const startTime = Date.now(); const TIMEOUT = 60000; const POLL\_INTERVAL = 15000; const contract = new Contract(factoryAddress, VAULT\_FACTORY\_ABI, provider); return new Promise((resolve, reject) => { const interval = setInterval(async () => { const elapsed = Date.now() - startTime; if (elapsed >= TIMEOUT) { clearInterval(interval); reject(new Error("Polling timeout (60s)")); return; } try { const result = await contract.canManageVault( taxToken, xHandle.toLowerCase(), BigInt(tweetId) ); const canManage: boolean = result\[0\]; const errorMessage: string = result\[1\]; if (!canManage && errorMessage === "The tweet is outdated") { clearInterval(interval); console.log("✅ Transaction confirmed on-chain!"); resolve(true); return; } console.log(\` Polling... (${Math.floor(elapsed / 1000)}s elapsed)\`); } catch (error) { console.error("Polling error:", error); } }, POLL\_INTERVAL); }); } // Main integration function async function submitVaultProof( tweet: Tweet, taxToken: string, xHandle: string, tweetId: string ): Promise { const provider = new JsonRpcProvider(RPC\_URL); try { console.log("=== Gift Vault Proof Submission ===\\n"); // STEP 1: Validate Tweet Content console.log("Step 1: Validating tweet content..."); const parsed = validateTweetContent(tweet, xHandle, taxToken); console.log("✅ Tweet validation passed"); console.log(\` Author: @${tweet.user?.screen\_name}\`); console.log(\` Tax Token: ${parsed.taxTokenAddress}\`); console.log(\` Target: ${parsed.targetAddress}\\n\`); // STEP 2: MANDATORY PREFLIGHT CHECK console.log("Step 2: ⚠️ PREFLIGHT CHECK"); await preflightCheck(provider, FACTORY\_ADDRESS, taxToken, xHandle, tweetId); console.log("✅ Preflight passed - safe to call backend\\n"); // STEP 3: Submit to Backend console.log("Step 3: Submitting to backend..."); const proofData = await submitToBackend(RELAYER\_ENDPOINT, taxToken, tweetId); console.log("✅ Backend accepted submission\\n"); // STEP 4: Poll for Confirmation console.log("Step 4: Waiting for on-chain confirmation..."); await pollForConfirmation(provider, FACTORY\_ADDRESS, taxToken, xHandle, tweetId); console.log("\\n✅ SUCCESS: Vault proof recorded on-chain!"); console.log("=== Submission Complete ==="); return proofData; } catch (error) { console.error("\\n❌ FAILED:", error); throw error; } } // Usage Example async function main() { const tweet = { \_\_typename: "Tweet", text: "gift the fee from 0x1234567890abcdef1234567890abcdef12345678 to 0xabcdefabcdefabcdefabcdefabcdefabcdefabcd #FlapGift", user: { screen\_name: "username" } }; await submitVaultProof( tweet, "0x1234567890abcdef1234567890abcdef12345678", "username", "1234567890123456789" ); } \`\`\` ## API reference ### Tweet content validation #### parseTweetText(text: string) \*\*Regex Pattern:\*\* \`\`\` /gift\\s+the\\s+fee\\s+from\\s+(0x\[a-fA-F0-9\]{40})\\s+to\\s+(0x\[a-fA-F0-9\]{40})\\s+#FlapGift/i \`\`\` \*\*Valid Example:\*\* \`\`\` gift the fee from 0x1234567890abcdef1234567890abcdef12345678 to 0xabcdefabcdefabcdefabcdefabcdefabcdefabcd #FlapGift \`\`\` \*\*Invalid Examples:\*\* \`\`\` send fees from 0x... to 0x... // Wrong keyword gift fees to 0x... from 0x... // Wrong order gift the fee from 0x123 to 0xabc // Invalid address format \`\`\` ### Contract: canManageVault() \*\*Parameters:\*\* \* \`taxToken\` (address): Tax token contract address \* \`xHandle\` (string): X handle (MUST be lowercase) \* \`tweetId\` (uint128): Tweet ID \*\*Returns:\*\* \* \`canManage\` (bool): Whether vault can be managed \* \`errorMessage\` (string): Error description if false \*\*Possible Error Messages:\*\* | Error Message | Meaning | | ----------------------------------------- | ----------------------------- | | "FlapXVault not found for this tax token" | No vault exists | | "xHandle does not match" | Wrong X handle | | "Vault is in snowball mode" | Vault cannot be managed | | "The tweet is outdated" | Tweet already used (success!) | | "" (empty string) | Can manage (success) | ### Backend API: POST /submit \*\*Endpoint:\*\* \`{relayerEndpoint}/submit\`\\ \*\*Rate Limit:\*\* 1 request per minute per IP \*\*Request:\*\* \`\`\`json { "tax\_token": "0x1234567890abcdef...", "tweet\_id": "1234567890123456789" } \`\`\` \*\*Success Response (200):\*\* \`\`\`json { "message": "Proof submitted successfully", "x\_proof": { "target\_address": "0xabcdef...", "tax\_token": "0x123456...", "x\_handle": "username", "x\_id": "123456789", "tweet\_id": "1234567890123456789" }, "signature": "0x1234abcd..." } \`\`\` \*\*Error Response (4xx/5xx):\*\* \`\`\`json { "error": "Error description" } \`\`\` ## Error handling ### Common errors | Error | Cause | Solution | | --------------------------- | ----------------- | ----------------------- | | \*\*Tweet Validation\*\* | | | | "Tweet is not valid" | Deleted/suspended | Use valid, active tweet | | "Tweet has no text" | Media-only tweet | Ensure text content | | "Author mismatch" | Wrong X handle | Verify correct user | | "Format mismatch" | Incorrect format | Use exact format | | "Token mismatch" | Wrong address | Verify addresses match | | \*\*Preflight Check\*\* | | | | "Vault not found" | No vault exists | Check token has vault | | "xHandle does not match" | Wrong vault owner | Use correct X handle | | "Vault is in snowball mode" | Vault locked | Cannot manage anymore | | "Tweet is outdated" | Already used | Use new tweet ID | | \*\*Backend\*\* | | | | Rate limit error | Too many requests | Wait 60 seconds | | Network timeout | Backend slow | Retry later | ## Manual submission fallback If the backend relayer times out after 60 seconds, you can submit the proof directly on-chain using the proof data returned from the backend: \`\`\`typescript async function manualSubmit( provider: JsonRpcProvider, factoryAddress: string, taxToken: string, proofData: SubmitResponse, signer: any // Wallet signer ): Promise { const MANUAL\_SUBMIT\_ABI = \[\ {\ "inputs": \[\ {"name": "taxToken", "type": "address"},\ {\ "name": "proof",\ "type": "tuple",\ "components": \[\ {"name": "targetAddress", "type": "address"},\ {"name": "taxToken", "type": "address"},\ {"name": "xHandle", "type": "string"},\ {"name": "XId", "type": "uint256"},\ {"name": "tweetId", "type": "uint128"}\ \]\ },\ {"name": "signature", "type": "bytes"}\ \],\ "name": "manageByProof",\ "outputs": \[\],\ "stateMutability": "nonpayable",\ "type": "function"\ }\ \]; const contract = new Contract(factoryAddress, MANUAL\_SUBMIT\_ABI, signer); const tx = await contract.manageByProof( taxToken, { targetAddress: proofData.x\_proof.target\_address, taxToken: proofData.x\_proof.tax\_token, xHandle: proofData.x\_proof.x\_handle, XId: BigInt(proofData.x\_proof.x\_id), tweetId: BigInt(proofData.x\_proof.tweet\_id) }, proofData.signature ); console.log("Manual submission tx:", tx.hash); await tx.wait(); console.log("✅ Manual submission confirmed"); } \`\`\` ## Summary checklist 1. ✅ Fetch tweet (you must do this yourself) 2. ✅ Validate tweet content (Step 1) \* Check tweet exists and is valid \* Verify author matches X handle \* Parse tweet text using regex \* Verify tax token matches 3. ✅ ⚠️ MANDATORY: Call canManageVault() (Step 2) \* xHandle MUST be lowercase \* If false, STOP - do NOT call backend 4. ✅ Submit to backend (Step 3) \* Only if preflight passed \* Rate limited: 1 request/minute 5. ✅ Poll canManageVault() (Step 4) \* Every 15s for max 60s \* Success = error: "The tweet is outdated" 6. ✅ Error handling \* Implement timeouts \* Have manual submission fallback {% hint style="danger" %} \*\*Key Points:\*\* \* NEVER skip preflight check - prevents IP ban \* Backend is rate-limited (1/min) - no exceptions \* xHandle must be lowercase for contract calls \* Polling confirms success - relayer is async \* Consider showing manual submission option to users after 30 seconds of polling, while continuing to poll up to 60 seconds in the background {% endhint %} ## Gift Vault interface \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @title IFlapXVault /// @notice Interface for the Gift Vault (FlapXVault) implementation. /// @dev Derived from the on-chain FlapXVault contract. interface IFlapXVault { /// @notice The vault states. enum State { ACCUMULATING, STREAMING, FALLBACK\_SNOWBALL } /// @notice The type of balance update for snowball events. enum BalanceUpdateType { ACCUMULATION, SNOWBALL } /// @notice Aggregate vault statistics. /// @param totalBNBSpent Total BNB spent on buyback. /// @param totalTokenBurn Total tax tokens burned. struct VaultStats { uint128 totalBNBSpent; uint128 totalTokenBurn; } /// @notice Historical proof record. /// @param XId The X (Twitter) account id. /// @param tweetId The tweet id used as proof. /// @param targetAddress The streaming target address. struct ProofRecord { uint128 XId; uint128 tweetId; address targetAddress; } /// @notice XProof structure for EIP-712 signature verification. /// @param targetAddress The desired streaming target. /// @param taxToken The tax token tied to this vault. /// @param xHandle The fee manager's X handle. /// @param XId The X (Twitter) account id. /// @param tweetId The tweet id used as proof. struct XProof { address targetAddress; address taxToken; string xHandle; uint128 XId; uint128 tweetId; } /// @notice Emitted when the vault state changes. /// @param token The tax token address. /// @param newState The new state (0: ACCUMULATING, 1: STREAMING, 2: FALLBACK\_SNOWBALL). event FlapTaxVaultStateChanged(address token, uint8 newState); /// @notice Emitted when the streaming target is updated. /// @param token The tax token address. /// @param newTarget The new streaming target address. event FlapTaxVaultStreamingTargetUpdated(address token, address newTarget); /// @notice Emitted when snowball balance is updated. /// @param token The tax token address. /// @param vault The vault address. /// @param newBalance The new BNB balance. /// @param updateType The type of update (ACCUMULATION or SNOWBALL). event FlapSnowballBalanceUpdated(address token, address vault, uint256 newBalance, BalanceUpdateType updateType); /// @notice Emitted when forwarding to streaming target fails. /// @param token The tax token address. /// @param target The target address that rejected the transfer. /// @param amount The amount that failed to transfer. event FlapStreamingForwardFailed(address token, address target, uint256 amount); /// @notice Error when trying to use an outdated proof. /// @param providedTweetId The tweet id provided in the proof. /// @param lastTweetId The latest stored tweet id. error OutdatedProof(uint128 providedTweetId, uint128 lastTweetId); /// @notice Error when the vault is in the wrong state for the operation. /// @param currentState The current vault state. error InvalidState(State currentState); /// @notice Error when xHandle is empty. error EmptyXHandle(); /// @notice Error when proof verification fails. error InvalidProof(); /// @notice Error when proof tax token does not match this vault. error MismatchedTaxToken(); /// @notice Error when proof xHandle does not match this vault. error MismatchedXHandle(); /// @notice Error when attempting to revoke the guardian role. error CannotRevokeGuardianRole(); /// @notice Role allowed to execute snowball buybacks. function SNOWBALL\_ROLE() external view returns (bytes32); /// @notice Dead address used for burns. function DEAD\_ADDRESS() external view returns (address); /// @notice Initialize the vault after cloning. /// @param \_taxToken The tax token address. /// @param \_quoteToken The quote token address. /// @param \_xHandle The fee manager's X handle. /// @param \_timeoutPeriod Time before fallback state (seconds). function initialize(address \_taxToken, address \_quoteToken, string calldata \_xHandle, uint256 \_timeoutPeriod) external; /// @notice Current computed vault state. /// @return The current state (computed from storage and time). function state() external view returns (State); /// @notice Transition state if timeout rules are met. function transitState() external; /// @notice Manage the vault using a signed X proof. /// @param proof The proof payload. /// @param signature The EIP-712 signature. function manageByProof(XProof calldata proof, bytes calldata signature) external; /// @notice Buy back and burn tax tokens using accumulated BNB. /// @param quoteAmt The amount of BNB to use for buyback. function snowball(uint256 quoteAmt) external; /// @notice Return historical proofs with pagination. /// @param offset Starting index for pagination (0 = most recent). /// @param limit Maximum number of records to return. /// @return records Array of proof records in descending order (newest first). /// @return total Total number of historical proofs. function getHistoricalProofs(uint256 offset, uint256 limit) external view returns (ProofRecord\[\] memory records, uint256 total); /// @notice Returns a human-readable description of the vault. /// @return A string describing the vault state and key metrics. function description() external view returns (string memory); /// @notice Override of AccessControl's revokeRole to protect guardian. /// @param role The role to revoke. /// @param account The account to revoke the role from. function revokeRole(bytes32 role, address account) external; /// @notice Current aggregate stats. function stats() external view returns (uint128 totalBNBSpent, uint128 totalTokenBurn); /// @notice The tax token associated with this vault. function taxToken() external view returns (address); /// @notice The quote token used for snowball swaps. function quoteToken() external view returns (address); /// @notice The fee manager's X handle. function xHandle() external view returns (string memory); /// @notice Timeout period before fallback. function timeoutPeriod() external view returns (uint256); /// @notice Creation timestamp for this vault. function createdAt() external view returns (uint256); /// @notice Vault factory address. function factory() external view returns (address); /// @notice Current streaming target (only in STREAMING state). function streamingTarget() external view returns (address); /// @notice Total streamed amount for a beneficiary. /// @param beneficiary The beneficiary address. function streamedAmount(address beneficiary) external view returns (uint256); /// @notice Latest recorded tweet id used for monotonicity checks. function lastTweetId() external view returns (uint128); } \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification.md). # Vault & VaultFactory Specification ## Table of Contents \* \[Quick start\](#quick-start) \* \[Overview\](#overview) \* \[The Vault Specification\](#the-vault-specification) \* \[VaultBase (V1)\](#vaultbase-v1) \* \[VaultBaseV2\](#vaultbasev2) \* \[Example vault — BlackHoleVault\](#example-vault--blackholevault) \* \[The Flap Guardian\](#the-flap-guardian) \* \[Adapter for legacy vaults\](#adapter-for-legacy-vaults) \* \[VaultFactory Specification\](#vaultfactory-specification) \* \[IVaultFactory (V1)\](#ivaultfactory-v1) \* \[VaultFactoryBaseV2\](#vaultfactorybasev2) \* \[V2.1: Validation hook — \`onBeforeNewTokenV6WithVault\`\](#v21-validation-hook--onbeforenewtokenv6withvault) \* \[V2.2: Generic validation hook — \`onBeforeLaunch(bytes)\`\](#v22-generic-validation-hook--onbeforelaunchbytes) \* \[V2.2: Validation payload — \`LaunchValidationDataV1\`\](#v22-validation-payload--launchvalidationdatav1) \* \[V2.2: Spec version discovery — \`factorySpecVersion()\`\](#v22-spec-version-discovery--factoryspecversion) \* \[V2.1+: Policy discovery — \`tokenCreationPolicies\`\](#v21-policy-discovery--tokencreationpolicies) \* \[Recommended commission fee structure\](#recommended-commission-fee-structure) \* \[UI Schema Reference (IVaultSchemasV1)\](#ui-schema-reference-ivaultschemasv1) \* \[FieldDescriptor\](#fielddescriptor) \* \[VaultDataSchema\](#vaultdataschema) \* \[FactoryPolicy\](#factorypolicy) \* \[VaultUISchema & supporting types\](#vaultuischema--supporting-types) \* \[Get your vault verified\](#get-your-vault-verified) \* \[FAQ\](#faq) ## Quick start 1. \*\*Follow the example\*\* — Work through the \[FlapVaultExample repository\](https://github.com/flap-sh/FlapVaultExample) to see a complete, working vault and vault factory implementation. It is the fastest way to understand the patterns you need to follow. 2. \*\*Verify your implementation\*\* — Once you have written your vault, use the \[FlapVaultSpecChecker\](https://github.com/flap-sh/FlapVaultSpecChecker) AI toolkit to automatically check that your implementation correctly follows this specification before deploying. ## Overview Flap's vault system allows anyone to build smart contracts for managing and distributing BNB revenue generated from tax tokens. Vaults can be customized to fit various use cases — splitting revenue among multiple recipients, routing funds based on social proof, funding a game treasury, or anything else you can imagine. {% hint style="info" %} \*\*Permissionless vault creation\*\* — You can now create and deploy your own vaults and vault factories \*\*without any permission or registration\*\*. Vault factories no longer need to be registered in the VaultPortal before they can be used to launch tokens. Users can use any vault they want when launching tokens. {% endhint %} {% hint style="warning" %} For a complete working example of a vault and vault factory implementation, check out the \[FlapVaultExample repository\](https://github.com/flap-sh/FlapVaultExample). {% endhint %} There are two generations of the vault specification, with a point release of the factory spec: | Version | Vault base contract | Factory base contract | Key addition | | ------- | ----------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------- | | V1 | \`VaultBase\` | \`IVaultFactory\` | Core spec — \`description()\`, Guardian mandate | | V2 | \`VaultBaseV2\` (extends \`VaultBase\`) | \`VaultFactoryBaseV2\` (implements \`IVaultFactory\`) | On-chain UI schema for automatic UI generation | | V2.1 | \*(no change)\* | \`VaultFactoryBaseV2\` (updated) | Legacy V6 validation hook + machine-readable policy discovery | | V2.2 | \*(no change)\* | \`VaultFactoryBaseV2\` (current) | Wrapper-agnostic validation via \`onBeforeLaunch(bytes)\` and normalized launch payloads | V2, V2.1, and V2.2 are \*\*fully backwards-compatible\*\* with V1. Existing vaults that extend \`VaultBase\` are unaffected. New vault implementations should extend \`VaultBaseV2\` and new factory implementations should extend \`VaultFactoryBaseV2\` to gain UI schema support. New factory implementations should generally target \*\*V2.2\*\*, which is the current default behavior of \`VaultFactoryBaseV2\` in the FlapTaxVaults repo. ## The Vault Specification ### VaultBase (V1) To be compatible with the VaultPortal system, your vault smart contract must inherit the \`VaultBase\` contract: \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @title VaultBase /// @notice Abstract base contract for all vault implementations /// @author The Flap Team abstract contract VaultBase { /// @notice Error thrown when the current chain is not supported error UnsupportedChain(uint256 chainId); /// @notice Get the Portal address for the current chain /// @dev Currently supports BNB Chain (chain ID 56) and BNB Testnet (chain ID 97) /// @return portal The Portal contract address function \_getPortal() internal view returns (address portal) { uint256 chainId = block.chainid; if (chainId == 56) { return 0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0; } else if (chainId == 97) { return 0x5bEacaF7ABCbB3aB280e80D007FD31fcE26510e9; } revert UnsupportedChain(chainId); } /// @notice Get the Guardian address for the current chain /// @dev Currently supports BNB Chain (chain ID 56) and BNB Testnet (chain ID 97) /// @return guardian The Guardian contract address function \_getGuardian() internal view returns (address guardian) { uint256 chainId = block.chainid; if (chainId == 56) { return 0x9e27098dcD8844bcc6287a557E0b4D09C86B8a4b; } else if (chainId == 97) { return 0x76Fa8C526f8Bc27ba6958B76DeEf92a0dbE46950; } revert UnsupportedChain(chainId); } /// @notice Returns a description of the vault /// @dev Must be overridden to provide a dynamic description based on the vault's current state /// @return A string describing the vault's current state and configuration function description() public view virtual returns (string memory); } \`\`\` \*\*Implementation requirements:\*\* 1. \*\*Implement \`description()\`\*\* — Return a dynamic string that describes the vault's current state. The description should change based on the vault's state (e.g. balance, streaming status, etc.). 2. \*\*Implement \`receive()\`\*\* — Accept BNB from the tax token and process the revenue according to your vault's logic. 3. \*\*Guardian mandate\*\* — If you have any permissioned functions that should be triggered by an external address, and it is not suitable to make them public (e.g. buyback which may be sandwich attacked), you \*\*must\*\* also give the Guardian address the permissions alongside other allowed addresses as a backup. See the \[Flap Guardian\](#the-flap-guardian) section for details. ### VaultBaseV2 \`VaultBaseV2\` inherits from \`VaultBase\` and adds a single new abstract method: \`vaultUISchema()\`. This allows the UI to automatically discover and render the vault's user-facing methods — without needing custom code for each vault type. \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {VaultBase} from "./VaultBase.sol"; import {VaultUISchema} from "./IVaultSchemasV1.sol"; /// @title VaultBaseV2 /// @author The Flap Team abstract contract VaultBaseV2 is VaultBase { /// @notice Returns the UI schema describing which methods the UI should /// render for this vault. /// @return schema The complete UI schema for this vault. function vaultUISchema() public pure virtual returns (VaultUISchema memory schema); } \`\`\` \*\*What changes from V1:\*\* \* All V1 obligations still apply (\`description()\`, \`receive()\`, Guardian mandate). \* You must additionally override \`vaultUISchema()\` to return a \`VaultUISchema\` describing every user-facing method your vault exposes. See the \[UI Schema Reference\](#ui-schema-reference-ivaultschemasv1) section for the full struct definitions. \*\*Why this matters:\*\* Without a self-describing mechanism, every new vault type would require a custom UI to be built and deployed before users could interact with it. \`vaultUISchema()\` solves this problem by enabling \*\*automatic UI generation for any future vault type\*\*. {% hint style="info" %} Existing vault types (FlapXVault, SplitVault, SnowBallVault, BlackHoleVault) already have purpose-built UIs. \`VaultBaseV2\` is designed for \*\*future\*\* vault implementations — the UI can automatically generate a full interaction page for any vault that implements this interface. {% endhint %} \*\*Example — Hypothetical DonationVault:\*\* \`\`\`solidity function vaultUISchema() public pure override returns (VaultUISchema memory schema) { schema.vaultType = "DonationVault"; schema.description = "Collects BNB donations and lets a designated charity withdraw."; schema.methods = new VaultMethodSchema\[\](3); // View: totalDonated() — no inputs, one uint256 output schema.methods\[0\].name = "totalDonated"; schema.methods\[0\].description = "Returns the total BNB donated so far."; schema.methods\[0\].outputs = new FieldDescriptor\[\](1); schema.methods\[0\].outputs\[0\] = FieldDescriptor("total", "uint256", "Total BNB donated", 18); schema.methods\[0\].approvals = new ApproveAction\[\](0); // Write: donate() — msg.value input // fieldType "msg.value": the UI sets tx.value on the transaction instead of // ABI-encoding the amount as a calldata parameter. Only valid on write methods. schema.methods\[1\].name = "donate"; schema.methods\[1\].description = "Send BNB as a donation."; schema.methods\[1\].inputs = new FieldDescriptor\[\](1); schema.methods\[1\].inputs\[0\] = FieldDescriptor("amount", "msg.value", "BNB to donate", 18); schema.methods\[1\].outputs = new FieldDescriptor\[\](0); schema.methods\[1\].approvals = new ApproveAction\[\](0); schema.methods\[1\].isWriteMethod = true; // Write: withdraw() — no inputs schema.methods\[2\].name = "withdraw"; schema.methods\[2\].description = "Withdraws accumulated donations to the charity address."; schema.methods\[2\].inputs = new FieldDescriptor\[\](0); schema.methods\[2\].outputs = new FieldDescriptor\[\](0); schema.methods\[2\].approvals = new ApproveAction\[\](0); schema.methods\[2\].isWriteMethod = true; } \`\`\` \*\*Example — StakingVault with approve actions:\*\* \`\`\`solidity function vaultUISchema() public pure override returns (VaultUISchema memory schema) { schema.vaultType = "StakingVault"; schema.description = "Stakes tax tokens or LP tokens to earn rewards."; schema.methods = new VaultMethodSchema\[\](2); // View: stakedBalance(address) schema.methods\[0\].name = "stakedBalance"; schema.methods\[0\].description = "Returns the staked balance for a given user."; schema.methods\[0\].inputs = new FieldDescriptor\[\](1); schema.methods\[0\].inputs\[0\] = FieldDescriptor("user", "address", "The user address to query", 0); schema.methods\[0\].outputs = new FieldDescriptor\[\](1); schema.methods\[0\].outputs\[0\] = FieldDescriptor("balance", "uint256", "Staked token balance", 18); // Write: deposit(uint256) — requires ERC-20 approval of the tax token schema.methods\[1\].name = "deposit"; schema.methods\[1\].description = "Stake tax tokens into the vault to earn rewards."; schema.methods\[1\].inputs = new FieldDescriptor\[\](1); schema.methods\[1\].inputs\[0\] = FieldDescriptor("amount", "uint256", "Amount of tax tokens to stake", 18); schema.methods\[1\].approvals = new ApproveAction\[\](1); schema.methods\[1\].approvals\[0\] = ApproveAction("taxToken", "amount"); schema.methods\[1\].isWriteMethod = true; } \`\`\` \*\*Example — NoteVault (string input, paginated array output):\*\* \`\`\`solidity function vaultUISchema() public pure override returns (VaultUISchema memory schema) { schema.vaultType = "NoteVault"; schema.description = "A public on-chain diary. Anyone can submit a note with an optional BNB tip. " "All entries are stored on-chain and readable page-by-page."; schema.methods = new VaultMethodSchema\[\](3); // View: noteCount() — no inputs, plain uint256 output (raw count, decimals = 0) schema.methods\[0\].name = "noteCount"; schema.methods\[0\].description = "Total number of notes submitted."; schema.methods\[0\].inputs = new FieldDescriptor\[\](0); schema.methods\[0\].outputs = new FieldDescriptor\[\](1); schema.methods\[0\].outputs\[0\] = FieldDescriptor("count", "uint256", "Total notes", 0); schema.methods\[0\].approvals = new ApproveAction\[\](0); // View: getNotes(uint256 offset, uint256 limit) — paginated, returns Note\[\] // isOutputArray = true: outputs describe the fields of \*each tuple\* in the returned array, // not a single return value. The UI renders a table with one row per Note. schema.methods\[1\].name = "getNotes"; schema.methods\[1\].description = "Fetch a page of notes. Use offset + limit to paginate."; schema.methods\[1\].inputs = new FieldDescriptor\[\](2); schema.methods\[1\].inputs\[0\] = FieldDescriptor("offset", "uint256", "Start index (0-based)", 0); schema.methods\[1\].inputs\[1\] = FieldDescriptor("limit", "uint256", "Max items to return", 0); schema.methods\[1\].outputs = new FieldDescriptor\[\](3); schema.methods\[1\].outputs\[0\] = FieldDescriptor("author", "address", "Author address", 0); schema.methods\[1\].outputs\[1\] = FieldDescriptor("createdAt", "time", "Submitted at", 0); schema.methods\[1\].outputs\[2\] = FieldDescriptor("content", "string", "Note content", 0); schema.methods\[1\].isOutputArray = true; // ← output is Note\[\], not a single tuple schema.methods\[1\].approvals = new ApproveAction\[\](0); // Write: submitNote(string content) — string input + optional msg.value tip schema.methods\[2\].name = "submitNote"; schema.methods\[2\].description = "Submit a note. Attach a BNB tip (optional, set 0 to skip)."; schema.methods\[2\].inputs = new FieldDescriptor\[\](2); schema.methods\[2\].inputs\[0\] = FieldDescriptor("content", "string", "Note content", 0); schema.methods\[2\].inputs\[1\] = FieldDescriptor("tip", "msg.value", "Optional BNB tip", 18); schema.methods\[2\].outputs = new FieldDescriptor\[\](0); schema.methods\[2\].approvals = new ApproveAction\[\](0); schema.methods\[2\].isWriteMethod = true; } \`\`\` ### The Flap Guardian The Flap Guardian is a privileged address that can always call permissioned functions in vault contracts that implement the Vault Specification. The Guardian serves as a backup mechanism to ensure that critical functions can be executed even if the primary authorized addresses are unable to do so. At this moment, the Guardian is an empty but upgradeable contract managed by Flap: \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @title FlapGuardian /// @notice Guardian contract for vault emergency management and CTO cases /// @dev Currently does nothing, but this contract is upgradeable. contract FlapGuardian { function version() external pure returns (string memory) { return "0.0.1"; } } \`\`\` Ideally, we don't need to implement any functionality in the Guardian contract. It just serves as a trusted address that can step in when necessary to protect users' funds. {% hint style="warning" %} If your vault has permissioned functions, you \*\*must\*\* grant the Guardian the same permissions. The Guardian's access \*\*must not\*\* be revocable by any other account — only the Guardian itself may renounce its own access. When using OpenZeppelin's \`AccessControl\`, override \`revokeRole()\`: \`\`\`solidity function revokeRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) { address guardian = \_getGuardian(); if (account == guardian) { revert CannotRevokeGuardianRole(); } super.revokeRole(role, account); } \`\`\` {% endhint %} ### Adapter for legacy vaults If you have built a vault to receive tax token revenue before the Vault Specification was introduced, you can still make your vault compatible with the VaultPortal system by creating an adapter contract that inherits from \`VaultBase\` and wraps around your existing vault. The adapter will implement the \`description()\` method and forward calls to your legacy vault as needed. This way, you can leverage VaultPortal features without modifying your original vault contract. Please reach out to our team for assistance in creating an adapter for your legacy vault. ## VaultFactory Specification A vault factory deploys vault instances for new tax tokens. When a user launches a token through the \`VaultPortal\`, the portal calls your factory's \`newVault()\` method to create the vault. {% hint style="info" %} \*\*No registration required\*\* — In V2, any contract that implements \`VaultFactoryBaseV2\` can be passed to the VaultPortal to launch tokens. You do not need to register your factory on-chain. {% endhint %} ### IVaultFactory (V1) The base interface that all vault factories must implement: \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @title IVaultFactory /// @notice Interface that all vault factory contracts must implement interface IVaultFactory { error OnlyVaultPortal(); error ZeroAddress(); /// @notice Creates a new vault instance for a tax token /// @dev IMPORTANT: The taxToken does not exist yet when this method is called. /// The VaultPortal predicts the token address and passes it here. /// The actual token will be created AFTER the vault is created. /// @param taxToken The predicted address of the tax token (not yet deployed) /// @param quoteToken The quote token address (e.g., address(0) for native BNB) /// @param creator The original msg.sender to VaultPortal who initiated token creation /// @param vaultData Custom encoded data specific to this vault type /// @return vault The address of the newly created vault function newVault(address taxToken, address quoteToken, address creator, bytes calldata vaultData) external returns (address vault); /// @notice Checks if a quote token is supported by this vault factory /// @param quoteToken The quote token address to check /// @return supported True if the quote token is supported, false otherwise function isQuoteTokenSupported(address quoteToken) external view returns (bool supported); } \`\`\` \*\*Key points:\*\* \* Currently, only BNB (\`quoteToken == address(0)\`) is supported as the quote token, but future support for other quote tokens may be added. \* The \`newVault\` method is called by the VaultPortal when a new tax token is being created. The tax token does \*\*not\*\* exist yet when this method is called — the VaultPortal predicts the token address and passes it. The actual token is created \*\*after\*\* the vault. ### VaultFactoryBaseV2 \`VaultFactoryBaseV2\` implements \`IVaultFactory\` and adds a new abstract method: \`vaultDataSchema()\`. This allows the UI to discover what \`vaultData\` encoding the factory expects, and render the launch form accordingly. \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {IVaultFactory} from "./IVaultFactory.sol"; import {VaultDataSchema} from "./IVaultSchemasV1.sol"; /// @title VaultFactoryBaseV2 /// @author The Flap Team abstract contract VaultFactoryBaseV2 is IVaultFactory { error UnsupportedChain(uint256 chainId); /// @notice Returns the schema describing the \`vaultData\` bytes expected /// by this factory's \`newVault()\` method. /// @return schema The vault data schema for this factory. function vaultDataSchema() public pure virtual returns (VaultDataSchema memory schema); /// @notice Get the VaultPortal address for the current chain. function \_getVaultPortal() internal view returns (address vaultPortal) { uint256 chainId = block.chainid; if (chainId == 56) { return 0x90497450f2a706f1951b5bdda52B4E5d16f34C06; } else if (chainId == 97) { return 0x027e3704fC5C16522e9393d04C60A3ac5c0d775f; } revert UnsupportedChain(chainId); } /// @notice Get the Guardian address for the current chain. function \_getGuardian() internal view returns (address guardian) { uint256 chainId = block.chainid; if (chainId == 56) { return 0x9e27098dcD8844bcc6287a557E0b4D09C86B8a4b; } else if (chainId == 97) { return 0x76Fa8C526f8Bc27ba6958B76DeEf92a0dbE46950; } revert UnsupportedChain(chainId); } } \`\`\` \*\*What changes from V1:\*\* \* All V1 obligations still apply (\`newVault()\`, \`isQuoteTokenSupported()\`). \* You must additionally override \`vaultDataSchema()\` to return a \`VaultDataSchema\` describing the fields your factory expects in the \`vaultData\` parameter. \* The factory now provides \`\_getVaultPortal()\` and \`\_getGuardian()\` helpers. \*\*Example — Single tuple schema:\*\* \`\`\`solidity function vaultDataSchema() public pure override returns (VaultDataSchema memory schema) { schema.description = "Creates a staking vault. Specify a reward rate and lock duration."; schema.fields = new FieldDescriptor\[\](2); schema.fields\[0\] = FieldDescriptor("rewardRateBps", "uint16", "Annual reward rate in basis points", 0); schema.fields\[1\] = FieldDescriptor("lockDuration", "uint256", "Lock duration in seconds", 0); schema.isArray = false; } \`\`\` \*\*Example — Array of tuples schema:\*\* \`\`\`solidity function vaultDataSchema() public pure override returns (VaultDataSchema memory schema) { schema.description = "Creates a vault that distributes received BNB " "among a dynamic set of payees by basis-point shares."; schema.fields = new FieldDescriptor\[\](2); schema.fields\[0\] = FieldDescriptor("payee", "address", "Payee wallet address", 0); schema.fields\[1\] = FieldDescriptor("bps", "uint16", "Basis points share (10000 = 100%)", 0); schema.isArray = true; // vaultData = abi.encode((address,uint16)\[\]) } \`\`\` \*\*Example — Factory that ignores vaultData:\*\* \`\`\`solidity function vaultDataSchema() public pure override returns (VaultDataSchema memory schema) { schema.description = "Creates a vault with no configurable parameters. " "No user input is required — vaultData is ignored."; schema.fields = new FieldDescriptor\[\](0); schema.isArray = false; } \`\`\` ### V2.1: Validation hook — \`onBeforeNewTokenV6WithVault\` \`VaultFactoryBaseV2\` v2.1 adds a \*\*legacy\*\* validation hook that \`VaultPortal\` may call immediately before creating a V6 tax token. Factories that want to enforce constraints on the legacy \`NewTokenV6WithVaultParams\` wrapper can override this hook. \`\`\`solidity function onBeforeNewTokenV6WithVault(IVaultPortalTypes.NewTokenV6WithVaultParams calldata params) external virtual returns (bool success, string memory reason) { revert LegacyV6ValidationHookNotImplemented(); } \`\`\` \*\*Important current behavior:\*\* in the current repo implementation, the base contract does \*\*not\*\* silently accept by default. It reverts with \`LegacyV6ValidationHookNotImplemented()\` so that new factories do not accidentally opt into the old V6-only validation surface. \*\*When this path is used:\*\* \`VaultPortal\` only uses this hook for factories that are detected as \*\*legacy V6 factories\*\*. New V2.2 factories should use \`onBeforeLaunch(bytes)\` instead. \*\*Return value contract\*\* — when the hook is implemented, \`VaultPortal\` calls it via a low-level call and interprets the result as follows: | Outcome | Meaning | VaultPortal action | | ----------------------------------- | --------------------------------------- | ----------------------------------------- | | Returns \`(true, "")\` | Params are accepted | Continue with token creation | | Returns \`(false, reason)\` | Params rejected | Revert with \`reason\` as the error message | | Reverts with error data | Unexpected error in hook | Propagate the revert | | Selector missing / empty returndata | Factory does not expose the legacy hook | Treat as “no legacy hook present” | \*\*Example — Enforce \`dividendToken\` equals \`quoteToken\`:\*\* \`\`\`solidity function onBeforeNewTokenV6WithVault(IVaultPortalTypes.NewTokenV6WithVaultParams calldata params) external override returns (bool success, string memory reason) { address resolvedQuote = params.quoteToken == address(0) ? WBNB : params.quoteToken; if (params.dividendToken != resolvedQuote) { return (false, "Dividend token must equal the quote token."); } return (true, ""); } \`\`\` {% hint style="info" %} The hook receives the full \`NewTokenV6WithVaultParams\` struct so it can inspect any field — tax rates, quote token, dividend token, salt, etc. Override it only when your factory is intentionally staying on the legacy V6 validation path. {% endhint %} ### V2.2: Generic validation hook — \`onBeforeLaunch(bytes)\` V2.2 introduces a \*\*wrapper-agnostic\*\* validation hook. Instead of coupling factory validation to a specific launch entrypoint such as \`newTokenV6WithVault(...)\`, \`VaultPortal\` now normalizes the launch parameters into a shared payload and calls the generic hook below: \`\`\`solidity function onBeforeLaunch(bytes calldata validationData) external view virtual returns (bool success, string memory reason) { LaunchValidationDataV1 memory data = abi.decode(validationData, (LaunchValidationDataV1)); return \_validateBeforeLaunch(data); } \`\`\` The default implementation is provided by \`VaultFactoryBaseV2\` itself. In most cases, your factory should \*\*override \`\_validateBeforeLaunch(...)\`\*\*, not \`onBeforeLaunch(...)\` directly: \`\`\`solidity function \_validateBeforeLaunch(LaunchValidationDataV1 memory data) internal view virtual returns (bool success, string memory reason) { data = data; return (true, ""); } \`\`\` This makes factory validation independent from whether the user launched via V6, V7, or some future wrapper. \`VaultPortal\` is responsible for translating wrapper-specific params into the shared validation payload. \*\*Return value contract\*\* — \`VaultPortal\` calls \`onBeforeLaunch(bytes)\` via low-level \`staticcall\` and interprets the result as follows: | Outcome | Meaning | VaultPortal action | | ----------------------------------- | ----------------------------------------------------------- | ----------------------------------------------- | | Returns \`(true, "")\` | Params are accepted | Continue with token creation | | Returns \`(false, reason)\` | Params rejected | Revert with \`reason\` | | Reverts with error data | Unexpected validation error | Bubble up the revert | | Selector missing / empty returndata | Factory claimed V2.2 but does not expose the hook correctly | Revert with \`"Factory validation hook missing"\` | {% hint style="info" %} \`onBeforeLaunch(bytes)\` is \*\*read-only\*\*. It is called via \`staticcall\`, so it should only inspect inputs and return pass/fail + reason. Any stateful setup still belongs in \`newVault()\`. {% endhint %} \*\*When to use V2.2 vs V2.1:\*\* \* \*\*New factories should use V2.2\*\* by overriding \`\_validateBeforeLaunch(...)\` and keeping \`factorySpecVersion()\` at the default \`"v2.2"\`. \* \*\*Legacy factories that still depend on \`NewTokenV6WithVaultParams\`\*\* may explicitly stay on V2.1 by overriding \`factorySpecVersion()\` to return \`"v2.1"\` and implementing \`onBeforeNewTokenV6WithVault(...)\`. \*\*Concrete example — \`GiftV4VaultFactoryV2\`:\*\* The current repo's \`src/GiftV4VaultFactoryV2.sol\` uses \`\_validateBeforeLaunch(...)\` to enforce these product rules: \* \`tokenVersion == TOKEN\_V3\_PERMIT\` \* \`quoteToken == address(0)\` (native BNB only) \* \`dividendBps == 0\` \* \`dividendToken == address(0)\` \* \`buyTaxRate == 0\` \* \`sellTaxRate == 0\` \`\`\`solidity function \_validateBeforeLaunch(LaunchValidationDataV1 memory data) internal pure override returns (bool success, string memory reason) { if (data.tokenVersion != IPortalTypes.TokenVersion.TOKEN\_V3\_PERMIT) { return (false, "Gift V4 requires TOKEN\_V3\_PERMIT."); } if (data.quoteToken != address(0)) { return (false, "Gift V4 currently supports native BNB only."); } if (data.dividendBps != 0) { return (false, "Use tracker-only dividend mode. The vault decides when to deposit rewards."); } if (data.dividendToken != address(0)) { return (false, "Dividend claims should be paid in native BNB (wrapped internally by the Dividend contract)."); } if (data.buyTaxRate != 0 || data.sellTaxRate != 0) { return (false, "Gift V4 requires buyTaxRate = sellTaxRate = 0%."); } return (true, ""); } \`\`\` ### V2.2: Validation payload — \`LaunchValidationDataV1\` The generic V2.2 hook receives a normalized payload defined in \`src/interfaces/IVaultFactory.sol\`: \`\`\`solidity struct LaunchValidationDataV1 { IPortalTypes.TokenVersion tokenVersion; address quoteToken; uint16 buyTaxRate; uint16 sellTaxRate; uint16 vaultBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; address dividendToken; uint256 minimumShareBalance; } \`\`\` \*\*Why this exists:\*\* \`newTokenV6WithVault(...)\` and \`newTokenV7WithVault(...)\` do not expose fee data in the same shape. V6 passes separate top-level fields like \`mktBps\`, while V7 passes fee settings as \`feeConfigs\[\]\`. Rather than forcing every factory to understand multiple launch wrapper ABIs, \`VaultPortal\` converts both launch styles into the same semantic payload before validation. \*\*How \`VaultPortal\` builds the payload today:\*\* \* For \*\*V6 launches\*\*, it maps: \* \`mktBps -> vaultBps\` \* \`deflationBps -> deflationBps\` \* \`dividendBps -> dividendBps\` \* \`lpBps -> lpBps\` \* \`dividendToken -> dividendToken\` \* \`minimumShareBalance -> minimumShareBalance\` \* For \*\*V7 launches\*\*, it scans \`feeConfigs\[\]\` and extracts the semantic equivalents for: \* vault / marketing fee \* deflation fee \* dividend fee + dividend token + minimum share balance \* LP fee So from the factory's perspective, \`\_validateBeforeLaunch(...)\` always sees the same shape regardless of which launch wrapper the user chose. ### V2.2: Spec version discovery — \`factorySpecVersion()\` \`VaultFactoryBaseV2\` now exposes a version string that tells \`VaultPortal\` which validation generation the factory belongs to: \`\`\`solidity function factorySpecVersion() public pure virtual returns (string memory) { return "v2.2"; } \`\`\` \*\*Important differences from older V2.1-era docs:\*\* \* In the current repo code, this method is \*\*virtual\*\*. \* The default return value is \*\*\`"v2.2"\`\*\*, not \`"v2.1"\`. \* Factories may override it to stay on an older validation generation, for example returning \`"v2.1"\` for a legacy V6-only factory. \*\*How \`VaultPortal\` interprets it:\*\* \* \`v2.2\`, \`v2.3\`, \`v3.0\`, etc. → use the generic \`onBeforeLaunch(bytes)\` path \* \`v2.1\` and below → treat as legacy / probe \`onBeforeNewTokenV6WithVault(...)\` \* missing / reverting selector → probe for the legacy V6 hook directly The parser uses \*\*major/minor semantics\*\*, so anything \`v2.2+\` counts as part of the normalized pre-launch validation generation. {% hint style="warning" %} Do not return \`"v2.2"\` unless your factory actually supports the generic validation path. If \`VaultPortal\` detects \`v2.2+\`, it will call \`onBeforeLaunch(bytes)\` and expect a valid \`(bool,string)\` response. {% endhint %} ### V2.1+: Policy discovery — \`tokenCreationPolicies\` Policy discovery was introduced in V2.1 and remains part of V2.2. In the current repo, policies are best understood as \*\*machine-readable UI hints\*\* that mirror whichever validation hook the factory actually uses. #### \`tokenCreationPolicies()\` \`\`\`solidity function tokenCreationPolicies() public pure virtual returns (FactoryPolicy\[\] memory policies) { return new FactoryPolicy\[\](0); } \`\`\` Returns an array of \`FactoryPolicy\` structs describing the constraints this factory enforces. The default implementation returns an empty array (no declared policies). \*\*\`FactoryPolicy\` struct:\*\* \`\`\`solidity struct FactoryPolicy { string target; // Field name in normalized launch params (e.g. "dividendToken") string operator; // Comparison operator (see table below) bytes value; // ABI-encoded expected value string description; // Human-readable hint shown in the UI } \`\`\` \*\*Supported operators:\*\* | Operator | Meaning | | --------- | ---------------------------------------------- | | \`"eq"\` | Field must equal \`value\` | | \`"neq"\` | Field must not equal \`value\` | | \`"gt"\` | Field must be strictly greater than \`value\` | | \`"gte"\` | Field must be ≥ \`value\` | | \`"lt"\` | Field must be strictly less than \`value\` | | \`"lte"\` | Field must be ≤ \`value\` | | \`"in"\` | Field must be one of a set (ABI-encoded array) | | \`"notIn"\` | Field must not be any of a set | Unknown operators must be \*\*ignored\*\* by the UI (forward-compatible). \*\*How the UI uses policies:\*\* 1. Call \`factory.tokenCreationPolicies()\` to get the policy array. 2. For each policy, decode \`value\` using the known ABI type for \`target\` from the normalized launch payload semantics. 3. Apply \`operator\` as a client-side validation rule on the corresponding input field and display \`description\` as an inline hint or error message. 4. Policies do \*\*not\*\* disable fields or block submission — they are advisory hints only. The actual enforcement happens inside the factory validation hook: \* \`onBeforeLaunch(bytes)\` for V2.2+ factories \* \`onBeforeNewTokenV6WithVault(...)\` for legacy V2.1 factories {% hint style="warning" %} Policies are \*\*informational only\*\*. Always implement the actual enforcement in the corresponding validation hook. Policies without a corresponding hook have no effect on-chain. {% endhint %} \*\*Concrete example — \`GiftV4VaultFactoryV2\`:\*\* The current \`src/GiftV4VaultFactoryV2.sol\` returns six policies matching its V2.2 validation logic: 1. \`tokenVersion == TOKEN\_V3\_PERMIT\` 2. \`quoteToken == address(0)\` 3. \`dividendBps == 0\` 4. \`dividendToken == address(0)\` 5. \`buyTaxRate == 0\` 6. \`sellTaxRate == 0\` This is a good pattern to follow: keep \`tokenCreationPolicies()\` and your real validation hook in sync so both the UI and on-chain behavior express the same product rules. \*\*Worked examples:\*\* \`\`\`solidity function tokenCreationPolicies() public pure returns (FactoryPolicy\[\] memory policies) { policies = new FactoryPolicy\[\](3); // Example A — dividendToken must equal a specific address (WBNB) policies\[0\] = FactoryPolicy({ target: "dividendToken", operator: "eq", value: abi.encode(address(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c)), description: "Dividend token must equal the quote token (WBNB)." }); // Example B — dividendBps must be at least 100 (1%) policies\[1\] = FactoryPolicy({ target: "dividendBps", operator: "gte", value: abi.encode(uint256(100)), description: "Dividend BPS must be at least 100 (1%) for this vault type." }); // Example C — quoteToken must be one of an allowed set address\[\] memory allowed = new address\[\](2); allowed\[0\] = WBNB; allowed\[1\] = USDT; policies\[2\] = FactoryPolicy({ target: "quoteToken", operator: "in", value: abi.encode(allowed), description: "Quote token must be WBNB or USDT." }); } \`\`\` ### Recommended commission fee structure Vault factories can charge a commission fee from the tax revenue. The fee structure must be clearly described in the vault's \`description()\` method. The recommended fee calculation is based on the tax rate (\`taxRateBps\`) and the received tax revenue (\`msg.value\`): \* If \`taxRate\` ≤ 1% (100 bps), the fee is \*\*6%\*\* of \`msg.value\`. \* If \`taxRate\` > 1%, the fee is \`(msg.value \* 6) / taxRateBps\`. \`\`\`solidity receive() external payable { if (msg.value == 0) return; if (taxRateBps == 0) { try ITaxToken(taxToken).taxRate() returns (uint256 \_taxRate) { if (\_taxRate > 0) { taxRateBps = \_taxRate; } } catch {} } uint256 fee = 0; if (taxRateBps <= 100) { // 6% of msg.value if taxRate <= 1% fee = msg.value \* 600 / 10000; } else { // Examples: // 1% (100 bps) → 6% // 2% (200 bps) → 3% // 3% (300 bps) → 2% // 10% (1000 bps) → 0.6% fee = (msg.value \* 6) / taxRateBps; } // your main logic — accumulate fee or send fee } \`\`\` ## UI Schema Reference (IVaultSchemasV1) The shared struct definitions in \`IVaultSchemasV1.sol\` power the automatic UI generation for both vault factories (token launch forms) and vaults (vault interaction pages). ### FieldDescriptor The unified leaf type shared by both the factory schema and the vault UI schema: \`\`\`solidity /// @notice Describes a single field (parameter, return value, or vault-data component). struct FieldDescriptor { string name; // Machine-readable name (e.g. "recipient", "bps", "amount") string fieldType; // Solidity ABI type string (e.g. "address", "uint256", "string") string description; // Human-readable explanation shown as label/tooltip uint8 decimals; // Decimal precision hint for numeric fields } \`\`\` \*\*Supported \`fieldType\` values:\*\* | \`fieldType\` | UI widget | Notes | | ------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------- | | \`"string"\` | Text input | | | \`"address"\` | Address input | With checksum validation | | \`"uint16"\` | Number input | 0–65535 | | \`"uint256"\` | Big-number input | | | \`"uint128"\` | Number input | | | \`"bool"\` | Checkbox | | | \`"bytes"\` | Hex input | | | \`"bytes32"\` | Hex input | 32 bytes | | \`"time"\` | Date/time picker | Alias for \`uint256\`; value is a Unix timestamp in seconds. Displayed as human-readable time or countdown in outputs | | \`"msg.value"\` | Number input | \*\*Special Type\*\*. Represents \`tx.value\` (wei). \*\*Not\*\* ABI-encoded. Only valid for write method inputs. | \*\*\`decimals\` behaviour:\*\* \* \*\*Factory encoding (input):\*\* If \`decimals > 0\`, the UI multiplies the user's input by $10^{\\text{decimals}}$ before ABI-encoding. If \`0\`, the raw value is used. \* \*\*Vault display (output):\*\* If \`decimals > 0\`, the UI divides the raw on-chain value by $10^{\\text{decimals}}$ for display. If \`0\`, the raw value is displayed. \* For non-numeric fields (\`string\`, \`address\`, \`bytes\`, \`bool\`), \`decimals\` should always be \`0\`. ### VaultDataSchema Returned by \`VaultFactoryBaseV2.vaultDataSchema()\`. Describes the shape of the \`vaultData\` bytes the factory expects: \`\`\`solidity /// @notice Describes the shape of the \`vaultData\` bytes expected by a factory's \`newVault()\`. struct VaultDataSchema { string description; // Free-form explanation shown to the user FieldDescriptor\[\] fields; // Ordered list of tuple components bool isArray; // true = vaultData is abi.encode(tuple\[\]) } \`\`\` \*\*How the UI uses VaultDataSchema:\*\* 1. Call \`factory.vaultDataSchema()\` to get the schema. 2. For each field in \`fields\`, render the appropriate input widget based on \`fieldType\`. 3. If \`isArray == true\`, render an "Add Item" button for dynamic array entries. 4. Encode the user input: if \`isArray\` use \`abi.encode(tuple\[\])\`; otherwise use \`abi.encode(tuple)\`. 5. The encoded bytes are passed as \`vaultData\` in \`NewTaxTokenWithVaultParams\`. If \`fields\` is empty and \`isArray\` is false, the factory ignores \`vaultData\` entirely. ### FactoryPolicy Returned by \`VaultFactoryBaseV2.tokenCreationPolicies()\`. Each struct describes one constraint the factory enforces on the \*\*normalized launch validation payload\*\* used by the current factory spec. \`\`\`solidity /// @notice A single constraint on normalized launch validation data. struct FactoryPolicy { string target; // Field name in normalized launch params string operator; // "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" | "notIn" bytes value; // ABI-encoded expected value (type inferred from target field) string description; // Human-readable hint shown in the UI } \`\`\` For legacy v2.1 factories, these policies normally correspond to checks on \`NewTokenV6WithVaultParams\`. For v2.2+ factories, they normally correspond to checks on \`LaunchValidationDataV1\`. See the \[V2.1+: Policy discovery\](#v21-policy-discovery--tokencreationpolicies) section for the full operator table, encoding rules, and worked examples. ### VaultUISchema & supporting types Returned by \`VaultBaseV2.vaultUISchema()\`. Describes the vault's entire UI surface: \`\`\`solidity /// @notice An ERC-20 approve action the UI must execute before calling a write method. struct ApproveAction { string tokenType; // "taxToken" or "lpToken" string amountFieldName; // Name of the input field whose value is the approve amount } /// @notice Describes a single view or write method the UI should render. struct VaultMethodSchema { string name; // Solidity method name string description; // Human-readable explanation FieldDescriptor\[\] inputs; // Ordered input parameters FieldDescriptor\[\] outputs; // Ordered return values ApproveAction\[\] approvals; // ERC-20 approvals required before a write bool isInputArray; // true = input is tuple\[\] bool isOutputArray; // true = output is tuple\[\] bool isWriteMethod; // true = state-changing, false = view } /// @notice Top-level schema describing the vault's entire UI surface. struct VaultUISchema { string vaultType; // e.g. "FlapXVault", "SplitVault" string description; // Overall explanation of the vault VaultMethodSchema\[\] methods; // All methods the UI should render (ordered) } \`\`\` \*\*How the UI uses VaultUISchema:\*\* 1. Display \`vaultType\` as a badge/header and \`description\` as subtitle. 2. Always call \`vault.description()\` and display the result as a dynamic status banner (polled periodically). 3. For each method: \* \*\*View methods\*\* (\`isWriteMethod == false\`): If no inputs, call immediately and display results. If inputs exist, render input fields with a "Query" button. \* \*\*Write methods\*\* (\`isWriteMethod == true\`): Render a form with inputs. If \`approvals\` is non-empty, execute each \`ApproveAction\` before sending the write transaction. 4. Methods are displayed in the order returned. \*\*ApproveAction workflow:\*\* 1. Resolve the token address from \`tokenType\`: \`"taxToken"\` → call \`vault.taxToken()\`, \`"lpToken"\` → call \`vault.lpToken()\`, unknown → skip (forward-compatible). 2. Read the amount from the user's input field named by \`amountFieldName\` (already scaled by \`decimals\`). 3. Check current allowance: \`token.allowance(user, vault)\`. If sufficient, skip. 4. Send \`token.approve(vault, amount)\` and wait for confirmation. ## Get your vault verified Please reach out to our team if you want to get your vault or vault factory verified by us or our auditing partners. Before reaching out, make sure: \* You have correctly implemented the Vault Specification (the \`description()\` method, the Guardian access to permissioned functions, and \`vaultUISchema()\` / \`vaultDataSchema()\` if using V2). \* Your factory contract is not upgradeable. If you want to upgrade your factory in the future, you must deploy a new factory contract and get it verified again. \* The commission fee structure is clearly described in the vault's \`description()\` method. \* You have passed some basic AI auditing tools — see the FAQ below. {% hint style="warning" %} \*\*We strongly recommend using AI auditing tools before deploying your smart contracts and tokens.\*\* This can help you resolve critical vulnerabilities before submitting for manual review. {% endhint %} ## FAQ ### How to use AI to audit your smart contracts? You can use any AI for auditing your smart contracts. For example, you can use \[Google's AI Studio which is free to use\](https://aistudio.google.com/prompts/new\_chat?model=gemini-2.5-pro). The prompt is as follows: {% code overflow="wrap" %} \`\`\` You are a professional smart contract auditor. Generate an audit report for the following Solidity smart contract code. Identify potential vulnerabilities, code smells, questions, and best practice violations. Provide recommendations for improvements. Return the result in markdown format and put the markdown in a code block. ======================== \`\`\` {% endcode %} This can help you identify common vulnerabilities and issues in your smart contracts. If your comments are clear enough, it will even help identify logical issues. However, AI tools are not perfect and may miss vulnerabilities or provide incorrect suggestions. Always review your smart contracts thoroughly before deploying them on mainnet. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/token-launcher-developers/launch-token-through-portal.md). # Launch token through Portal This page explains how to launch a token directly through \`Portal\`. The current launch entry points are: \* \`newTokenV6\` for \`TOKEN\_V2\_PERMIT\` and all currently supported tax-token versions (\`TOKEN\_TAXED\`, \`TOKEN\_TAXED\_V2\`, \`TOKEN\_TAXED\_V3\`) \* \`newTokenV7\` for \`TOKEN\_V3\_PERMIT\`, the TokenV3-based non-tax flow used for CL-style migration Legacy methods (\`newTokenV2\`, \`newTokenV3\`, \`newTokenV4\`, \`newTokenV5\`) remain available for backward compatibility. {% hint style="info" %} \`TOKEN\_TAXED\_V3\` (\`FlapTaxTokenV3\`) is the recommended token type for all new tax token launches. It supports asymmetric buy/sell tax rates, commission receivers, and bidirectional dynamic liquidation thresholds. Use \`newTokenV6\` with \`tokenVersion = TOKEN\_TAXED\_V3\` for new integrations. {% endhint %} {% hint style="info" %} \`TOKEN\_V3\_PERMIT\` is launched through \`newTokenV7\`, not \`newTokenV6\`. Use it when you want the TokenV3 non-tax path together with CL-style migration. {% endhint %} ## 1) Prepare token metadata Token metadata is stored on IPFS. You can upload your image and metadata JSON through the Flap upload API. Example (TypeScript): \`\`\`typescript async function uploadTokenMeta(cfg: { buy: string | null; creator: string; description: string; sell: string | null; telegram: string | null; twitter: string | null; website: string | null; image\_path: string; }) { const form = new FormData(); const MUTATION\_CREATE = \` mutation Create($file: Upload!, $meta: MetadataInput!) { create(file: $file, meta: $meta) } \`; form.append( "operations", JSON.stringify({ query: MUTATION\_CREATE, variables: { file: null, meta: { website: cfg.website, twitter: cfg.twitter, telegram: cfg.telegram, description: cfg.description, creator: "0x0000000000000000000000000000000000000000", } }, }) ); form.append( "map", JSON.stringify({ "0": \["variables.file"\], }) ); const file = new File(\[fs.readFileSync(cfg.image\_path)\], "image.png", { type: "image/png", }); form.append("0", file); const res = await axios.postForm(FlapConfig.api, form, { headers: { "Content-Type": "multipart/form-data", }, }); if (res.status !== 200) { throw new Error(\`failed to upload the token meta: ${res.statusText}\`); } const cid = res.data.data.create; return cid; } \`\`\` {% hint style="warning" %} You must upload the image to IPFS and pin the metadata using our API: . Our indexer will not be able to fetch your file if it is not pinned on our gateway. Besides, many terminals fetch files through our gateway and we will warm the CDN on upload to improve loading speed. {% endhint %} ## 2) Call \`newTokenV6\` \`newTokenV6\` is the recommended unified entry point for all token types. The token implementation is selected via the \`tokenVersion\` field. \`\`\`solidity /// @notice Create a new token (V6) — unified entry point for all token versions. /// @param params The parameters for the new token (see NewTokenV6Params). /// @return token The address of the created token. function newTokenV6(NewTokenV6Params calldata params) external payable returns (address token); \`\`\` \`NewTokenV6Params\` covers all token types via \`tokenVersion\`: \`\`\`solidity struct NewTokenV6Params { string name; string symbol; string meta; DexThreshType dexThresh; bytes32 salt; MigratorType migratorType; address quoteToken; uint256 quoteAmt; address beneficiary; bytes permitData; bytes32 extensionID; bytes extensionData; DEXId dexId; V3LPFeeProfile lpFeeProfile; // Tax fields (set to 0 for non-tax tokens) uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; uint64 antiFarmerDuration; uint16 mktBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; uint256 minimumShareBalance; // V3-only fields address dividendToken; address commissionReceiver; TokenVersion tokenVersion; } \`\`\` ### Token version dispatch The \`tokenVersion\` field controls which token implementation is created: | \`tokenVersion\` | Token type | Notes | | ----------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------- | | \`TOKEN\_V2\_PERMIT\` | Standard ERC-20 with permit | Set both tax rates to 0, \`commissionReceiver\` must be \`address(0)\` | | \`TOKEN\_TAXED\` | FlapTaxToken V1 | Legacy; symmetric rates only, \`mktBps\` must be 10000, no commission. \*\*Not recommended for new launches.\*\* | | \`TOKEN\_TAXED\_V2\` | FlapTaxTokenV2 | Legacy; symmetric rates only, \`mktBps\` must not be 10000, no commission. \*\*Not recommended for new launches.\*\* | | \`TOKEN\_TAXED\_V3\` | FlapTaxTokenV3 | \*\*Recommended.\*\* Asymmetric rates, commission supported. | {% hint style="warning" %} \`TOKEN\_TAXED\` and \`TOKEN\_TAXED\_V2\` are deprecated. New integrations should always use \`TOKEN\_TAXED\_V3\`. {% endhint %} \`TOKEN\_V3\_PERMIT\` is intentionally not part of \`newTokenV6\`. Use \`newTokenV7\` for that launch path. ### Launching a Tax Token V3 (recommended) Set \`tokenVersion = TOKEN\_TAXED\_V3\` and fill in the tax fields: \`\`\`typescript import { parseEther, zeroAddress } from "viem"; const params = { name: "My Token", symbol: "MTK", meta: "", dexThresh: DexThreshType.TWO\_THIRDS, salt, // must produce 7777 vanity suffix migratorType: MigratorType.V2\_MIGRATOR, quoteToken: zeroAddress, // native gas token quoteAmt: parseEther("0.01"), beneficiary: beneficiaryAddress, permitData: "0x", extensionID: "0x0000000000000000000000000000000000000000000000000000000000000000", extensionData: "0x", dexId: DEXId.DEX0, lpFeeProfile: V3LPFeeProfile.LP\_FEE\_PROFILE\_STANDARD, buyTaxRate: 300, // 3% sellTaxRate: 1000, // 10% taxDuration: 365n \* 24n \* 60n \* 60n, antiFarmerDuration: 60n \* 60n, mktBps: 10000, // all tax to beneficiary (after protocol fee) deflationBps: 0, dividendBps: 0, lpBps: 0, minimumShareBalance: 0n, dividendToken: zeroAddress, // Case 2: quote token (zeroAddress = native) // Case 1: 0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd (MAGIC\_DIVIDEND\_SELF = launching token itself) // Case 3: any ERC-20 supported by SwapRegistry commissionReceiver: zeroAddress, // set to your address to earn commission tokenVersion: TokenVersion.TOKEN\_TAXED\_V3, }; const hash = await walletClient.writeContract({ address: portalAddress, abi: portalAbi, functionName: "newTokenV6", args: \[params\], value: parseEther("0.01"), }); \`\`\` Key notes: \* \`meta\` is the IPFS CID of your metadata JSON. \* \`migratorType\` must be \`V2\_MIGRATOR\` for tax tokens. \* \`quoteToken = address(0)\` uses the native gas token. \* \`salt\` must produce the required vanity suffix: \`7777\` for tax tokens, \`8888\` for standard tokens. \* \`mktBps + deflationBps + dividendBps + lpBps\` must equal 10000. \* \`dividendToken\` supports three modes: \*\*Case 1\*\* — \`0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd\` (\`MAGIC\_DIVIDEND\_SELF\`, distributes the launching token itself); \*\*Case 2\*\* — \`address(0)\` or the quote token address (most common); \*\*Case 3\*\* — any ERC-20 that has a registered swap path in \`SwapRegistry\`. See \[Tax Token V3\](/flap/developers/basic-and-mechanism/flap-tax-token/tax-token-v3.md#dividend-token) for full details. \* Set \`commissionReceiver\` to your address to earn a protocol-calculated commission from every taxable transaction. ### Launching a standard token Set \`tokenVersion = TOKEN\_V2\_PERMIT\` and all tax fields to 0: \`\`\`solidity function launchStandardToken(IPortal portal) external payable returns (address token) { IPortalTypes.NewTokenV6Params memory params = IPortalTypes.NewTokenV6Params({ // ... base fields ... buyTaxRate: 0, sellTaxRate: 0, // ... other tax fields = 0 ... commissionReceiver: address(0), tokenVersion: IPortalTypes.TokenVersion.TOKEN\_V2\_PERMIT }); token = portal.newTokenV6{value: msg.value}(params); } \`\`\` ## 3) Call \`newTokenV7\` \`newTokenV7\` is the TokenV3-based launch entry point. It is intended for CL-style migration and introduces \`feeConfigs\`, which replace the old \`beneficiary + mktBps/deflationBps/dividendBps/lpBps\` split used by \`newTokenV6\` tax launches. \`\`\`solidity /// @notice Create a new token with V4/PCS Infinity migration support /// @param params The V7 token parameters /// @return token The created token address function newTokenV7(NewTokenV7Params calldata params) external payable returns (address token); enum FeeType { NONE, MARKETING\_OR\_VAULT, DIVIDEND, DEFLATION, LP\_BPS } struct FeeConfig { FeeType feeType; uint16 bps; address marketingAddress; address dividendToken; uint256 minimumShareBalance; } struct NewTokenV7Params { string name; string symbol; string meta; DexThreshType dexThresh; bytes32 salt; MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; DEXId dexId; uint16 buyTaxRate; uint16 sellTaxRate; uint64 taxDuration; uint64 antiFarmerDuration; address commissionReceiver; TokenVersion tokenVersion; FeeConfig\[4\] feeConfigs; } \`\`\` ### Current \`newTokenV7\` rollout details The interface includes both \`TOKEN\_V3\_PERMIT\` and \`TOKEN\_TAXED\_V3\` paths, but the current implementation is narrower: \* Public \`newTokenV7\` launches currently support \`TOKEN\_V3\_PERMIT\` only. \* \`TOKEN\_TAXED\_V3\` through \`newTokenV7\` currently reverts with \`NewTokenV7RuleViolation(4)\`. \* For tax-token launches, keep using \`newTokenV6\`. \* The current implementation accepts \`PCS\_INFINITY\_CL\_MIGRATOR\` for the public \`TOKEN\_V3\_PERMIT\` path. \* \`commissionReceiver\` must be \`address(0)\` for \`TOKEN\_V3\_PERMIT\`. \* \`buyTaxRate\` and \`sellTaxRate\` must both be \`0\` for \`TOKEN\_V3\_PERMIT\`. \* \`antiFarmerDuration\` must not exceed \`365 days\`. {% hint style="warning" %} Although \`MigratorType\` includes \`V4\_UNI\_MIGRATOR\`, the current \`TOKEN\_V3\_PERMIT\` implementation behind \`newTokenV7\` validates against \`PCS\_INFINITY\_CL\_MIGRATOR\`. If you send any other migrator type on the public path, the call reverts with \`InvalidMigratorType()\`. {% endhint %} ### How \`feeConfigs\` work \`feeConfigs\` has 4 fixed slots. General validation rules from the launcher are: \* every slot with \`feeType != NONE\` must have \`bps > 0\` \* duplicate fee types are not allowed \* all active slots together must sum to exactly \`10000\` bps \* \`MARKETING\_OR\_VAULT\` requires a non-zero \`marketingAddress\` \* \`DIVIDEND\` uses \`dividendToken\` and \`minimumShareBalance\` For the current public \`TOKEN\_V3\_PERMIT\` rollout, there are extra restrictions: \* only \*\*one\*\* active fee mode is currently supported \* supported mode A: a single \`MARKETING\_OR\_VAULT\` slot with \`bps = 10000\` \* supported mode B: a single \`DIVIDEND\` slot with \`bps = 10000\` and \`dividendToken == quoteToken\` \* \`DEFLATION\` and \`LP\_BPS\` are not enabled yet for \`TOKEN\_V3\_PERMIT\` and revert with \`UnsupportedV7TokenV3PermitFeeType(...)\` There is no standalone \`beneficiary\` field in \`NewTokenV7Params\`. If you use \`MARKETING\_OR\_VAULT\`, the first such slot's \`marketingAddress\` becomes the primary beneficiary used by the launcher. ### Example: zero-tax \`TOKEN\_V3\_PERMIT\` launch \`\`\`typescript const zeroAddress = "0x0000000000000000000000000000000000000000"; const params = { name: "My CL Token", symbol: "MCL", meta: "", dexThresh: DexThreshType.FOUR\_FIFTHS, salt: vanitySalt, // must produce 8888 suffix migratorType: MigratorType.PCS\_INFINITY\_CL\_MIGRATOR, quoteToken: zeroAddress, quoteAmt: parseEther("0.1"), permitData: "0x", extensionID: "0x0000000000000000000000000000000000000000000000000000000000000000", extensionData: "0x", dexId: DEXId.DEX0, buyTaxRate: 0, sellTaxRate: 0, taxDuration: 0, antiFarmerDuration: 0, commissionReceiver: zeroAddress, tokenVersion: TokenVersion.TOKEN\_V3\_PERMIT, feeConfigs: \[\ {\ feeType: FeeType.MARKETING\_OR\_VAULT,\ bps: 10000,\ marketingAddress: beneficiaryAddress,\ dividendToken: zeroAddress,\ minimumShareBalance: 0n,\ },\ {\ feeType: FeeType.NONE,\ bps: 0,\ marketingAddress: zeroAddress,\ dividendToken: zeroAddress,\ minimumShareBalance: 0n,\ },\ {\ feeType: FeeType.NONE,\ bps: 0,\ marketingAddress: zeroAddress,\ dividendToken: zeroAddress,\ minimumShareBalance: 0n,\ },\ {\ feeType: FeeType.NONE,\ bps: 0,\ marketingAddress: zeroAddress,\ dividendToken: zeroAddress,\ minimumShareBalance: 0n,\ },\ \], }; const tx = await portal.newTokenV7(params, { value: params.quoteAmt }); \`\`\` If you use the dividend-only mode instead of the marketing mode, set exactly one \`DIVIDEND\` slot with \`bps = 10000\`, and set \`dividendToken\` to the same address as \`quoteToken\`. ## 4) Find the salt (vanity suffix) \`Portal\` uses CREATE2 for deterministic deployments. The \`salt\` must produce a token address with the required suffix: \* Non-tax token: address ends with \`8888\`. \* Tax token: address ends with \`7777\`. To find a valid \`salt\`, repeatedly hash a random seed and check the predicted address until it matches the suffix. You can predict the address using CREATE2 with the \`Portal\` address and the token implementation address for the token type you’re launching. Example (TypeScript): \`\`\`typescript /// Find a vanity salt by predicting the CREATE2 address. async function findVanityTokenSalt(suffix: string, tokenImpl: Address, portal: Address) { if (suffix.length !== 4) { throw new Error("Suffix must be exactly 4 characters"); } const predictVanityTokenAddress = (salt: Hex): Address => { const bytecode = "0x3d602d80600a3d3981f3363d3d373d3d3d363d73" + tokenImpl.slice(2).toLowerCase() + "5af43d82803e903d91602b57fd5bf3" as Hex; return getContractAddress({ from: portal, salt: toBytes(salt), bytecode, opcode: "CREATE2", }); }; // you don't need to use privatekey, you can use any random seed as long as it is unique for each attempt. Using a privatekey is just a convenient way to get a random 32-byte value. const seed = generatePrivateKey(); let salt = keccak256(toHex(seed)); let iterations = 0; while (!predictVanityTokenAddress(salt).endsWith(suffix)) { salt = keccak256(salt); iterations++; } return { salt, address: predictVanityTokenAddress(salt), iterations, }; } \`\`\` Use the correct \`tokenImpl\` and \`portal\` addresses for your network (see the deployed addresses page in this section). Token implementation selection for launch methods: \* \`TOKEN\_V2\_PERMIT\` (standard token): use the \*\*Standard Token Impl\*\* (\`tokenImplV2\`). \* \`TOKEN\_TAXED\_V3\` (recommended tax token): use the \*\*Tax Token V3 Impl\*\* (\`tokenImplTaxedV3\`). \* \`TOKEN\_TAXED\` (legacy V1, not recommended): use the \*\*Tax Token V1 Impl\*\* (\`tokenImplTaxed\`). \* \`TOKEN\_TAXED\_V2\` (legacy V2, not recommended): use the \*\*Tax Token V2 Impl\*\* (\`tokenImplTaxedV2\`). \* \`TOKEN\_V3\_PERMIT\` via \`newTokenV7\`: use the \*\*Standard Token V3 Impl\*\*. It is still a non-tax launch, so the vanity suffix is \`8888\`. See \[deployed-contract-addresses.md\](/flap/developers/token-launcher-developers/deployed-contract-addresses.md) for the exact implementation addresses. {% hint style="warning" %} For new launches, always use \`tokenImplTaxedV3\` when computing salts for tax tokens. Legacy tax token implementations (\`TOKEN\_TAXED\`, \`TOKEN\_TAXED\_V2\`) are deprecated and should not be used for new integrations. {% endhint %} {% hint style="warning" %} \`newTokenV7\` currently does not expose a public \`TOKEN\_TAXED\_V3\` rollout. For tax-token launches, compute the salt against the \`newTokenV6\` implementation path and use \`newTokenV6\`. {% endhint %} ## 5) Legacy methods \`newTokenV2\`, \`newTokenV3\`, \`newTokenV4\`, and \`newTokenV5\` are still supported for legacy integrations. They follow the same flow but provide fewer features than the current launch paths. New integrations should use \`newTokenV6\` for tax tokens and \`TOKEN\_V2\_PERMIT\`, or \`newTokenV7\` for \`TOKEN\_V3\_PERMIT\`. ## IPortal.sol Full interface reference for developers: \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {IAccessControlUpgradeable} from "@openzeppelin-contracts-upgradeable/access/IAccessControlUpgradeable.sol"; import {IUniswapV3MintCallback} from "uni-v3-core/interfaces/callback/IUniswapV3MintCallback.sol"; import {IPancakeV3MintCallback} from "pancake-v3-core/interfaces/callback/IPancakeV3MintCallback.sol"; /// @dev Magic address value for \`dividendToken\` in NewTokenV6Params. /// When set to this address, the dividend token is resolved to the tax token's own address /// after it is created. This is a sugar for devs who do not want to pre-compute the tax token /// address (it is deterministically computed from the salt). /// Uses a well-known non-conflicting sentinel (0xfEED...fEED) to avoid conflict with: /// - address(0): native gas token dividend (only valid when quoteToken is also native gas) /// - any ERC-20 address: use that token as dividend (including quoteToken) address constant MAGIC\_DIVIDEND\_SELF = address(0xfEEDFEEDfeEDFEedFEEdFEEDFeEdfEEdFeEdFEEd); /// @title Common Types /// @notice This interface defines common types shared across the portal interface IPortalCommonTypes { /// @dev curve Types enum CurveType { CURVE\_LEGACY\_15, // r = 15 CURVE\_4, // r = 4 CURVE\_0\_974, // r = 0.974 CURVE\_0\_5, // r = 0.5 CURVE\_1000, // r = 1000 CURVE\_20000, // r = 20000 CURVE\_2500, // r = 2500 CURVE\_500, // r = 500 CURVE\_2, // r = 2 CURVE\_6, // r = 6 CURVE\_75, // r = 75 CURVE\_4M, // r= 4 M CURVE\_28, // r = 28 CURVE\_21\_25, // r = 21.25 CURVE\_RH\_UNUSED, // r = 27.6, h = 352755468 CURVE\_RH\_28D25\_108002126, // r = 28.25, h = 108002126, k = 31301060059.5 CURVE\_RH\_14981\_108002125, // r = 14981, h = 108002125, k = 16598979834625 CURVE\_RH\_TOSHI\_MORPH\_2ETH, // r = 0.7672, h = 107036751, k = 849318595.3672 - TOSHI/MORPH 2ETH curve CURVE\_RH\_TOSHI, // r = 6140351, h = 107036752, k = 6797594227179952 - TOSHI Curve CURVE\_RH\_BGB, // r = 767.5, h = 107036752, k = 849650707160 - BGB curve CURVE\_RH\_BNB, // r = 6.14, h = 107036752, k = 6797205657.28 - BNB Curve CURVE\_RH\_USD, // r = 3837, h = 107036752, k = 4247700017424 - USD curve CURVE\_RH\_MONAD, // r = 50000, h = 107036752, k = 55351837600000 - MONAD curve CURVE\_RH\_MONAD\_V2, // r = 107400, h = 107036752, k = 118895747164800 - MONAD V2 curve CURVE\_RH\_KGST // r = 380000, h = 107036752, k = 420673965760000 - KGST curve } /// @dev dex threshold types enum DexThreshType { TWO\_THIRDS, // 66.67% supply FOUR\_FIFTHS, // 80% supply HALF, // 50% supply \_95\_PERCENT, // 95% supply \_81\_PERCENT, // 81% supply \_1\_PERCENT // 1% supply => mainly for testing } /// @notice Fee profile for tokens /// @dev Determines the fee structure applied to a token's trades and liquidity operations /// Fees are represented in basis points (bps), where 1% = 100 bps enum FlapFeeProfile { FEE\_GLOBAL\_DEFAULT, // Default fee profile used when no specific profile is set for a token FEE\_FLAPSALE\_V0, // Fee profile for FlapSale V0 FEE\_ZERO // Zero fee profile: no protocol fee charged on trades } // // Custom Errors for Common Types // /// @notice error if the curve type is invalid /// @param curveType The invalid curve type error InvalidCurveType(CurveType curveType); /// @notice error if the dex threshold type is invalid error InvalidDexThresholdType(DexThreshType threshold); } /// @title Types and Structs /// @notice This interface defines the types and structs used in the portal interface IPortalTypes is IPortalCommonTypes { // // public constants // // // Types and Structs // /// @dev Profile types for deployment-specific parameters or behaviors enum Profile { DEFAULT, TOSHI\_MART, X\_LAYER, MORPH, MONAD } /// @dev Token version /// Which token implementation is used enum TokenVersion { TOKEN\_LEGACY\_MINT\_NO\_PERMIT, TOKEN\_LEGACY\_MINT\_NO\_PERMIT\_DUPLICATE, // for historical reasons, both 0 and 1 are the same: TOKEN\_LEGACY\_MINT\_NO\_PERMIT TOKEN\_V2\_PERMIT, // 2 TOKEN\_GOPLUS, // 3 TOKEN\_TAXED, // 4: The original tax token (FlapTaxToken) TOKEN\_TAXED\_V2, // 5: The new advanced tax token (FlapTaxTokenV2) TOKEN\_TAXED\_V3, // 6: The next-generation tax token with asymmetric buy/sell rates (FlapTaxTokenV3) TOKEN\_V3\_PERMIT // 7: Non-tax token using TokenV3 implementation with permit support } /// @dev the quote token, i.e, the token as the reserve enum QuoteTokenType { NATIVE\_GAS\_TOKEN, // The native gas token ERC20\_TOKEN\_WITH\_PERMIT, // The ERC20 token with permit ERC20\_TOKEN\_WITHOUT\_PERMIT // The ERC20 token without permit } /// @notice the status of a token /// The token has 5 statuses: // - Tradable: The token can be traded(buy/sell) // - InDuel: (obsolete) The token is in a battle, it can only be bought but not sold. // - Killed: (obsolete) The token is killed, it can not be traded anymore. Can only be redeemed for another token. // - DEX: The token has been added to the DEX // - Staged: The token is staged but not yet created (address is predetermined) enum TokenStatus { Invalid, // The token does not exist Tradable, InDuel, // obsolete Killed, // obsolete DEX, Staged // The token is staged (address determined, but not yet created) } /// @notice the migrator type /// @dev the migrator type determines how the liquidity is added to the DEX. /// Note: To mitigate the risk of DOS, if a V3 migrator is used but the liquidity cannot /// be added to v3 pools, the migrator will fallback to a V2 migrator. /// A TAX token must use a V2 migrator. enum MigratorType { V3\_MIGRATOR, // Migrate the liquidity to a Uniswap V3 like pool V2\_MIGRATOR, // Migrate the liquidity to a Uniswap V2 like pool V4\_UNI\_MIGRATOR, // Migrate the liquidity to a Uniswap V4 pool (Base, XLayer) PCS\_INFINITY\_CL\_MIGRATOR // Migrate the liquidity to a Pancake Infinity CL Pool (BNB) } /// @notice the V3 LP fee profile /// @dev determines the LP fee tier to use when migrating tokens to Uniswap V3 or Pancake V3 enum V3LPFeeProfile { LP\_FEE\_PROFILE\_STANDARD, // Standard fee tier: 0.25% on PancakeSwap, 0.3% on Uniswap LP\_FEE\_PROFILE\_LOW, // Low fee tier: typically, 0.01% on PancakeSwap, 0.05% on Uniswap LP\_FEE\_PROFILE\_HIGH // High fee tier (1% for exotic pairs) } /// @notice the DEX ID /// @dev determines the DEX we want to migrate to /// On BSC: /// - only DEX0 will be enabled, which is PancakeSwap /// On xLayer: /// - only DEX0 will be enabled, which is PotatoSwap /// On Monad: /// - DEX0 is Uniswap /// - DEX1 is PancakeSwap /// - DEX2 is Monday /// Note that, currently, we only support at most 3 DEXes /// We may add more DEXes in the future if needed enum DEXId { DEX0, DEX1, DEX2 } /// @notice the state of a token (with dex related fields) struct TokenStateV2 { TokenStatus status; // the status of the token uint256 reserve; // the reserve of the token uint256 circulatingSupply; // the circulatingSupply of the token uint256 price; // the price of the token TokenVersion tokenVersion; // the version of the token implementation this token is using uint256 r; // the r of the curve of the token uint256 dexSupplyThresh; // the cirtulating supply threshold for adding the token to the DEX } /// @notice the state of a token (with all V2 fields plus quoteTokenAddress and nativeToQuoteSwapEnabled) struct TokenStateV3 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; } /// @notice the state of a token (with all V3 fields plus extensionID and 'r' curve parameter only) struct TokenStateV4 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; } /// @notice the state of a token (with all V4 fields plus all curve parameters) struct TokenStateV5 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; } /// @notice the state of a token (with all V5 fields plus taxRate, pool, and progress) struct TokenStateV6 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The tax rate in basis points (0 if not a tax token) uint256 taxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; } /// @notice the state of a token (with all V6 fields plus lpFeeProfile) struct TokenStateV7 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The tax rate in basis points (0 if not a tax token) uint256 taxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; /// The V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; /// The Dex Id DEXId dexId; } struct TokenStateV8 { /// The status of the token (see TokenStatus enum) TokenStatus status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum) TokenVersion tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The buy tax rate in basis points (0 if not a tax token) uint256 buyTaxRate; /// The sell tax rate in basis points (0 if not a tax token) uint256 sellTaxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; /// The V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; /// The Dex Id DEXId dexId; } /// @notice A forward-compatible version of TokenStateV8, where enum-typed fields are returned /// as uint8 instead of their Solidity enum types. /// @dev Safe because Solidity revert when decoding an enum value that exceeds the enum's /// declared maximum (e.g. a new TOKEN\_TAXED\_V3 value read by a contract compiled against /// an old TokenVersion enum). By using uint8 for TokenStatus, TokenVersion, /// V3LPFeeProfile, and DEXId, callers are not affected when new variants are added to /// any of those enums in the future. Use getTokenV8 if you are always up-to-date with /// the latest interface; use this method if you need forward/backward compatibility. struct TokenStateV8Safe { /// The status of the token (see TokenStatus enum for interpretation) uint8 status; /// The reserve amount of the quote token held by the bonding curve uint256 reserve; /// The circulating supply of the token uint256 circulatingSupply; /// The current price of the token (in quote token units, 18 decimals) uint256 price; /// The version of the token implementation (see TokenVersion enum for interpretation) uint8 tokenVersion; /// The curve parameter 'r' used for the bonding curve uint256 r; /// The curve parameter 'h' - virtual token reserve uint256 h; /// The curve parameter 'k' - square of virtual liquidity uint256 k; /// The circulating supply threshold for adding the token to the DEX uint256 dexSupplyThresh; /// The address of the quote token (address(0) if native gas token) address quoteTokenAddress; /// Whether native-to-quote swap is enabled for this token bool nativeToQuoteSwapEnabled; /// The extension ID used by the token (bytes32(0) if no extension) bytes32 extensionID; /// The buy tax rate in basis points (0 if not a tax token) uint256 buyTaxRate; /// The sell tax rate in basis points (0 if not a tax token) uint256 sellTaxRate; /// The DEX pool address (address(0) if not listed on DEX) address pool; /// The progress towards DEX listing (0 to 1e18, where 1e18 = 100%) uint256 progress; /// The V3 LP fee profile for the token (see V3LPFeeProfile enum for interpretation) uint8 lpFeeProfile; /// The Dex Id (see DEXId enum for interpretation) uint8 dexId; } /// @notice Parameters for creating a new token (V2) struct NewTokenV2Params { /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The tax rate in basis points (if non-zero, this is a tax token) uint16 taxRate; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token /// For rev share tokens, this is the address that can claim the LP fees /// For tax tokens, this is the address that receives the tax fees address beneficiary; /// The optional permit data for the quote token bytes permitData; } /// @notice Parameters for creating a new token (V3) with extension support struct NewTokenV3Params { /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The tax rate in basis points (if non-zero, this is a tax token) uint16 taxRate; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token /// For rev share tokens, this is the address that can claim the LP fees /// For tax tokens, this is the address that receives the tax fees address beneficiary; /// The optional permit data for the quote token bytes permitData; /// @notice The ID of the extension to be used for the new token if not zero bytes32 extensionID; /// @notice Additional extension specific data to be passed to the extension's \`onTokenCreation\` method, check the extension's documentation for details on the expected format and content. bytes extensionData; } /// @notice Parameters for creating a new token (V4) with DEX ID and LP fee profile support struct NewTokenV4Params { /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The tax rate in basis points (if non-zero, this is a tax token) uint16 taxRate; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token /// For rev share tokens, this is the address that can claim the LP fees /// For tax tokens, this is the address that receives the tax fees address beneficiary; /// The optional permit data for the quote token bytes permitData; /// @notice The ID of the extension to be used for the new token if not zero bytes32 extensionID; /// @notice Additional extension specific data to be passed to the extension's \`onTokenCreation\` method, check the extension's documentation for details on the expected format and content. bytes extensionData; /// @notice The preferred DEX ID for the token DEXId dexId; /// @notice The preferred V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; } /// @notice Parameters for creating a new token (V5) with tax V2 support struct NewTokenV5Params { /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The tax rate in basis points (if non-zero, this is a tax token) uint16 taxRate; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token /// For rev share tokens, this is the address that can claim the LP fees /// For tax tokens, this is the address that receives the tax fees address beneficiary; /// The optional permit data for the quote token bytes permitData; /// @notice The ID of the extension to be used for the new token if not zero bytes32 extensionID; /// @notice Additional extension specific data to be passed to the extension's \`onTokenCreation\` method, check the extension's documentation for details on the expected format and content. bytes extensionData; /// @notice The preferred DEX ID for the token DEXId dexId; /// @notice The preferred V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; // New V5 tax-specific fields (only used when taxRate > 0) /// Tax duration in seconds (max: 100 years) uint64 taxDuration; /// Anti-farmer duration in seconds (max: 1 year) uint64 antiFarmerDuration; /// Market allocation basis points (to beneficiary) uint16 mktBps; /// Deflation basis points (burned) uint16 deflationBps; /// Dividend basis points (to dividend contract) uint16 dividendBps; /// Liquidity provision basis points (LP to dead address) uint16 lpBps; /// Minimum balance for dividend eligibility (min: 10K ether, required when dividendBps > 0) uint256 minimumShareBalance; } /// @dev Magic address value for \`dividendToken\` in NewTokenV6Params. /// When set to MAGIC\_DIVIDEND\_SELF, the dividend token is resolved to the tax token's own address /// after it is created. See MAGIC\_DIVIDEND\_SELF file-level constant. /// @notice Parameters for creating a new token (V6) with asymmetric tax and custom dividend token struct NewTokenV6Params { // --- Same base fields as V5 --- /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token address beneficiary; /// The optional permit data for the quote token bytes permitData; /// The ID of the extension to be used for the new token if not zero bytes32 extensionID; /// Additional extension specific data bytes extensionData; /// The preferred DEX ID for the token DEXId dexId; /// The preferred V3 LP fee profile for the token V3LPFeeProfile lpFeeProfile; // --- Tax fields (all zero = non-tax token, behaves like newTokenV5) --- /// Buy tax rate in basis points (0 = no buy tax) uint16 buyTaxRate; /// Sell tax rate in basis points (0 = no sell tax) uint16 sellTaxRate; /// Tax duration in seconds uint64 taxDuration; /// Anti-farmer duration in seconds uint64 antiFarmerDuration; /// Market allocation basis points (to beneficiary) uint16 mktBps; /// Deflation basis points (burned) uint16 deflationBps; /// Dividend basis points (to dividend contract) uint16 dividendBps; /// Liquidity provision basis points (LP to dead address) uint16 lpBps; /// Minimum balance for dividend eligibility (required when dividendBps > 0) uint256 minimumShareBalance; // --- New V3-only fields --- /// @notice Dividend distribution token: /// address(0) = native gas token dividend (only valid when quoteToken is also address(0)) /// MAGIC\_DIVIDEND\_SELF = distribute the tax token itself as dividend /// quoteToken address = explicitly use the quote token as dividend (must be set explicitly) /// any other ERC-20 = use that specific ERC-20 as dividend token address dividendToken; /// @notice Commission receiver address (zero = disabled). /// MUST be address(0) for non-tax tokens (buyTaxRate == 0 && sellTaxRate == 0). /// commissionBps is NOT provided here — it is calculated internally by the launcher /// based on the effective tax rate via \_commissionForTax(). address commissionReceiver; /// @notice The token version to create. Determines which launcher path is used. /// TOKEN\_V2\_PERMIT — non-tax token (standard ERC-20 with permit) /// TOKEN\_TAXED — V1 tax token (symmetric rates, mktBps=10000, no commission) /// TOKEN\_TAXED\_V2 — V2 tax token (asymmetric rates, flexible distribution, no commission, mktBps != 10000) /// TOKEN\_TAXED\_V3 — V3 tax token (asymmetric rates, flexible distribution, commission supported) TokenVersion tokenVersion; } // NOTE: \`converter\` is NOT in this struct — it is provided as an immutable // in PortalBase and automatically passed to TaxProcessor at initialization. // ─── V7 Fee Configuration ──────────────────────────────────────────────── /// @notice Fee type for token V7 /// @dev Not only tax fees but also LP fees and other fee types enum FeeType { NONE, // Not used / disabled MARKETING\_OR\_VAULT, // Fee goes to marketing address or vault DIVIDEND, // Fee distributed as dividends DEFLATION, // Fee is burned LP\_BPS // Fee added to LP } /// @notice Fee configuration for a single fee slot /// @dev Not only tax fees but also possibly the LP fee. /// Only the fields relevant to the feeType need to be populated: /// MARKETING\_OR\_VAULT → marketingAddress + bps /// DIVIDEND → dividendToken + minimumShareBalance + bps /// DEFLATION → bps /// LP\_BPS → bps /// NONE → all fields ignored struct FeeConfig { FeeType feeType; // The type of fee uint16 bps; // Basis points for this fee (0-10000) address marketingAddress; // Only used for MARKETING\_OR\_VAULT type address dividendToken; // Only used for DIVIDEND type (the token to distribute) uint256 minimumShareBalance; // Only used for DIVIDEND type (min balance for eligibility) } /// @notice Parameters for launching a V7 token (V4/PCS Infinity migration support) /// @dev Flat struct — all fields from V6 are duplicated here for clarity and forward-compatibility. /// If a V8 is needed, copy this struct and append new fields (same pattern). /// /// Fee distribution is fully described by feeConfigs\[4\]. Each slot is independent and /// can hold any FeeType (NONE, MARKETING\_OR\_VAULT, DIVIDEND, DEFLATION, LP\_BPS). /// All non-NONE slots' bps MUST sum to exactly 10000. /// The first MARKETING\_OR\_VAULT entry's marketingAddress is used as the primary beneficiary /// for LP-fee claiming purposes. Up to 4 MARKETING\_OR\_VAULT slots are supported. struct NewTokenV7Params { // ── Base fields (same as NewTokenV6Params) ────────────────── /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The optional permit data for the quote token bytes permitData; /// The ID of the extension to be used for the new token if not zero bytes32 extensionID; /// Additional extension specific data bytes extensionData; /// The preferred DEX ID for the token DEXId dexId; // ── Tax fields (all zero = non-tax token) ─────────────────── /// Buy tax rate in basis points (0 = no buy tax) uint16 buyTaxRate; /// Sell tax rate in basis points (0 = no sell tax) uint16 sellTaxRate; /// Tax duration in seconds uint64 taxDuration; /// Anti-farmer duration in seconds uint64 antiFarmerDuration; /// @notice Commission receiver address (zero = disabled). /// @dev Only meaningful for TOKEN\_TAXED\_V3. /// MUST be address(0) for TOKEN\_V3\_PERMIT (non-tax tokens do not support commission). /// commissionBps is NOT provided here — it is auto-computed internally by the launcher /// based on the effective tax rate via \_commissionForTax(). address commissionReceiver; /// @notice The token version to create (see TokenVersion enum) TokenVersion tokenVersion; /// @notice Fee distribution configuration (4 slots, must sum to 10000 bps). /// @dev Not only tax fees but also possibly the LP fee. FeeConfig\[4\] feeConfigs; } /// @notice Parameters for staging a new token (V5) - immutable parameters only struct StageNewTokenV5Params { /// The DEX supply threshold type DexThreshType dexThresh; /// The salt for deterministic deployment bytes32 salt; /// Whether this is a tax token bool isTaxToken; /// The migrator type (see MigratorType enum) MigratorType migratorType; /// The quote token address (native gas token if zero address) address quoteToken; /// @notice The preferred DEX ID for the token DEXId dexId; } /// @notice Parameters for committing a staged token (V5) - mutable/deployment-time parameters struct CommitNewTokenV5Params { /// The salt for deterministic deployment (must match staged salt) bytes32 salt; /// The tax rate in basis points (0 for non-tax tokens) uint16 taxRate; /// The name of the token string name; /// The symbol of the token string symbol; /// The metadata URI of the token string meta; /// The initial quote token amount to spend for buying uint256 quoteAmt; /// The beneficiary address for the token /// For rev share tokens, this is the address that can claim the LP fees /// For tax tokens, this is the address that receives the tax fees address beneficiary; /// The optional permit data for the quote token bytes permitData; // New V5 tax-specific fields (only used when taxRate > 0) /// Tax duration in seconds (max: 100 years) uint64 taxDuration; /// Anti-farmer duration in seconds (max: 1 year) uint64 antiFarmerDuration; /// Market allocation basis points (to beneficiary) uint16 mktBps; /// Deflation basis points (burned) uint16 deflationBps; /// Dividend basis points (to dividend contract) uint16 dividendBps; /// Liquidity provision basis points (LP to dead address) uint16 lpBps; /// Minimum balance for dividend eligibility (required when dividendBps > 0) uint256 minimumShareBalance; } /// @dev The configuration of the "native to quote" swap /// i.e How to swap ETH for the quote token when the quote token is not ETH enum NativeToQuoteSwapType { SWAP\_DISABLED, // 0: disabled SWAP\_VIA\_V2\_POOL, // 1: swap through v2 pool SWAP\_VIA\_V3\_2500\_POOL, // 2: swap through v3 2500 pool SWAP\_VIA\_V3\_500\_POOL, // 3: swap through v3 500 pool SWAP\_VIA\_V3\_3000\_POOL, // 4: swap through v3 3000 pool SWAP\_VIA\_V3\_10000\_POOL, // 5: swap through v3 10000 pool SWAP\_VIA\_MIXED\_ROUTER // 6: multi-hop via PancakeSwap Infinity MixedQuoter + UniversalRouter (BSC only) // used for tokens like uUSD that route BNB ↔ USDT(V3) ↔ uUSD(BinPool). // The actual routing logic is bypassed in \_shouldUseMixedRouter() before // the enum is checked, so this value serves as a meaningful marker when // calling setQuoteTokenConfiguration — any non-SWAP\_DISABLED value would // work, but this makes intent explicit. } /// @dev the quote token configurations struct QuoteTokenConfiguration { uint8 enabled; // 8bit: 1 if allowed, 0 if not allowed CurveType defaultCurve; // 8bit: the default token curve type of the quote token CurveType alternativeCurve; // 8bit: the alternative token curve type of the quote token NativeToQuoteSwapType nativeToQuoteSwapType; // 8bit: the native to quote swap feature configuration of the quote token uint8 dexId; // 8bit: DEX ID for multiple DEXes support } /// @dev Enum for DEX pool types enum PoolType { V2, // Uniswap V2 style pools V3, // Uniswap V3 style pools V4, // Uniswap V4 style pools PCS\_INFINITY\_CL // PancakeSwap Infinity Concentrated Liquidity pools } /// @dev Packed DEX pool information struct PackedDexPool { address pool; // 160 bits: pool address uint24 fee; // 24 bits: fee tier (for V3), 0 for V2 PoolType poolType; // 8 bits: enum for pool type uint24 tickSpacing; // 24 bits: tick spacing (for V4/PCS), 0 for V2/V3 uint40 unused; // 40 bits: reserved for future use } /// @notice Records who locked a CREATE2 salt and for which token version. /// locker + tokenVersion + isUsed pack into a single 32-byte storage slot. struct SaltLockEntry { address locker; // 20 bytes — address that paid to reserve this salt uint8 tokenVersion; // 1 byte — TokenVersion enum value at lock time bool isUsed; // 1 byte — true once the salt has been consumed by a token launch // locker + tokenVersion + isUsed pack into a single 32-byte slot } // // Events // /// @notice emitted when a token is staged (but not yet created) /// /// @param ts The timestamp of the event /// @param creator The address of the creator /// @param token The predetermined address of the token event FlapTokenStaged(uint256 ts, address creator, address token); /// @notice emitted when a new token is created /// /// @param ts The timestamp of the event /// @param creator The address of the creator /// @param nonce The nonce of the token /// @param token The address of the token /// @param name The name of the token /// @param symbol The symbol of the token /// @param meta The meta URI of the token event TokenCreated( uint256 ts, address creator, uint256 nonce, address token, string name, string symbol, string meta ); /// @notice emitted when a token is bought /// /// @param ts The timestamp of the event /// @param token The address of the token /// @param buyer The address of the buyer /// @param amount The amount of tokens bought /// @param eth The amount of ETH spent /// @param fee The amount of ETH spent on fee /// @param postPrice The price of the token after this trade event TokenBought( uint256 ts, address token, address buyer, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice ); /// @notice emitted when a token is sold /// /// @param ts The timestamp of the event /// @param token The address of the token /// @param seller The address of the seller /// @param amount The amount of tokens sold /// @param eth The amount of ETH received /// @param fee The amount of ETH deducted as a fee /// @param postPrice The price of the token after this trade event TokenSold( uint256 ts, address token, address seller, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice ); /// emitted when a token's curve is set /// @param token The address of the token /// @param curve The address of the curve /// @param curveParameter The parameter of the curve event TokenCurveSet(address token, address curve, uint256 curveParameter); /// @notice emitted when a token's curve parameters are set (V2) /// @param token The address of the token /// @param r The virtual ETH reserve parameter /// @param h The virtual token reserve parameter /// @param k The square of the virtual Liquidity parameter event TokenCurveSetV2(address token, uint256 r, uint256 h, uint256 k); /// emitted when a token's dexSupplyThresh is set /// @param token The address of the token /// @param dexSupplyThresh The new dexSupplyThresh of the token event TokenDexSupplyThreshSet(address token, uint256 dexSupplyThresh); /// emitted when a token's implementation is set /// @param token The address of the token /// @param version The version of the token event TokenVersionSet(address token, TokenVersion version); /// @notice emitted when a new vanity token is created /// @param token The address of the created token /// @param creator The address of the creator /// @param beneficiary The address of the beneficiary event VanityTokenCreated(address token, address creator, address beneficiary); /// @notice emitted when a token's quote token is set /// @param token The address of the token /// @param quoteToken The address of the quote token event TokenQuoteSet(address token, address quoteToken); /// @notice emitted when a token's migrator is set /// @param token The address of the token /// @param migratorType The migrator type event TokenMigratorSet(address token, MigratorType migratorType); /// @notice emitted when a token's extension is enabled /// @param token The address of the token /// @param extensionID The extension ID /// @param extensionAddress The address of the extension contract /// @param version The version of the extension event TokenExtensionEnabled(address token, bytes32 extensionID, address extensionAddress, uint8 version); /// @notice emitted when a token's pool info is updated /// @param token The address of the token /// @param poolInfo The new pool information event TokenPoolInfoUpdated(address token, PackedDexPool poolInfo); /// @notice emitted when a trader's fee exemption status is updated /// @param trader The address of the trader /// @param isExempted Whether the trader is exempted from fees event FeeExemptionUpdated(address indexed trader, bool isExempted); // // events // /// @notice emitted when token is redeemed /// @param ts The timestamp of the event /// @param srcToken The address of the token to redeem /// @param dstToken The address of the token to receive /// @param srcAmount The amount of srcToken to redeem /// @param dstAmount The amount of dstToken to receive /// @param who The address of the redeemer event TokenRedeemed( uint256 ts, address srcToken, address dstToken, uint256 srcAmount, uint256 dstAmount, address who ); /// @notice emitted when the bit flags are changed /// @param oldFlags The old flags /// @param newFlags The new flags event BitFlagsChanged(uint256 oldFlags, uint256 newFlags); /// @notice emitted when adding liquidity to DEX /// @param token The address of the token /// @param pool The address of the pool /// @param amount The amount of token added /// @param eth The amount of quote Token added event LaunchedToDEX(address token, address pool, uint256 amount, uint256 eth); /// @notice Emitted when V4/PCS LP fees are collected and forwarded event V4LPFeesCollected(address indexed token, uint256 quoteAmount, uint256 tokenAmount); /// @notice emitted when a new CL pool is created for a token /// @param token The address of the token /// @param poolId The ID of the created CL pool /// @param sqrtPriceX96 The initial sqrt price used to initialize the CL pool event FlapTokenCLPoolCreated(address token, bytes32 poolId, uint160 sqrtPriceX96); /// @notice emitted when the progress of a token changes /// @param token The address of the token /// @param newProgress The new progress value in Wad event FlapTokenProgressChanged(address token, uint256 newProgress); // // Token V2 supply change // /// @notice emitted when the circulating supply of a token changes /// @param token The address of the token /// @param newSupply The new circulating supply event FlapTokenCirculatingSupplyChanged(address token, uint256 newSupply); /// @notice emitted when a new tax is set for a token /// @dev For V3 tokens with asymmetric rates, this carries max(buyTax, sellTax) for backward compatibility. /// Listen for FlapTokenAsymmetricTaxSet to get the full buy/sell split. /// @param token The address of the token /// @param tax The tax value set for the token (max of buy and sell rates for V3 tokens) event FlapTokenTaxSet(address token, uint256 tax); /// @notice Emitted when a token is launched with asymmetric buy/sell tax rates (V3 tokens only). /// @dev Emitted in addition to FlapTokenTaxSet (which carries max(buyTax, sellTax) for backward /// compatibility). New systems that support asymmetric rates should listen to this event. /// Old systems that only read FlapTokenTaxSet will continue to work correctly. /// @param token The address of the token /// @param buyTax The buy tax rate in basis points /// @param sellTax The sell tax rate in basis points event FlapTokenAsymmetricTaxSet(address token, uint256 buyTax, uint256 sellTax); // operation related // should remove later /// @notice emitted when a users successfully checked in /// @param user The address of the user event CheckedIn(address user); /// @notice emitted when a beneficiary claims fees /// @param token The address of the token /// @param beneficiary The address of the beneficiary /// @param tokenAmount The amount of the token claimed /// @param ethAmount The amount of ETH claimed event BeneficiaryClaimed(address token, address beneficiary, uint256 tokenAmount, uint256 ethAmount); /// @notice emitted when a token beneficiary is changed /// @param token The address of the token /// @param oldBeneficiary The previous beneficiary address /// @param newBeneficiary The new beneficiary address event BeneficiaryChanged(address token, address oldBeneficiary, address newBeneficiary); /// @notice emitted when an extension is registered /// @param extensionId The unique identifier for the extension /// @param extensionAddress The address of the extension contract /// @param version The version of the extension event ExtensionRegistered(bytes32 extensionId, address extensionAddress, uint8 version); /// @notice emitted when a spammer's blocked status is changed /// @param spammer The address of the spammer /// @param blocked True if blocked, false if unblocked event SpammerBlockedStatusChanged(address spammer, bool blocked); /// @notice emitted when a user is rate limited from creating tokens /// @param user The address of the rate-limited user /// @param lastCreationTime The timestamp of their last successful token creation event RateLimited(address user, uint256 lastCreationTime); /// @notice emitted when a quote token configuration is set /// @param quoteToken The address of the quote token /// @param config The configuration set for the quote token event QuoteTokenConfigurationSet(address quoteToken, QuoteTokenConfiguration config); /// @notice emitted when a V3 favored fee is set for a quote token /// @param quoteToken The address of the quote token /// @param favoredFee The favored fee set for the quote token event V3FavoredFeeSet(address quoteToken, uint24 favoredFee); /// @notice emitted when a token's DEX preference is set /// @param token The address of the token /// @param dexId The preferred DEX ID for the token /// @param lpFeeProfile The preferred V3 LP fee profile for the token event TokenDexPreferenceSet(address token, DEXId dexId, V3LPFeeProfile lpFeeProfile); /// @notice emitted when a message is sent /// @param sender The address of the sender /// @param token The address of the token /// @param message The message sent event MsgSent(address sender, address token, string message); // // Custom Errors // /// @notice error if the dex is both pancake and algebra1.9 /// which is impossible error DEXCannotBeBothPancakeAndAlgebra1\_9(); /// @notice error if the portal lens address is zero error PortalLensCannotBeZero(); /// @notice error if the portal lens v2 address is zero error PortalLensV2CannotBeZero(); /// @notice error if the multi dex router address is zero error MultiDexRouterCannotBeZero(); /// @notice error if the token does not exist error TokenNotFound(address token); /// @notice error if the amount is too small error AmountTooSmall(uint256 amount); /// @notice error if slippage is too high /// i.e: actualAmount < minAmount error SlippageTooHigh(uint256 actualAmount, uint256 minAmount); /// @notice error if the input token & output token of a swap is the same error SameToken(address tokenA); /// @notice error if trying to trade a killed token error TokenKilled(address token); /// @notice error if token is not tradable error TokenNotTradable(address token); /// @notice error if trying to sell a token that is in a battle error TokenInDuel(address token); /// @notice error if trying to redeem a token that is not killed error TokenNotKilled(address token); /// @notice error if the token has already been added to the DEX error TokenAlreadyDEXed(address token); /// @notice error if the token has already been staged or created error TokenAlreadyStaged(address token); /// @notice error if the token is not in staged status error TokenNotStaged(address token); /// @notice error if the token is not listed on DEX yet error TokenNotDEXed(address token); /// @notice error if there is no conversion path from srcToken to dstToken error NoConversionPath(address srcToken, address dstToken); /// @notice error if the round is not found error RoundNotFound(uint256 id); /// @notice error if the round id is invalid error InvalidRoundID(uint256 id); /// @notice error if try to start a new round but the last round is not resolved error LastRoundNotResolved(); /// @notice cannot use a token for the next round of the game error InvalidTokenForBattle(address token); /// @notice error if the signature is invalid error InvalidSigner(address signer); /// @notice error if the seq is not found in Game queue error SeqNotFound(uint256 seq); /// @notice error if not implemented yet error NotImplemented(); /// @notice error a token is already in the game error TokenAlreadyInGame(address token); /// @notice error if a call reverted but without any data error CallReverted(); /// @notice error if creating token is disabled error PermissionlessCreateDisabled(); /// @notice error if trading is disabled error TradeDisabled(); /// @notice error if the circuit breakers are off error ProtocolDisabled(); /// @notice error if the game supply threshold is not valid error InvalidGameSupplyThreshold(); /// @notice error if the dex supply threshold is not valid error InvalidDEXSupplyThreshold(); /// @notice error if the proof does not match the msg.sender error MismatchedAddressInProof(address expected, address actual); /// @notice error if the whitlist creator cannot create more tokens error NoQuotaForCreator(uint256 created, uint256 max); /// @notice error if the piggyback lenght is not valid error InvalidPiggybackLength(uint256 expected, uint256 actual); /// @notice error if the tax processor is not set for UniV4 related operations error TaxProcessorUniV4Required(); /// @notice error if the feature is disabled error FeatureDisabled(); /// @notice error if caller is not authorized (e.g., only SaleForge can call) error OnlySaleForge(); /// @notice error if the quote token is not allowed error QuoteTokenNotAllowed(address quoteToken); /// @notice error if a native to quote swap is required but not supported /// native to quote swap: i.e, swap the input token to the desired quote token error NativeToQuoteSwapNotSupported(); /// @notice error if the native to quote swap v3 fee type is not supported /// @param NativeToQuoteSwapType The unsupported native to quote swap type error NativeToQuoteSwapFeeTierNotSupported(uint8 NativeToQuoteSwapType); /// @notice error if met any dirty bits error DirtyBits(); // // Dex Related // /// @notice error if sqrPriceA is gte than sqrtPriceB error PriceAMustLTPriceB(uint160 sqrtPriceA, uint160 sqrtPriceB); /// @notice error if the actual amount is more than the expected amount error ActualAmountMustLTEAmount(uint256 actualAmount, uint256 amount1); /// @notice error if the msg.sender is not a Uniswap V3 pool error NotUniswapV3Pool(address sender); /// @notice error if the uniswap v2 pool's liquidity is not zero error UniswapV2PoolNotZero(address pool, uint256 liquidity); /// @notice error if the required token amount for adding Uniswap v2 liquidity is more than the remaining token error RequiredTokenMustLTE(uint256 requiredToken, uint256 reserveToken); /// @notice revert when calling slot0 of a Uniswap V3 pool failed error UniswapV3Slot0Failed(); /// @notice error if a non-position NFT is received error NonPositionNFTReceived(address collection); /// @notice error if the provided dex threshold is invalid error InvalidDexThreshold(uint256 threshold); /// @notice error if the provided address is not a valid pool error InvalidPoolAddress(address pool, address expected); // // staking related // /// @notice error if the locks are invalid error InvalidLocks(); /// @notice error if staking feature is not enabled error StakingDisabled(); /// @notice error if the operator does not have the roller role error NotRoller(); // operation related /// @notice error if the user cannot check in yet /// @param next The timestamp when the user can check in again error cannotCheckInUntil(uint256 next); // misc /// @notice error if another token has the same meta error MetaAlreadyUsedByOtherToken(string meta); /// @notice error if the creation fee is insufficient error InsufficientCreationFee(uint256 required, uint256 provided); /// @notice error if the provided ETH is insufficient to cover the required fee /// @param required The required fee amount /// @param provided The provided ETH amount error InsufficientFee(uint256 required, uint256 provided); /// @notice error if the vanity address requirement is not met /// @param token The generated token address error VanityAddressRequirementNotMet(address token); /// @notice error if the token is not in DEX status error TokenNotInDEXStatus(address token); /// @notice error if the caller is not the token's beneficiary error CallerNotBeneficiary(address caller, address expected); /// @notice error if no locks are available for the token error NoLocksAvailable(address token); /// @notice error if the provided ETH is insufficient to cover the required input amount /// @param provided The provided ETH amount /// @param required The required ETH amount error InsufficientEth(uint256 provided, uint256 required); /// @notice error if the provided tax bps is invalid /// @param tax The provided tax bps error InvalidTaxBps(uint256 tax); /// @notice error if the transferFrom call failed /// @param token The address of the token /// @param from The address from which the tokens were to be transferred /// @param amount The amount of tokens that were to be transferred error TransferFromFailed(address token, address from, uint256 amount); /// @notice error if the migrator type is invalid error InvalidMigratorType(); /// @notice error if the quote token is not native but not using PortalTradeV2 error QuoteTokenNotNativeButNotUsingTradeV2(); /// @notice error if the msg.value is less than expected value when creating /// a tax token using an ERC20 (e.g: USDC) token as the quote token. /// @dev For the tax token's tax splitter to work properly, we need approximately 1gwei due to our /// implemenation of the tax splitter. error InsufficientValueForTaxTokenCreation(uint256 expected, uint256 provided); /// @notice error if the caller is not a guardian or admin /// @param caller The address of the caller error NotGuardian(address caller); /// @notice error if the extension version is not supported /// @param version The unsupported extension version error UnsupportedExtensionVersion(uint8 version); /// @notice error if the token uses an extension but is traded through the legacy PortalTrade contract /// @param token The address of the token with an extension error TokenWithExtensionNotSupported(address token); /// @notice error if the parameters for ToshiMart are invalid error InvalidParamsForToshiMart(); /// @notice error if the parameters for X\_LAYER are invalid error InvalidParamsForXLayer(); /// @notice error if the quote token configuration is invalid error InvalidQuoteTokenConfiguration(); /// @notice error when trying to use PortalTrade with tokens that have non-zero h parameter error ErrShouldUsePortalTradeV2(); /// @notice error when no supported DEX is found for the token error NoSupportedDEX(); /// @notice invalid fee tier for DEX error InvalidFeeTierForDEX(); /// @notice error if the tax distribution percentages don't add up to 100% error InvalidTaxDistribution(); /// @notice error if tax duration exceeds maximum allowed error TaxDurationTooLong(); /// @notice error if tax duration is less than minimum allowed error TaxDurationTooShort(); /// @notice error if anti-farmer duration exceeds maximum allowed error AntiFarmerDurationTooLong(); /// @notice error when the swap from quoteToken to dividendToken is not supported by the SwapRegistry error DividendSwapNotSupported(address quoteToken, address dividendToken); /// @notice error if minimum share balance is too low for dividend eligibility error MinimumShareBalanceTooLow(); /// @notice error when dividend parameters are missing but required error DividendParametersRequired(); /// @notice error when \`dividendBps > 0\` is configured together with a dividend token that is /// not the quote token. Emitted by \`launchTaxTokenV3\` while live quote→custom-token /// dividend conversion is not yet enabled. Custom dividend tokens remain permitted /// when \`dividendBps == 0\` (tracker-only mode). error DividendTokenMustEqualQuoteToken(); /// @notice error when a non-NONE FeeConfig slot has bps == 0 error InvalidFeeConfigBps(); /// @notice error when a FeeConfig slot type appears more than once in the feeConfigs array /// @param feeType The uint8 value of the duplicated FeeType enum entry error DuplicateFeeSlot(uint8 feeType); /// @notice error when a MARKETING\_OR\_VAULT fee slot has a zero marketing/vault address error ZeroMarketingAddress(); /// @notice error when a newTokenV7 request violates the currently supported launch rules. /// @dev Detailed code mapping is documented in \`src/PortalTokenLauncher.sol\`. error NewTokenV7RuleViolation(uint8 code); /// @notice error when TOKEN\_V3\_PERMIT via newTokenV7 is configured with a fee type /// that is not enabled for the initial V7 release. /// @param feeType The unsupported FeeType enum value. error UnsupportedV7TokenV3PermitFeeType(uint8 feeType); /// @notice error when user is rate limited from creating tokens /// @param user The address that is rate limited /// @param lastCreationTime The timestamp of the user's last successful token creation error RateLimitExceeded(address user, uint256 lastCreationTime); /// @notice error when user is permanently blocked from creating tokens /// @param user The address that is blocked error SpammerBlocked(address user); // --- Salt Locking --- /// @notice Emitted when a salt is locked by a user. /// @param locker The address that paid to lock the salt. /// @param salt The CREATE2 salt that was locked. /// @param tokenAddress The predicted token address derived from salt + tokenVersion. /// @param tokenVersion The TokenVersion enum value the lock applies to. /// @param ts Block timestamp of the lock. event FlapSaltLocked(address locker, bytes32 salt, address tokenAddress, uint8 tokenVersion, uint256 ts); /// @notice Emitted when a locked salt is consumed by a successful launch. /// @param locker The address that originally locked the salt. /// @param salt The CREATE2 salt that was used. /// @param tokenAddress The token address that was launched. /// @param tokenVersion The TokenVersion enum value that was locked. /// @param ts Block timestamp of the launch. event FlapSaltUsed(address locker, bytes32 salt, address tokenAddress, uint8 tokenVersion, uint256 ts); /// @notice msg.value does not equal the required SALT\_LOCK\_FEE. error SaltLockFeeMismatch(uint256 required, uint256 provided); /// @notice The salt is already locked by a different address. error SaltAlreadyLockedByAnotherUser(bytes32 salt, address existingLocker); /// @notice The caller tried to lock a salt they already hold the lock for. error SaltAlreadyLockedBySelf(bytes32 salt); /// @notice Launch was rejected because the salt is locked by someone other than the caller. error FlapSaltLockedByAnotherUser(bytes32 salt, address locker); /// @notice Launch was rejected because the locked tokenVersion does not match the one being launched. error SaltLockTokenVersionMismatch(bytes32 salt, TokenVersion locked, TokenVersion provided); /// @notice The tokenVersion passed to lockSalt() is not currently supported. error UnsupportedTokenVersion(TokenVersion provided); } /// @notice Callback interface implemented by VaultPortal (or any trusted caller). /// Portal calls getSaltOwner() when msg.sender == VAULT\_PORTAL to resolve the /// effective user for salt-lock enforcement without changing any parameter structs. interface ISaltOwnerProvider { /// @notice Return the address that should be treated as the salt owner for the current launch. /// @dev VaultPortal must set \_pendingSaltOwner before delegating into Portal. /// The call is a staticcall so it cannot modify state. function getSaltOwner() external view returns (address); } /// @title IPortalLauncherTwoStep /// @notice Interface for the two-step token launch process interface IPortalLauncherTwoStep is IPortalTypes { /// @notice Stage a new token (V5) without creating it yet /// @param params The immutable parameters for the new token /// @return token The predetermined address of the token /// @dev This stages a token by validating parameters and reserving the token address. /// The token contract is not deployed yet. Call commitNewTokenV5 to actually deploy it. /// Extensions are not supported in two-step launch. LP fee profile is always STANDARD. /// FIXME: this is not enabled in this version. Will be available once the audit for this part is ready. function stageNewTokenV5(StageNewTokenV5Params calldata params) external returns (address token); /// @notice Commit a staged token (V5) and create it /// @param params The parameters for token deployment (includes salt to identify staged token) /// @dev This deploys the token contract and initializes it. The token must have been staged first via stageNewTokenV5. /// The salt and isTaxToken in params must match what was used during staging. /// If taxRate > 0, creates FlapTaxTokenV2, otherwise creates regular token. /// FIXME: this is not enabled in this version. Will be available once the audit for this part is ready. function commitNewTokenV5(CommitNewTokenV5Params calldata params) external payable; } /// @notice Handles token creation and related operations interface IPortalLauncher is IPortalTypes { /// @notice Create a new token (V2) with flexible parameters /// @param params The parameters for the new token /// @return token The address of the created /// @dev due to the implementation limit, when creating a tax token and using an ERC20 token as the quote token, /// You need to pay an extra 1gwei native gas token (i.e msg.value = 1 gwei), or you will encounter an InsufficientValueForTaxTokenCreation error function newTokenV2(NewTokenV2Params calldata params) external payable returns (address token); /// @notice Create a new token (V3) with extension support /// @param params The parameters for the new token including extension configuration /// @return token The address of the created token /// @dev Similar to newTokenV2 but with extension support. Extension hooks will be called if extensionID is non-zero function newTokenV3(NewTokenV3Params calldata params) external payable returns (address token); /// @notice Create a new token (V4) with DEX ID and LP fee profile support /// @param params The parameters for the new token including DEX ID and LP fee profile /// @return token The address of the created token /// @dev Similar to newTokenV3 but with DEX ID and LP fee profile support. Allows specifying preferred DEX and fee tier function newTokenV4(NewTokenV4Params calldata params) external payable returns (address token); /// @notice Create a new token (V5) with tax V2 support /// @param params The parameters for the new token including advanced tax features /// @return token The address of the created token /// @dev Similar to newTokenV4 but with support for FlapTaxTokenV2 when taxRate > 0. /// When taxRate is 0, behaves like newTokenV4 (uses regular token or FlapTaxToken). /// When taxRate > 0, creates a FlapTaxTokenV2 with advanced tax distribution features. /// FIXME: this is not enabled in this version. Will be available once the audit for this part is ready. function newTokenV5(NewTokenV5Params calldata params) external payable returns (address token); /// @notice Create a new token (V6) — unified entry point for all token versions. /// @param params The parameters for the new token (see NewTokenV6Params). /// @return token The address of the created token. /// /// @dev Dispatch logic based on \`params.tokenVersion\`: /// /// TOKEN\_V2\_PERMIT (non-tax token): /// - buyTaxRate and sellTaxRate MUST both be 0. /// - commissionReceiver MUST be address(0). /// - Dispatched to PortalLauncherV5 → creates a standard ERC-20 (TOKEN\_V2\_PERMIT). /// /// TOKEN\_TAXED (V1 tax token): /// - At least one tax rate must be > 0. /// - buyTaxRate MUST equal sellTaxRate (symmetric rates only). /// - mktBps MUST be 10000 (all tax goes to beneficiary after protocol fee). /// - dividendBps MUST be 0; deflationBps and lpBps MUST be 0. /// - commissionReceiver MUST be address(0) (commission not supported). /// - Dispatched to PortalLauncherV5Tax → creates a FlapTaxToken (TOKEN\_TAXED). /// /// TOKEN\_TAXED\_V2 (V2 tax token): /// - At least one tax rate must be > 0. /// - buyTaxRate MUST equal sellTaxRate (symmetric rates only via V5 path). /// - mktBps MUST NOT be 10000 (use TOKEN\_TAXED for that case). /// - mktBps + deflationBps + dividendBps + lpBps MUST equal 10000. /// - commissionReceiver MUST be address(0) (commission not supported). /// - Dispatched to PortalLauncherV5Tax → creates a FlapTaxTokenV2 (TOKEN\_TAXED\_V2). /// /// TOKEN\_TAXED\_V3 (V3 tax token): /// - At least one tax rate must be > 0. /// - Asymmetric rates allowed (buyTaxRate != sellTaxRate is OK). /// - mktBps + deflationBps + dividendBps + lpBps MUST equal 10000. /// - mktBps == 10000 IS allowed (all tax → protocol fee + commission, no marketing). /// - commissionReceiver CAN be non-zero (commission supported). /// - Dispatched to PortalLauncherTaxV3.launchTaxTokenV3 → creates a FlapTaxTokenV3 (TOKEN\_TAXED\_V3). /// /// Emits FlapTokenTaxSet (with max rate) AND FlapTokenAsymmetricTaxSet for tax tokens. function newTokenV6(NewTokenV6Params calldata params) external payable returns (address token); /// @notice Reserve the token address derived from \`salt\` by paying SALT\_LOCK\_FEE. /// @dev The caller must pass the \`tokenVersion\` they intend to lock for. /// Accepted values: TOKEN\_V2\_PERMIT (2) for non-tax tokens (NON\_TAX\_TOKEN\_SUFFIX /// vanity requirement, typically 0x8888) and TOKEN\_TAXED\_V3 (6) for tax tokens /// (TAX\_TOKEN\_SUFFIX vanity requirement, typically 0x7777). Any other value /// reverts with UnsupportedTokenVersion. /// Fee is forwarded to FEE\_RECEIVER. Emits FlapSaltLocked. /// @param salt CREATE2 salt to reserve. /// @param tokenVersion Token version to lock for. Must be TOKEN\_V2\_PERMIT or TOKEN\_TAXED\_V3. function lockSalt(bytes32 salt, TokenVersion tokenVersion) external payable; /// @notice Launch a new token with V4/PCS Infinity migration support /// @param params The V7 token parameters /// @return token The created token address function newTokenV7(NewTokenV7Params calldata params) external payable returns (address token); } /// @title Portal Lens Interface /// @notice Handles read-only token state queries interface IPortalLens is IPortalTypes { /// @notice Get token state /// @param token The address of the token /// @return state The state of the token function getTokenV2(address token) external view returns (TokenStateV2 memory state); /// @notice Get token state (V3) /// @param token The address of the token /// @return state The state of the token (V3) function getTokenV3(address token) external view returns (TokenStateV3 memory state); /// @notice Get token state (V4) /// @param token The address of the token /// @return state The state of the token (V4) with only 'r' curve parameter function getTokenV4(address token) external view returns (TokenStateV4 memory state); /// @notice Get token state (V5) /// @param token The address of the token /// @return state The state of the token (V5) with all curve parameters (r, h, k) function getTokenV5(address token) external view returns (TokenStateV5 memory state); /// @notice Get token state (V6) /// @param token The address of the token /// @return state The state of the token (V6) with all V5 fields plus taxRate, pool, and progress function getTokenV6(address token) external view returns (TokenStateV6 memory state); /// @notice Get token state (V7) /// @param token The address of the token /// @return state The state of the token (V7) with all V6 fields plus lpFeeProfile function getTokenV7(address token) external view returns (TokenStateV7 memory state); /// @notice Get token state (V8) /// @param token The address of the token /// @return state The state of the token (V8) with asymmetric buyTaxRate and sellTaxRate function getTokenV8(address token) external view returns (TokenStateV8 memory state); /// @notice Get token state (V8Safe) /// @dev Returns enum-typed fields (TokenStatus, TokenVersion, V3LPFeeProfile, DEXId) as uint8 /// instead of their Solidity enum types, preventing ABI-decoding reverts when new enum /// variants are introduced. Use this method when you need forward/backward compatibility /// with future contract upgrades that may add new enum values. /// @param token The address of the token /// @return state The state of the token with enum fields encoded as uint8 function getTokenV8Safe(address token) external view returns (TokenStateV8Safe memory state); /// @notice Get the quote token configuration for a given quote token address /// @param quoteToken The address of the quote token /// @return config The configuration of the quote token function getQuoteTokenConfiguration(address quoteToken) external view returns (QuoteTokenConfiguration memory config); } interface IPortalLensV2 is IPortalTypes { /// @notice Get the salt lock entry for a given CREATE2 salt. /// @param salt The CREATE2 salt to query. /// @return entry The SaltLockEntry (locker == address(0) means unlocked). function getSaltLock(bytes32 salt) external view returns (SaltLockEntry memory entry); /// @notice Get the total number of salts locked by a user for a specific token version in the on-chain index. /// @dev Only locks made from v5.11.0 onwards are present; pre-upgrade locks are not indexed. /// @param user The address of the user. /// @param tokenVersion The TokenVersion (as uint8) to filter on. /// @return count The number of salts in the on-chain index for this user and version. function getLockedSaltsCountByUserAndVersion(address user, uint8 tokenVersion) external view returns (uint256 count); /// @notice Paginated enumeration of salts locked by a user for a specific token version. /// @dev Only locks made from v5.11.0 onwards are present; pre-upgrade locks are not indexed. /// \`limit\` is capped at 100. /// @param user The address of the user. /// @param tokenVersion The TokenVersion (as uint8) to filter on. /// @param offset Zero-based start index within the user+version set. /// @param limit Maximum number of entries to return (capped at 100). /// @return salts The salt values in \[offset, offset+limit).\ /// @return entries The corresponding SaltLockEntry structs.\ /// @return total The total number of salts in the on-chain index for this user and version.\ function getLockedSaltsByUserAndVersion(address user, uint8 tokenVersion, uint256 offset, uint256 limit)\ external\ view\ returns (bytes32\[\] memory salts, SaltLockEntry\[\] memory entries, uint256 total);\ }\ \ /// @title IPortalTrade Interface\ /// @notice Handles token trading and redemption\ interface IPortalTrade is IPortalTypes {\ /// @notice Buy token with ETH on creation\ /// @param token The address of the token to buy\ /// @param recipient The address to send the token to\ /// @param inputAmount The amount of ETH to spend\ ///\ /// @dev This function is mainly for internal use (be delegated called from the portal contract)\ /// The msg.value can be greater than inputAmount, the excess ETH will not be\ /// refunded to the caller. They will be charged as a fee.\ ///\ /// Note: the slippage is not checked in this function.\ ///\ function buyOnCreation(address token, address recipient, uint256 inputAmount)\ external\ payable\ returns (uint256 amount);\ \ /// @notice Buy token with ETH\ /// @param token The address of the token to buy\ /// @param recipient The address to send the token to\ /// @param minAmount The minimum amount of tokens to buy\ function buy(address token, address recipient, uint256 minAmount) external payable returns (uint256 amount);\ \ /// @notice Sell token for ETH\ /// @param token The address of the token to sell\ /// @param amount The amount of tokens to sell\ /// @param minEth The minimum amount of ETH to receive\ function sell(address token, uint256 amount, uint256 minEth) external returns (uint256 eth);\ \ /// @notice Redeem a killed token for another token\ /// @param srcToken The address of the token to redeem\ /// @param dstToken The address of the token to receive\ /// @param srcAmount The amount of srcToken to redeem\ /// @return dstAmount The amount of dstToken to receive\ function redeem(address srcToken, address dstToken, uint256 srcAmount) external returns (uint256 dstAmount);\ \ /// @notice Preview the amount of tokens to buy with ETH\ /// @param token The address of the token to buy\ /// @param eth The amount of ETH to spend\ /// @return amount The amount of tokens to buy\ function previewBuy(address token, uint256 eth) external view returns (uint256 amount);\ \ /// @notice Preview the amount of ETH to receive for selling tokens\ /// @param token The address of the token to sell\ /// @param amount The amount of tokens to sell\ /// @return eth The amount of ETH to receive\ function previewSell(address token, uint256 amount) external view returns (uint256 eth);\ \ /// @notice Preview redeem\ /// @param srcToken The address of the token to redeem\ /// @param dstToken The address of the token to receive\ /// @param srcAmount The amount of srcToken to redeem\ /// @return dstAmount The amount of dstToken to receive\ function previewRedeem(address srcToken, address dstToken, uint256 srcAmount)\ external\ view\ returns (uint256 dstAmount);\ }\ \ /// @title IPortalTradeV2 Interface\ /// @notice Handles unified token swaps and quoting\ interface IPortalTradeV2 is IPortalTypes {\ /// @notice Emitted when tax is paid on bonding curve for TAX\_TOKEN\ /// @param token The address of the token\ /// @param amount The amount of tax paid\ event TaxOnBondingCurvePaid(address indexed token, uint256 amount);\ \ /// @notice Emitted when tax is paid on bonding curve for TAX\_TOKEN\_V2\ /// @param token The address of the token\ /// @param amount The amount of tax paid\ event TaxV2OnBondingCurvePaid(address indexed token, uint256 amount);\ \ /// @notice Parameters for swapping exact input amount for output token\ struct ExactInputParams {\ /// @notice The address of the input token (use address(0) for native asset)\ address inputToken;\ /// @notice The address of the output token (use address(0) for native asset)\ address outputToken;\ /// @notice The amount of input token to swap (in input token decimals)\ uint256 inputAmount;\ /// @notice The minimum amount of output token to receive\ uint256 minOutputAmount;\ /// @notice Optional permit data for the input token (can be empty)\ bytes permitData;\ }\ \ /// @notice Parameters for swapping exact input amount for output token (V3) with extension support\ struct ExactInputV3Params {\ /// @notice The address of the input token (use address(0) for native asset)\ address inputToken;\ /// @notice The address of the output token (use address(0) for native asset)\ address outputToken;\ /// @notice The amount of input token to swap (in input token decimals)\ uint256 inputAmount;\ /// @notice The minimum amount of output token to receive\ uint256 minOutputAmount;\ /// @notice Optional permit data for the input token (can be empty)\ bytes permitData;\ /// @notice Additional extension specific data to be passed to the extension's \`onTrade\` method, check the extension's documentation for details on the expected format and content\ bytes extensionData;\ }\ \ /// @notice Parameters for quoting the output amount for a given input\ struct QuoteExactInputParams {\ /// @notice The address of the input token (use address(0) for native asset)\ address inputToken;\ /// @notice The address of the output token (use address(0) for native asset)\ address outputToken;\ /// @notice The amount of input token to swap (in input token decimals)\ uint256 inputAmount;\ }\ /// @notice Swap exact input amount for output token\ /// @param params The swap parameters\ /// @return outputAmount The amount of output token received\ /// @dev Here are some possible scenarios:\ /// If the token's reserve is BNB or ETH (i.e: the quote token is the native gas token):\ /// - BUY: input token is address(0), output token is the token address\ /// - SELL: input token is the token address, output token is address(0)\ /// If the token's reserve is another ERC20 token (eg. USD\*, i.e, the quote token is an ERC20 token):\ /// - BUY with USD\*: input token is the USD\* address, output token is the token address\ /// - SELL for USD\*: input token is the token address, output token is the USD\* address\ /// - BUY with BNB or ETH: input token is address(0), output token is the token address.\ /// (Note: this requires an internal swap to convert BNB/ETH to USD\*, nativeToQuoteSwap must be anabled for this quote token)\ /// Note: Currently, this method supports trading tokens that is either still on the bonding curve or already listed on DEX.\ \ function swapExactInput(ExactInputParams calldata params) external payable returns (uint256 outputAmount);\ \ /// @notice Swap exact input amount for output token (V3) with extension support\ /// @param params The swap parameters including extension data\ /// @return outputAmount The amount of output token received\ /// @dev Similar to swapExactInput but with extension support. Extension hooks will be called if the token uses an extension\ function swapExactInputV3(ExactInputV3Params calldata params) external payable returns (uint256 outputAmount);\ \ /// @notice Quote the output amount for a given input\ /// @param params The quote parameters\ /// @return outputAmount The quoted output amount\ /// @dev refer to the swapExactInput method for the scenarios\ function quoteExactInput(QuoteExactInputParams calldata params) external returns (uint256 outputAmount);\ }\ \ interface IV4Locker {\ /// @notice Collect V4/PCS Infinity LP fees for a token and distribute via TaxProcessor\ /// @dev Can be called by anyone (permissionless keeper). The Locker's collectAddress is\ /// set to Portal, so fees always flow through Portal → TaxProcessor → distribution.\ /// @param token The token whose LP fees to collect\ function collectV4Fees(address token) external;\ \ /// @notice Add liquidity to locked V4/PCS Infinity LP positions for a token.\ /// @dev Called by TaxProcessor to reinvest LP-share tax revenue.\ /// Tokens must be transferred to Portal before calling this function.\ /// Portal (as lock owner) calls increaseLiquidity on the GoPlus/UNCX locker\ /// for both the quote-only (lower) and token-only (upper) positions.\ /// @param token The protocol token address\ /// @param tokenAmount Amount of protocol token available for LP\ /// @param quoteAmount Amount of quote token available for LP\ /// @return actualTokenUsed Total protocol token consumed\ /// @return actualQuoteUsed Total quote token consumed\ function addV4LPLiquidity(address token, uint256 tokenAmount, uint256 quoteAmount)\ external\ returns (uint256 actualTokenUsed, uint256 actualQuoteUsed);\ }\ \ /// @title IPortalCore Interface\ /// @notice Combines IPortalLauncher and IPortalTrade\ interface IPortalCore is IPortalLauncher, IPortalTrade, IPortalTradeV2 {}\ \ /// @title IPortalMigrator Interface\ /// @notice Add liquidity from the bonding curve to DEX\ /// @dev this is not a public interface of the portal.\ /// All the functions of this interface are either called from the portal\ /// or from the UniswapV3Pool contract.\ interface IPortalMigrator {\ /// @notice Add liquidity to DEX\ /// @param token The address of the token\ /// @dev This is an internal function\ /// Any dispatch to this function should be checked in portal contract\ /// This function may be dellegated called from a payable function.\ function luanchToDEX(address token) external payable;\ }\ \ /// @title IRoller Interface\ /// @notice This acts as the glue between the portal and the flap staking contract\ interface IRoller {\ /// @notice The lock the token is using\ enum LockType {\ INVALID\_LOCK, // Invalid lock\ UNCX\_LOCK, // The UNCX lock\ GOPLUS\_UNIV3\_LOCK, // The Goplus UNIv3 lock\ TOSHI\_LP\_LOCK, // The Toshi LP lock\ IZI\_LP\_LOCK // The IziSwap LP locker\ }\ \ /// @notice get the locks by token address\ /// @param token The address of the token\ /// @return locks The lock ids of the token\ function getLocks(address token) external view returns (uint256\[\] memory locks);\ \ /// @dev deprecated\ function rollv2(bytes calldata packedParams) external;\ \ /// @notice Revenue Share: Claim LP fees for a vanity token\ /// @param token The address of the token\ /// @return tokenAmount The amount of the token claimed\ /// @return ethAmount The amount of ETH claimed\ /// @dev Only the beneficiary of the token can call this function.\ function claim(address token) external returns (uint256 tokenAmount, uint256 ethAmount);\ \ /// @notice Allows the default admin to change the beneficiary of a token\ /// @param token The address of the token\ /// @param newBeneficiary The new beneficiary address\ function setTokenBeneficiary(address token, address newBeneficiary) external;\ \ /// @notice Allows a roller or default admin to claim LP fees on behalf of the beneficiary\ /// @param token The address of the token\ /// @return tokenAmount The amount of the token claimed\ /// @return quoteAmount The amount of quote token (or ETH) claimed\ /// @dev Only the roller or default admin can call this function.\ /// The claimed fee will be sent to the beneficiary of the token.\ function delegateClaim(address token) external returns (uint256 tokenAmount, uint256 quoteAmount);\ }\ \ interface IPortalDexRouter {\ // @notice Update the DEX pool information for a token\ // @dev can only be called by DEX\_ROUTER\_MANAGER\_ROLE roles\ function updateTokenPoolInfo(address token, IPortalTypes.PackedDexPool calldata poolInfo) external;\ }\ \ /// @title IPortalTweak Interface\ /// @notice Handles admin-only configuration operations for the Portal\ interface IPortalTweak is IPortalTypes {\ /// @notice Parameters for updating tax token addresses\ struct TaxTokenAddressUpdate {\ /// @notice The address of the tax token to update\ address token;\ /// @notice The new beneficiary address for the tax splitter\ address beneficiary;\ /// @notice The new fee receiver address for the tax splitter\ address feeReceiver;\ }\ \ /// @notice Emitted when tax token addresses are updated\ /// @param token The address of the tax token\ /// @param beneficiary The new beneficiary address\ /// @param feeReceiver The new fee receiver address\ /// @dev Only Default ADMIN can change this\ event TaxTokenAddressesUpdated(address indexed token, address beneficiary, address feeReceiver);\ \ /// @notice Set the configuration for a quote token\ /// @dev Only callable by the default admin\ /// @param quoteToken The address of the quote token\ /// @param config The configuration struct for the quote token\ function setQuoteTokenConfiguration(address quoteToken, QuoteTokenConfiguration calldata config) external;\ \ /// @notice Set the fee exemption status for a list of traders\ /// @dev Only callable by the default admin\ /// @param traders The addresses of the traders to set exemption for\ /// @param isExempted Whether the traders should be exempted from fees\ function setFeeExemption(address\[\] memory traders, bool isExempted) external;\ \ /// @notice Get the current buy and sell fee rates\ /// @return buyFeeRate The current buy fee rate in basis points (e.g. 200 = 2%)\ /// @return sellFeeRate The current sell fee rate in basis points (e.g. 200 = 2%)\ function getFeeRate() external view returns (uint256 buyFeeRate, uint256 sellFeeRate);\ \ /// @notice Set the fee profile for a token\ /// @dev Only callable by DEFAULT\_ADMIN\_ROLE or TOKEN\_FLAP\_FEE\_SETTER\_ROLE\ /// @param token The address of the token\ /// @param feeProfile The fee profile to set\ function setFlapFeeProfile(address token, FlapFeeProfile feeProfile) external;\ \ /// @notice Update beneficiary and feeReceiver addresses for one or more tax tokens\ /// @dev Only callable by the default admin, TAX\_MANAGER\_ROLE, or TAX\_GUARDIAN\_ROLE\ /// @param updates Array of TaxTokenAddressUpdate structs containing token addresses and new addresses\ function updateTaxTokenAddresses(TaxTokenAddressUpdate\[\] calldata updates) external;\ \ /// @notice Register an extension for use with the portal\ /// @param extensionId The unique identifier for this extension\ /// @param extensionAddress The address of the extension contract\ /// @param version The version of the extension interface it implements (starting from 1)\ /// @dev Only callable by the default admin\ function registerExtension(bytes32 extensionId, address extensionAddress, uint8 version) external;\ \ /// @notice Block or unblock multiple addresses from creating tokens\ /// @param spammers The addresses to block or unblock\ /// @param blocked True to block, false to unblock\ /// @dev Only callable by the default admin or MODERATOR\_ROLE\ function setSpammerBlockedBatch(address\[\] calldata spammers, bool blocked) external;\ \ /// @notice Quickly pause only \`newTokenV7\` while leaving other global-switch entrypoints unaffected.\ /// @dev Callable by V7\_GUARDIAN\_ROLE or DEFAULT\_ADMIN\_ROLE.\ function haltNewTokenV7() external;\ \ /// @notice Emitted when stuck tax tokens are recovered from the tax splitter\ /// @param taxToken The address of the tax token\ /// @param amountSentToToken The amount of tokens sent back to the tax token contract\ /// @param amountReturnedToSplitter The amount of tokens returned to the tax splitter\ event StuckTaxTokenRecovered(address indexed taxToken, uint256 amountSentToToken, uint256 amountReturnedToSplitter);\ \ /// @notice Recover stuck tax tokens from the tax splitter and re-inject up to liquidationThreshold into the token\ /// @dev Only callable by AUDITOR\_ROLE. Currently supports V1 (TOKEN\_TAXED) tax tokens.\ /// The name is intentionally generic to allow extension to V2/V3 tax tokens in the future.\ /// @param taxToken The address of the V1 tax token to recover stuck tokens for\ function recoverStuckTaxToken(address taxToken) external;\ \ /// @notice Emitted when stuck tax tokens are burned (sent to the dead address)\ /// @param taxToken The address of the tax token\ /// @param amountBurned The amount of tokens sent to the dead address\ event StuckTaxTokenBurned(address indexed taxToken, uint256 amountBurned);\ \ /// @notice Burn stuck tax tokens by sweeping them from the tax splitter and sending to the dead address\ /// @dev Only callable by DEFAULT\_ADMIN\_ROLE. Supports V1 (TOKEN\_TAXED) tax tokens.\ /// @param taxToken The address of the V1 tax token to burn stuck tokens for\ function burnStuckTaxToken(address taxToken) external;\ \ /// @notice Emitted when marketAddress is updated for a V2/V3 tax token.\ event MarketWalletChanged(address indexed token, address indexed oldMarket, address indexed newMarket);\ \ /// @notice Changes the market wallet (TaxProcessor.marketAddress) for a TOKEN\_TAXED\_V2 or TOKEN\_TAXED\_V3 token.\ /// @dev Restricted to TAX\_GUARDIAN\_ROLE or DEFAULT\_ADMIN\_ROLE.\ /// @param token The tax token address.\ /// @param newMarketWallet The new market wallet address.\ function changeMarketWallet(address token, address newMarketWallet) external;\ }\ \ /// @title Portal Interface\ /// @notice This interface combines the core and game interfaces\ interface IPortal is\ IPortalCore,\ IAccessControlUpgradeable,\ IRoller,\ IV4Locker,\ IPortalDexRouter,\ IPortalTweak,\ IPortalLens,\ IPortalLensV2,\ IPortalLauncherTwoStep\ {\ /// @notice Get the version of the portal\ /// @return The version string\ function version() external view returns (string memory);\ \ /// @notice Check if tax on bonding curve is enabled\ /// @return enabled True if tax on bonding curve is enabled\ function enableTaxOnBondingCurve() external view returns (bool enabled);\ \ /// @notice Change the protocol bit flags\ /// @dev Can only be called with DEFAULT\_ADMIN\_ROLE\ /// @param flags The new flags\ function setBitFlags(uint256 flags) external;\ \ /// @notice Can only be called by the guardian role or the default admin role\ /// @dev This function is used to pause the protocol\ function halt() external;\ \ /// @notice Check if an address is blocked from creating tokens\ /// @param spammer The address to check\ /// @return True if the address is blocked\ function isSpammerBlocked(address spammer) external view returns (bool);\ \ /// @notice Send a message for a token\ /// @param token The address of the token\ /// @param message The message to send\ function sendMsg(address token, string memory message) external;\ \ /// @notice Get the current nonce of the portal\ function nonce() external view returns (uint256);\ }\ \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2.md). # Gift Vault V2 ## Overview Gift Vault V2 allows you to assign an X (Twitter) account as the \*\*Gift Owner\*\* for a token launched through \`VaultPortal\`. The gift owner can prove control of that X account and direct the vault's fee flow to any EVM address of their choice. The core mechanism is: 1. \*\*Assign Gift Owner\*\*: The X account you specify becomes the gift owner for the vault. 2. \*\*Accumulating Phase\*\*: After launch, the vault first stays in \`ACCUMULATING\` mode and holds incoming native BNB fees. 3. \*\*Flexible Beneficiary\*\*: Once the gift owner proves control, the vault enters \`STREAMING\` mode and routes fees to the chosen EVM address. 4. \*\*7-Day Grace Period\*\*: If the gift owner does not take over within the grace period, the gift flow is closed. 5. \*\*Fallback: Holder Dividends\*\*: After timeout, the vault enters \`DIVIDEND\` mode and routes value to token holders. Compared with the legacy \[Gift Vault (FlapXVault)\](/flap/developers/vault-developers/gift-vault.md), Gift Vault V2 mainly brings: \* two V2 product options \* a new vault lifecycle: \`ACCUMULATING -> STREAMING -> DIVIDEND\` \* new launch constraints for the V2 product line \* holder-dividend fallback instead of buyback & burn > Important: \`VaultPortal\` here is \*\*not\*\* the same contract as the Flap Bonding Curve Protocol \`Portal\`. ## Legacy Gift Vault vs Gift Vault V2 The old \`gift-vault.md\` document is for the legacy Gift Vault flow. This document is for the new Gift Vault V2 product line. For integrators, the practical differences are: | Topic | Gift Vault V1 | Gift Vault V2 | | ----------------------- | -------------------------- | ----------------------------------------------------------------------- | | Product options | one legacy Gift Vault flow | two product options: \`GiftV4VaultFactoryV2\` and \`GiftTaxVaultFactoryV2\` | | API integration | existing API | same API, request format, and response format as V1 | | Fee routing lifecycle | legacy gift flow | \`ACCUMULATING -> STREAMING -> DIVIDEND\` | | Timeout fallback | buyback & burn | holder dividends | | What you need to update | legacy integration | mainly factory selection and launch-parameter validation | So if you already support Gift Vault V1, the main work for V2 is not rewriting your API integration. The main work is choosing the correct V2 factory and respecting the V2 launch constraints. ## Network Details ### BNB Smart Chain Mainnet \* \`GiftV4VaultFactoryV2\`: \`0x6909aD1822Ece349CDDAb98E6F62EeeD9fAa2e10\` \* \`GiftTaxVaultFactoryV2\`: \`0xFb7ccc4Fd09Da5b7016A18d51e227Af4ABE53f44\` ### BNB Smart Chain Testnet \* \`GiftV4VaultFactoryV2\`: \`0xaa00b4CeFeE8b4BF7D2E23C21ae82cfC9AFC9D67\` \* \`GiftTaxVaultFactoryV2\`: \`0x2eDD880AB36b07bD030BDa28A13c3E9148C11622\` ## Product matrix Gift Vault V2 has two product options. The difference is mainly the launch constraints of the selected factory. ### \`GiftV4VaultFactoryV2\` Use this factory for the zero-tax Gift V4 product. Launch constraints: \* \`tokenVersion == TOKEN\_V3\_PERMIT\` \* \`quoteToken == address(0)\` \* \`buyTaxRate == 0\` \* \`sellTaxRate == 0\` \* \`dividendBps == 0\` \* \`dividendToken == address(0)\` ### \`GiftTaxVaultFactoryV2\` Use this factory for the taxed Gift product. Launch constraints: \* \`tokenVersion == TOKEN\_TAXED\_V3\` \* \`quoteToken == address(0)\` \* \`buyTaxRate == sellTaxRate\` \* \`buyTaxRate / sellTaxRate\` must both be one of: \* \`100\` (1%) \* \`200\` (2%) \* \`300\` (3%) \* \`dividendBps == 0\` \* \`dividendToken == address(0)\` ### Shared launch-time vault data For both Gift Vault V2 product options, the only launch-time vault parameter is the gift owner's X handle. ## Lifecycle Gift Vault V2 has three states: ### 1. \`ACCUMULATING\` \* initial state after deployment \* vault accepts native BNB fees from the token \* funds stay inside the vault \* waiting for a valid X proof to assign the streaming recipient ### 2. \`STREAMING\` \* entered after a valid \`manageByProof()\` call \* all accumulated BNB is flushed immediately to \`streamingTarget\` \* future BNB is forwarded to the same target on receipt \* the target can be updated again by a newer valid proof ### 3. \`DIVIDEND\` \* entered after \`createdAt + timeoutPeriod\` if streaming was never activated \* sticky once entered \* vault wraps native BNB into the dividend token (normally WBNB) \* vault deposits the wrapped balance into the token's Dividend contract for holders Default timeout for newly created vaults is \`7 days\`, configurable at the factory level for future vaults. ## API compatibility with Gift Vault V1 \`gift vault v1\` and \`gift vault v2\` use the same API interface. This means: \* the API endpoints are the same \* the request format is the same \* the response format is the same \* existing V1 API integration code can be reused for V2 directly So if you have already integrated the API in \[Gift Vault (FlapXVault)\](/flap/developers/vault-developers/gift-vault.md), you do not need to change your request / response parsing logic for V2. ## What is different in V2 For API integrators, the main V2 differences are: \* the factory addresses are different \* there are now two V2 product options \* launch constraints are different depending on which product you choose \* the vault lifecycle is now \`ACCUMULATING -> STREAMING -> DIVIDEND\` \* the fallback behavior is holder dividends instead of buyback & burn ## Integration notes When integrating Gift Vault V2, the main things you need to do are: 1. choose the correct Gift V2 factory address 2. launch through \`VaultPortal\` 3. pass the gift owner's \`xHandle\` in \`vaultData\` 4. keep using the same API request / response handling as V1 5. update your product selection logic to match the V2 launch constraints ### Launch data For Gift Vault V2, the only launch-time vault parameter is the gift owner's X handle. ### Product selection \* use \`GiftV4VaultFactoryV2\` for the zero-tax Gift V4 product \* use \`GiftTaxVaultFactoryV2\` for the taxed Gift product For \`GiftTaxVaultFactoryV2\`, buy tax and sell tax should use the same preset, and the supported presets are \`1%\`, \`2%\`, or \`3%\`. ## Summary If you already support Gift Vault V1 API integration, then Gift Vault V2 is mainly a product / factory upgrade, not an API upgrade. In most cases, you only need to: \* switch to the correct V2 factory address \* choose the correct V2 product type \* respect the new launch constraints \* keep the existing API request / response integration unchanged --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault.md). # Flap Tax Vault ## Table of Contents \* \[Overview\](#overview) \* \[VaultPortal\](#vaultportal) \* \[Deployed Addresses\](#deployed-addresses) \* \[Get Vault Info\](#get-vault-info) \* \[Get Audit Reports for A token\](#get-audit-reports-for-a-token) \* \[Launch A Token With A Vault\](#launch-a-token-with-a-vault) ## Overview When you use a smart contract as the "Funds Recipient Wallet", our system will try to parse it as a \`vault\`.
If you implement your smart contract following our Vault Specification, your Vault's information can be shown on our website.
Furthermore, if your vault is verified by our team or our auditing partners, your vault's "unverified" risk level can be updated to the corresponding risk level based on the audit results. And all audit reports will be uploaded to ipfs and available on our website for users to check. Users will be able to do their own research on the vaults before investing in your token. {% hint style="info" %} You can now create and deploy your own tax vaults \*\*without any permission or registration\*\*. Users can also use any vault they want when launching tokens. Vault factories no longer need to be registered in the VaultPortal before they can be used. See the \[Vault & VaultFactory specification\](/flap/developers/vault-developers/vault-and-vaultfactory-specification.md) page for full details on how to build your own vaults and vault factories. {% endhint %} {% hint style="success" %} If you are a developer, you can build a vault factory that deploys vaults following our Vault Specification. Once your vault factory is verified by our team or our auditing partners, all vaults deployed from your factory will have the same risk level as your factory. This way, you don't need to submit each vault for verification individually. Besides, you can charge a fee for each vault deployed from your factory to build your business upon Flap. Please refer to the \[Vault & VaultFactory specification\](/flap/developers/vault-developers/vault-and-vaultfactory-specification.md) page for more details. {% endhint %} ## VaultPortal The \`VaultPortal\` is a registry that manages vault factories and their deployed vaults. It provides a unified interface for interacting with vaults across different chains. ### Deployed Addresses | Chain | VaultPortal Address | | ----------- | ------------------------------------------ | | BNB mainnet | 0x90497450f2a706f1951b5bdda52B4E5d16f34C06 | | BNB testnet | 0x027e3704fC5C16522e9393d04C60A3ac5c0d775f | The ABI of the VaultPortal contract is as follows: {% file src="/files/GankgjlEVKt1RdWhSGQw" %} ### Get Vault Info We have two methods for fetching the vault info of a token: \* \`tryGetVault(address taxToken)\` : This method first tries to find the vault created through the VaultPortal. If not found, it will try to find the vault by searching all registered vault factories and manually verified vaults. \*\*You should use this method for most cases to avoid unnecessary reverts.\*\* \* \`getVault(address taxToken)\` : This method returns vault that created through the VaultPortal. \`\`\`solidity /// @notice Complete vault information returned to external callers /// @dev This struct is used for view functions and includes human-readable description /// @param vault The address of the vault contract /// @param vaultFactory The address of the vault factory that created this vault (zero address if unknown) /// @param description Human-readable description of the vault's purpose and functionality /// @param isOfficial Whether this vault is marked as official/endorsed by the protocol /// @param riskLevel The risk level classification from audit struct VaultInfo { address vault; address vaultFactory; string description; bool isOfficial; RiskLevel riskLevel; } /// @notice Get the complete vault information for a tax token /// @dev Reverts if no vault is found for the given tax token /// @dev Attempts to fetch the vault's description by calling its description() function /// @param taxToken The address of the tax token /// @return info The VaultInfo struct containing complete vault details function getVault(address taxToken) external view returns (VaultInfo memory info); /// @notice Attempt to get vault information for a tax token without reverting /// @dev First checks the internal mapping, then falls back to searching the Portal /// @dev For fallback results, isOfficial and isVerified will always be false /// @param taxToken The address of the tax token /// @return found Whether a vault was found for this tax token /// @return info The VaultInfo struct (empty if not found) function tryGetVault(address taxToken) external view returns (bool found, VaultInfo memory info); \`\`\` ### Get Audit Reports for A token For each token, you can get its audit reports with pagination support using the getAuditReports method. If the token itself has no audit reports, it will fall back to its vault's factory's audit reports. \`\`\`solidity /// @notice Audit report for a tax token /// @param auditor The address of the auditor who submitted this report /// @param riskLevel The risk level classification from this audit /// @param ipfsCid The IPFS CID of the audit report document struct AuditReport { address auditor; RiskLevel riskLevel; string ipfsCid; } /// @notice Get recent audit reports for a tax token with pagination /// @dev Returns reports starting from the most recent (end of array) /// @dev If the token has no audit reports, falls back to its factory's audit reports /// @param taxToken The address of the tax token /// @param offset The number of reports to skip from the end (0 = most recent) /// @param limit The maximum number of reports to return /// @return reports The array of audit reports /// @return total The total number of audit reports for this token function getAuditReports(address taxToken, uint256 offset, uint256 limit) external view returns (AuditReport\[\] memory reports, uint256 total); \`\`\` ### Launch A Token With A Vault How to launch a token using a vault? Vault is a system built outside of the main Flap Bonding Curve Protocol. To launch a token with a vault, you need to use the \`VaultPortal\` contract: \`\`\`solidity pragma solidity ^0.8.13; import {IPortalTypes} from "./IPortal.sol"; /// @title IVaultPortalTypes /// @notice Type definitions for the VaultPortal contract /// @dev Contains all structs and custom types used by VaultPortal interface IVaultPortalTypes { /// @notice Parameters required to create a new tax token with an associated vault /// @dev All parameters are passed as a single struct to avoid stack-too-deep errors /// @param name The name of the tax token (e.g., "MyToken") /// @param symbol The symbol of the tax token (e.g., "MTK") /// @param meta Metadata URI or string for additional token information /// @param dexThresh The DEX supply threshold type /// @param salt A unique salt for deterministic address generation (must produce vanity suffix) /// @param taxRate The tax rate in basis points (e.g., 100 = 1%, max 1000 = 10%) /// @param migratorType The migrator type (see MigratorType enum) /// @param quoteToken The token used for initial liquidity (address(0) for native token) /// @param quoteAmt The amount of quote token to provide as initial liquidity /// @param permitData The optional permit data for the quote token /// @param extensionID The ID of the extension to be used for the new token if not zero /// @param extensionData Additional extension specific data /// @param dexId The preferred DEX ID for the token /// @param lpFeeProfile The preferred V3 LP fee profile for the token /// @param taxDuration Tax duration in seconds (max: 100 years) /// @param antiFarmerDuration Anti-farmer duration in seconds (max: 1 year) /// @param mktBps Market allocation basis points (to beneficiary) /// @param deflationBps Deflation basis points (burned) /// @param dividendBps Dividend basis points (to dividend contract) /// @param lpBps Liquidity provision basis points (LP to dead address) /// @param minimumShareBalance Minimum balance for dividend eligibility /// @param vaultFactory The address of the vault factory to use for creating the vault /// @param vaultData Encoded data specific to the vault type being created struct NewTaxTokenWithVaultParams { string name; string symbol; string meta; IPortalTypes.DexThreshType dexThresh; bytes32 salt; uint16 taxRate; IPortalTypes.MigratorType migratorType; address quoteToken; uint256 quoteAmt; bytes permitData; bytes32 extensionID; bytes extensionData; IPortalTypes.DEXId dexId; IPortalTypes.V3LPFeeProfile lpFeeProfile; uint64 taxDuration; uint64 antiFarmerDuration; uint16 mktBps; uint16 deflationBps; uint16 dividendBps; uint16 lpBps; uint256 minimumShareBalance; address vaultFactory; bytes vaultData; } } \`\`\` The parameters are similar to the \`newTokenV5\` of the \`Portal\` contract, with two additional parameters at the end: \`vaultFactory\` and \`vaultData\`. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/trade-tokens.md). # Trade Tokens ## Get A Quote {% hint style="warning" %} If a token is already migrated, you can still get a quote using the following method. However, we will always use the pool that the token has migrated to, this may not be the best quote.\\ \\ You can also do an off-chain quote to save the RPC calls. Check the end of this document to learn more about the off-chain quote. {% endhint %} To get a quote , we call the \`quoteExactInput\` function: \`\`\`solidity /// @notice Parameters for quoting the output amount for a given input struct QuoteExactInputParams { /// @notice The address of the input token (use address(0) for native asset) address inputToken; /// @notice The address of the output token (use address(0) for native asset) address outputToken; /// @notice The amount of input token to swap (in input token decimals) uint256 inputAmount; } /// @notice Quote the output amount for a given input /// @param params The quote parameters /// @return outputAmount The quoted output amount /// @dev refer to the swapExactInput method for the scenarios function quoteExactInput(QuoteExactInputParams calldata params) external returns (uint256 outputAmount); \`\`\` Note: the \`quoteExactInput\` method is not a view function, but we don’t need to send a transaction to get the quote (an \`eth\_call\` or the simulation in viem will do the work) . Here are the possible scenarios: \* \*\*When the quote token is the native gas token (BNB or ETH):\*\* \* buy a token: \* \`inputToken\` is zero address, representing the gas token \* \`outputToken\` is the token you wanna buy \* sell a token: \* \`inputToken\` is the token you wanna sell \* \`outputToken\` is zero address, representing the gas token \* \*\*When the quote token is not the native gas token (taking USD1 as an example)\*\* : \* buy a token with USD1: \* \`inputToken\` is USD1 address \* \`outputToken\` is the token you wanna buy \* \*\*buy with native gas token\*\* (only when the token’s \`nativeToQuoteSwap\` is enabled , check below to inspect if a token’s quote token supports \`nativeToQuoteSwap\` ): \* \`inputToken\` is zero address, representing the gas token \* \`outputToken\` is the token you wanna buy \* under the hood, we will help you swap BNB for for USD1 on PancakeSwap as an intermediate step in the contract. \* sell a token: \* \`inputToken\` is the token you wanna sell \* \`outputToken\` is USD1 \* Sell directly from token to BNB is also supported, but we will not support this in our UI. ## Swap To swap we call the \`swapExactInput\` method: \`\`\`solidity /// @notice Parameters for swapping exact input amount for output token struct ExactInputParams { /// @notice The address of the input token (use address(0) for native asset) address inputToken; /// @notice The address of the output token (use address(0) for native asset) address outputToken; /// @notice The amount of input token to swap (in input token decimals) uint256 inputAmount; /// @notice The minimum amount of output token to receive uint256 minOutputAmount; /// @notice Optional permit data for the input token (can be empty) bytes permitData; } /// @notice Swap exact input amount for output token /// @param params The swap parameters /// @return outputAmount The amount of output token received /// @dev Here are some possible scenarios: /// If the token's reserve is BNB or ETH (i.e: the quote token is the native gas token): /// - BUY: input token is address(0), output token is the token address /// - SELL: input token is the token address, output token is address(0) /// If the token's reserve is another ERC20 token (eg. USD\*, i.e, the quote token is an ERC20 token): /// - BUY with USD\*: input token is the USD\* address, output token is the token address /// - SELL for USD\*: input token is the token address, output token is the USD\* address /// - BUY with BNB or ETH: input token is address(0), output token is the token address. /// (Note: this requires an internal swap to convert BNB/ETH to USD\*, nativeToQuoteSwap must be anabled for this quote token) /// Note: Currently, this method only supports trading tokens that are still in the bonding curve state. /// However, in the future, we may also support trading tokens that are already in DEX state. function swapExactInput(ExactInputParams calldata params) external payable returns (uint256 outputAmount); \`\`\` This is quite straightforward after getting a quote. #### How to construct the permitData in ExactInputParams ? When selling the tokens, you can use our permitData field to save an approve transaction. The permitData field is the abi encoding of each field from the Permit struct plus the EIP712 signature. Check the \`genPermitData\` below : \`\`\`typescript import { Account, Address, createWalletClient, encodeAbiParameters, encodeDeployData, encodeFunctionData, formatEther, getContract, getContractAddress, HDAccount, Hex, hexToBigInt, http, keccak256, parseEther, parseGwei, parseSignature, parseTransaction, parseUnits, PublicClient, serializeTransaction, toBytes, toHex } from 'viem'; // generate ERC20 permit data for token (returns all fields, not abi encoded) async function genPermitDataNoPack(account: Account, token: \`0x${string}\`, spender: \`0x${string}\`, amount: bigint, client: PublicClient) { const tokenInst = getContract({ abi: \[\ {\ type: "function",\ name: "name",\ inputs: \[\],\ outputs: \[{ name: "", type: "string" }\],\ stateMutability: "view"\ },\ {\ type: "function",\ name: "nonces",\ inputs: \[{ name: "", type: "address" }\],\ outputs: \[{ name: "", type: "uint256" }\],\ stateMutability: "view"\ },\ {\ type: "function",\ name: "permit",\ inputs: \[\ { name: "owner", type: "address" },\ { name: "spender", type: "address" },\ { name: "value", type: "uint256" },\ { name: "deadline", type: "uint256" },\ { name: "v", type: "uint8" },\ { name: "r", type: "bytes32" },\ { name: "s", type: "bytes32" }\ \],\ outputs: \[\],\ stateMutability: "nonpayable"\ }\ \], address: token, client, }); const nonce = await tokenInst.read.nonces(\[account.address\]) as bigint; const name = await tokenInst.read.name() as string; const deadline = BigInt(Date.now() + 10 \* 60 \* 1000); // 10 minutes ttl const sig = await (account as HDAccount).signTypedData({ domain: { name, version: "1", chainId: bsc.id, // or bsc.id for mainnet verifyingContract: token as \`0x${string}\` }, types: { Permit: \[\ { name: "owner", type: "address" },\ { name: "spender", type: "address" },\ { name: "value", type: "uint256" },\ { name: "nonce", type: "uint256" },\ { name: "deadline", type: "uint256" }\ \] }, primaryType: "Permit", message: { owner: account.address, spender: spender, value: amount, nonce, deadline, } }); const { r, s, v } = parseSignature(sig) as { r: \`0x${string}\`, s: \`0x${string}\`, v: bigint, yParity: number }; return { owner: account.address, spender, value: amount, nonce, deadline, v: Number(v), r, s }; } // generate ERC20 permit data for token (returns abi encoded data) async function genPermitData(account: Account, token: \`0x${string}\`, amount: bigint, client: PublicClient) { const permitFields = await genPermitDataNoPack(account, token, flapConfig.portal, amount, client); const data = encodeAbiParameters(\[\ { name: "owner", type: "address" },\ { name: "spender", type: "address" },\ { name: "value", type: "uint256" },\ { name: "deadline", type: "uint256" },\ { name: "v", type: "uint8" },\ { name: "r", type: "bytes32" },\ { name: "s", type: "bytes32" }\ \], \[permitFields.owner, permitFields.spender, permitFields.value, permitFields.deadline, permitFields.v, permitFields.r, permitFields.s\]); return data; } \`\`\` ## Events ### \`TokenBought\` Emitted when a user buys tokens through the Portal. \`\`\`solidity event TokenBought( uint256 ts, address token, address buyer, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice ); \`\`\` \*\*Parameters:\*\* \* \`ts\`: Timestamp of the trade. \* \`token\`: Address of the token bought. \* \`buyer\`: Address of the buyer. \* \`amount\`: Amount of tokens bought. \* \`eth\`: Amount of ETH (or quote token) spent. \* \`fee\`: Amount of ETH (or quote token) spent as a fee. \* \`postPrice\`: Price of the token after this trade. \*\*When emitted:\*\*\\ Whenever a user successfully buys tokens via the Portal. ### \`TokenSold\` Emitted when a user sells tokens through the Portal. \`\`\`solidity event TokenSold( uint256 ts, address token, address seller, uint256 amount, uint256 eth, uint256 fee, uint256 postPrice ); \`\`\` \*\*Parameters:\*\* \* \`ts\`: Timestamp of the trade. \* \`token\`: Address of the token sold. \* \`seller\`: Address of the seller. \* \`amount\`: Amount of tokens sold. \* \`eth\`: Amount of ETH (or quote token) received. \* \`fee\`: Amount of ETH (or quote token) deducted as a fee. \* \`postPrice\`: Price of the token after this trade. \*\*When emitted:\*\*\\ Whenever a user successfully sells tokens via the Portal. ### \`LaunchedToDEX\` Emitted when a token finishes its bonding-curve phase and is launched to the target DEX. \`\`\`solidity event LaunchedToDEX(address token, address pool, uint256 amount, uint256 eth); \`\`\` \*\*Parameters:\*\* \* \`token\`: Address of the token being migrated. \* \`pool\`: Compatibility field for the DEX target. \* \`amount\`: Amount of token liquidity added. \* \`eth\`: Amount of quote-token liquidity added. {% hint style="info" %} For classic \`V2\_MIGRATOR\` and \`V3\_MIGRATOR\` launches, \`pool\` is the actual pool address. For \`PCS\_INFINITY\_CL\_MIGRATOR\`, \`pool\` is the PancakeSwap Infinity vault address. For \`V4\_UNI\_MIGRATOR\`, \`pool\` is the Uniswap PoolManager address. This is kept mainly for compatibility with older indexers. For these two CL-style migrators, the actual pool identifier should be read from \`FlapTokenCLPoolCreated\`. {% endhint %} ### \`FlapTokenCLPoolCreated\` This event is only relevant for the concentrated-liquidity launch paths: \*\*PancakeSwap Infinity CL\*\* and \*\*Uniswap v4\*\*. \`\`\`solidity /// @notice emitted when a new CL pool is created for a token /// @param token The address of the token /// @param poolId The ID of the created CL pool /// @param sqrtPriceX96 The initial sqrt price used to initialize the CL pool event FlapTokenCLPoolCreated(address token, bytes32 poolId, uint160 sqrtPriceX96); \`\`\` \*\*Parameters:\*\* \* \`token\`: Address of the launched token. \* \`poolId\`: The CL pool ID. For \`PCS\_INFINITY\_CL\_MIGRATOR\` and \`V4\_UNI\_MIGRATOR\`, this is the value you should index and store as the real pool identifier. \* \`sqrtPriceX96\`: The initial sqrt price used to initialize the CL pool. \*\*When emitted:\*\*\\ Emitted when the token is launched through \`PCS\_INFINITY\_CL\_MIGRATOR\` or \`V4\_UNI\_MIGRATOR\` and the CL pool is initialized. ### FlapTokenProgressChanged {% hint style="warning" %} This event is available since v4.12.1 {% endhint %} Whenever a token's progress changes, the \`FlapTokenProgressChanged\` event will be emitted. Note that the \`newProgress\` is in \`wad\` (i.e, with 18 decimals, 1 ether = 100%). \`\`\`solidity /// @notice emitted when the progress of a token changes /// @param token The address of the token /// @param newProgress The new progress value in Wad event FlapTokenProgressChanged(address token, uint256 newProgress); \`\`\` ## How To Do An Off-Chain Quote? ### \\\[Off-Chain Quote\] 1. The Preliminary The preliminary steps to quote off-chain is to get the info of a token first. You can either use the methods provided by our smart contract (i.e: \[getTokenV6 or getTokenV7\](https://github.com/flap-sh/gitbook/blob/main/developers/wallet-and-terminal-and-bot-developers/inspect-a-token/README.md) ), or you can build your indexer by indexing the following events: \* \`TokenCreated\`: This event gives us the basic info of the token \* \`TokenCurveSet\` and \`TokenCurveSetV2\`: We use these events to determine our bonding curve parameters. \* \`TokenDexSupplyThreshSet\`: this event gives us the circulating supply threshold for the token be migrated. \* \`FlapTokenCirculatingSupplyChanged\`: This event gives us the current circulating supply of the token. \* \`LaunchedToDEX\` : This event indicates that the token has been launched to a decentralized exchange (DEX), and the bonding curve equation ceases to be valid. For \`PCS\_INFINITY\_CL\_MIGRATOR\` and \`V4\_UNI\_MIGRATOR\`, note that the \`pool\` field is a compatibility address (Infinity vault / PoolManager) rather than the final CL pool ID. \* \`FlapTokenTaxSet\` : (optional), if a token does not emit this event, the tax is 0 or it is not a tax token. \* \`FlapTokenProgressChanged\` : emitted when the bonding curve progress of a token changes \* \`FlapTokenCLPoolCreated\` : For \`PCS\_INFINITY\_CL\_MIGRATOR\` and \`V4\_UNI\_MIGRATOR\`, use this event's \`poolId\` as the real CL pool identifier. ### \\\[Off-Chain Quote\] 2. The Curve Library With the bonding curve parameters (either a single \`r\` from \`TokenCurveSet\` or the full set (\`r\`, \`h\`, \`k\`) from \`TokenCurveSetV2\`), you can construct a curve instance. Here are the reference typescript and solidity code. You can always feed the code to your AI (Claude or others), and ask them to help you to convert to your preferred programming language: \* The legacy curve is a special case of the new curve where h = 0. For constructing a legacy curve, we can pass only \`r\` as the parameter. \* For latest curve, we should pass all the parameters (\`r\`, \`h\`, \`k\`). \`\`\`tsx import { Decimal } from "decimal.js"; const BILLION: Decimal = new Decimal("1000000000"); // The latest curve is CDPV2 export class CDPV2 { // the initial virtual reserve private r: number; private h: number; private k: number; static defaultDexSupplyThreshold() { return new Decimal(8e8); } static getCurve(r: number, h?: number, k?: number): CDPV2 { if (h == null) { return new CDPV2(r, 0, 1e9 \* r); } return new CDPV2(r, h, k); } constructor(r: number, h: number = 0, k: number = 0) { this.r = r; this.h = h; this.k = k; } estimateSupply(reserve: string): Decimal { // s = 1e9 + h - k/(r + eth) if (!reserve) return new Decimal(0); return new Decimal(BILLION).add(this.h).sub( new Decimal(this.k).div(new Decimal(reserve).add(this.r)) ); } estimateReserve(amount: string): Decimal { // eth = k/(h + 1e9 - s) - r if (!amount) return new Decimal(0); return new Decimal(this.k) .div(new Decimal(BILLION).add(this.h).sub(new Decimal(amount))) .sub(this.r); } mc(reserve: string): Decimal { return this.fdv(this.totalSupply(reserve).toString()); } price(supply: string): Decimal { // Price: k/(h + 1e9 - s)^2 const denominator = new Decimal(BILLION).add(this.h).sub(new Decimal(supply || 0)); return new Decimal(this.k).div(denominator.pow(2)); } fdv(supply: string): Decimal { return this.price(supply).mul(new Decimal(BILLION)); } } \`\`\` The solidity version of the curve lib: \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol"; /// @title constant product bonding curve /// @author The Flap Team /// @dev v2 /// /// Spec: /// - max supply: 1 Billion tokens /// - The constant product equation is : /// (1e9 + h - s) \* (eth + r) = k /// - s is the current circulating supply of the token /// - eth is the current eth reserve /// - special case: When h = 0, k = r \* 1e9 /// - estimateSupply(estimate s given eth): s = 1e9 + h - k/(r + eth) /// - estimateReserve(estimate eth given s): eth = k/(h + 1e9 - s) - r /// - price estimation: k/(h + 1e9 - s)\*\*2 library LibCurve { /// @notice The curve type is represented by a struct /// refer to Uniswap V3 white paper. struct Curve { uint256 r; // Virtual ETH reserve uint256 h; // Virtual token reserve uint256 k; // The square of the virtual Liquidity } // The total supply of the token uint256 public constant TOTAL\_SUPPLY = 1\_000\_000\_000 ether; // custom error type /// @notice error if the new supply is greater than the total supply error SupplyExceedsTotalSupply(uint256 newSupply); /// @notice error if reserve is greater than the max reserve error ReserveExceedsMaxReserve(uint256 reserve); // @notice Return the estimate supply given the reserve amount /// @param reserve The reserve amount /// @dev The resulting supply is rounded down and may even subtract small amount /// /// This function is used when a user wants to buy tokens, /// a rounded down value is more favorable to the protocol. function estimateSupply(Curve memory curve, uint256 reserve) internal pure returns (uint256 supply) { // s = 1e9 + h - k/(r + eth) // Round down for protocol safety when buying supply = TOTAL\_SUPPLY + curve.h - FixedPointMathLib.divWadUp(curve.k, curve.r + reserve); } /// @notice estimate the reserve given the supply /// @dev This function returns a roundup value, because we want the following invariant to hold: /// currReserve >= estimateReserve\_without\_roudup(currSupply) /// /// This function is used when a user wants to sell tokens, a rounded up value /// is more favorable to the protocol. function estimateReserve(Curve memory curve, uint256 supply) internal pure returns (uint256 reserve) { if (supply > TOTAL\_SUPPLY) { revert SupplyExceedsTotalSupply(supply); } // eth = k/(h + 1e9 - s) - r // Round up for protocol safety when selling reserve = FixedPointMathLib.divWadUp(curve.k, TOTAL\_SUPPLY + curve.h - supply) - curve.r; } /// @notice price (wei) of a token (1e18) if you buy/sell inifinitesimal amount at current supply function price(Curve memory curve, uint256 supply) internal pure returns (uint256) { // Price: k/(h + 1e9 - s)^2 uint256 denominator = TOTAL\_SUPPLY + curve.h - supply; if (denominator < 1e9 + 1) { return type(uint256).max; } // Calculate (h + 1e9 - s)^2 using mulWad for precision uint256 denominator\_squared = FixedPointMathLib.mulWad(denominator, denominator); return FixedPointMathLib.divWad(curve.k, denominator\_squared); } // helper from r to curve function fromR(uint256 r) internal pure returns (Curve memory) { // legacy curve: h = 0 and k = r \* TOTAL\_SUPPLY return Curve({r: r, h: 0, k: FixedPointMathLib.mulWad(r, TOTAL\_SUPPLY)}); } // helper from r,h,k to curve function fromRHK(uint256 r, uint256 h, uint256 k) internal pure returns (Curve memory) { return Curve({r: r, h: h, k: k}); } /// @notice Return the estimate supply given the reserve amount with support for different reserve token decimals /// @param curve The curve parameters /// @param reserve The reserve amount /// @param reserveDecimals The number of decimals of the reserve token (must be <= 18) /// @dev The resulting supply is rounded down, same as estimateSupply /// This function is used when a user wants to buy tokens, /// a rounded down value is more favorable to the protocol. function estimateSupplyV2(Curve memory curve, uint256 reserve, uint8 reserveDecimals) internal pure returns (uint256 supply) { if (reserveDecimals > 18) { revert("Reserve decimals must be <= 18"); } if (reserveDecimals == 18) { // If reserve token has 18 decimals, use the original estimateSupply function return estimateSupply(curve, reserve); } // Adjust the reserve to 18 decimals uint256 scaleFactor = 10 \*\* (18 - reserveDecimals); uint256 scaledReserve = reserve \* scaleFactor; // Use the adjusted reserve amount supply = TOTAL\_SUPPLY + curve.h - FixedPointMathLib.divWadUp(curve.k, curve.r + scaledReserve); } /// @notice Estimate the reserve given the supply with support for different reserve token decimals /// @param curve The curve parameters /// @param supply The token supply /// @param reserveDecimals The number of decimals of the reserve token (must be <= 18) /// @dev This function returns a roundup value, same as estimateReserve /// This function is used when a user wants to sell tokens, a rounded up value /// is more favorable to the protocol. function estimateReserveV2(Curve memory curve, uint256 supply, uint8 reserveDecimals) internal pure returns (uint256 reserve) { if (reserveDecimals > 18) { revert("Reserve decimals must be <= 18"); } if (supply > TOTAL\_SUPPLY) { revert SupplyExceedsTotalSupply(supply); } if (reserveDecimals == 18) { // If reserve token has 18 decimals, use the original estimateReserve function return estimateReserve(curve, supply); } // Calculate the reserve in 18 decimals uint256 scaledReserve = FixedPointMathLib.divWadUp(curve.k, TOTAL\_SUPPLY + curve.h - supply) - curve.r; // Convert the reserve from 18 decimals to the actual reserve decimals uint256 scaleFactor = 10 \*\* (18 - reserveDecimals); // Divide scaled reserve by scaleFactor (rounding up) // For rounding up division: (a + b - 1) / b reserve = (scaledReserve + scaleFactor - 1) / scaleFactor; } } \`\`\` Note that the curve has the following methods: \* \`estimateSupply(reserve: string)\`: Estimates the token circulating supply given the reserve amount. \* \`estimateReserve(amount: string)\`: Estimates the reserve amount given the token’s circulating supply. ### \\\[Off-Chain Quote\] 3. Quote With The Curve Instance We will use the typescript pseudo code to demonstrate how to quote with the curve instance. \*\*Case1: Buy 1 BNB Value of Token\*\* \`\`\`tsx //// Values From your Indexer or getTokenV6/V7 // current circulating supply // This can be obtained from getTokenV6 or the FlapTokenCirculatingSupplyChanged event. let curr\_circulating\_supply; // The circulating supply threshold for the token to be migrated to DEX // Typicially, this is 800M // This can be obtained from getTokenV6 or the TokenDexSupplyThreshSet event. let dex\_supply\_threshold = 800\_000\_000; // The input bnb amount let input\_bnb = 1; //// Compute the Quote // We cannot buy more than the dex\_supply\_threshold on the bonding curve // The max\_reserve is the cap of the reserve let max\_reserve = curve.estimateReserve(dex\_supply\_threshold); // We estimate the current reserve from the circulating supply let curr\_reserve = curve.estimateReserve(curr\_circulating\_supply); // protocol fee on BNB chain is 1% let fee = 0.01; // 1% fee // Charge a 1% fee on the input bnb. let input\_after\_fee = input\_bnb \* (0.99); // If the user pays more than required, we will automatically refund the user let new\_reserve = curr\_reserve + min(input\_after\_fee, max\_reserve - curr\_reserve); // estimate new circulating supply from new reserve let new\_circulating\_supply = curve.estimateSupply(new\_reserve); // The diff of circulating supply is the output token amount let output\_amt = new\_circulating\_supply - curr\_circulating\_supply; \`\`\` \*\*Case 2: Sell 1M token for bnb\*\* \`\`\`tsx //// From Indexer Or getTokenV6/V7 // current circulating supply let curr\_circulating\_supply; // The circulating supply threshold for the token to be migrated to DEX // Typicially, this is 800M // This can be obtained from getTokenV6/V7 or the TokenDexSupplyThreshSet event. let dex\_supply\_threshold; // BNB fee let fee = 0.01; // 1% fee // The input token amount let input\_token\_amt = 1\_000\_000; // estimate the current reserve let curr\_reserve = curve.estimateReserve(curr\_circulating\_supply); // estimate the new resereve let new\_reserve = curve.estimateReserve(curr\_circulating\_supply - input\_token\_amt); // The diff is the output amount without deducting the fee let output\_eth\_before\_fee = curr\_reserve - new\_reserve; // we take 1% fee from the quote token let output\_eth = output\_eth\_before\_fee \* (0.99); \`\`\` ### \\\[Off-Chain Quote\] 4. Quote for tax tokens If a token has tax, we also charge the tax on the bonding curve. For off-chain quotes, this means you should \*\*add the tax rate to the protocol fee\*\*. \* Effective fee = protocol fee (1%) + token tax rate \* Tax rate can be read from \`getTokenV6\` / \`getTokenV7\`, or indexed from the \`FlapTokenTaxSet\` event Below are the same examples as above, updated for tax tokens. You can also read the dedicated tax guide at \[developers/basic-and-mechanism/flap-tax-token/prebond-tax.md\](/flap/developers/basic-and-mechanism/flap-tax-token/prebond-tax.md). \*\*Case 1: Buy 1 BNB value of token (tax token)\*\* \`\`\`tsx //// Values From your Indexer or getTokenV6/V7 // current circulating supply // This can be obtained from getTokenV6 or the FlapTokenCirculatingSupplyChanged event. let curr\_circulating\_supply; // The circulating supply threshold for the token to be migrated to DEX // Typicially, this is 800M // This can be obtained from getTokenV6 or the TokenDexSupplyThreshSet event. let dex\_supply\_threshold = 800\_000\_000; // The token's tax rate // This can be obtained from getTokenV6 or the FlapTokenTaxSet event from the Portal let tax = 0; // The input bnb amount let input\_bnb = 1; //// Compute the Quote // We cannot buy more than the dex\_supply\_threshold on the bonding curve // The max\_reserve is the cap of the reserve let max\_reserve = curve.estimateReserve(dex\_supply\_threshold); // We estimate the current reserve from the circulating supply let curr\_reserve = curve.estimateReserve(curr\_circulating\_supply); // Effective fee = 1% protocol fee + tax let fee = 0.01 + tax; // Charge the fee on the input bnb. let input\_after\_fee = input\_bnb \* (1 - fee); // If the user pays more than required, we will automatically refund the user let new\_reserve = curr\_reserve + min(input\_after\_fee, max\_reserve - curr\_reserve); // estimate new circulating supply from new reserve let new\_circulating\_supply = curve.estimateSupply(new\_reserve); // The diff of circulating supply is the output token amount let output\_amt = new\_circulating\_supply - curr\_circulating\_supply; \`\`\` \*\*Case 2: Sell 1M token for BNB (tax token)\*\* \`\`\`tsx //// From Indexer Or getTokenV6/V7 // current circulating supply let curr\_circulating\_supply; // The circulating supply threshold for the token to be migrated to DEX // Typicially, this is 800M // This can be obtained from getTokenV6/V7 or the TokenDexSupplyThreshSet event. let dex\_supply\_threshold; // The token's tax rate // This can be obtained from getTokenV6 or the FlapTokenTaxSet event from the Portal let tax = 0.03; // BNB fee // Effective fee: 1% protocol + tax let fee = 0.01 + tax; // The input token amount let input\_token\_amt = 1\_000\_000; // estimate the current reserve let curr\_reserve = curve.estimateReserve(curr\_circulating\_supply); // estimate the new reserve let new\_reserve = curve.estimateReserve(curr\_circulating\_supply - input\_token\_amt); // The diff is the output amount without deducting the fee let output\_eth\_before\_fee = curr\_reserve - new\_reserve; // apply the fee on the quote token let output\_eth = output\_eth\_before\_fee \* (1 - fee); \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/preview/flap-candy-box.md). # Flap Candy Box ## Overview Flap Candy Box is an on-chain data oracle that delivers paginated, addressable lists to consumer contracts via a request-response callback pattern. It serves as an infrastructure layer for programmable, transparent, and automated reward distribution — vaults and other smart contracts can access dynamic CandyBox lists on-chain and act on them automatically, without maintaining the lists themselves. Each subscription ID represents a list — it could be a static snapshot, a dynamically refreshed dataset, or any curated set of addresses. The first subscription is provided by Flap and refreshed every 24 hours, featuring the most active users from the previous day's trading activity on Flap. More subscriptions will be added over time. > Want to build your own custom list? \[Reach out to us\](https://flap.sh) — we'd love to explore it with you. \*\*Deployed addresses:\*\* | Network | Address | Max Callback Gas | | ----------- | -------------------------------------------- | ---------------- | | BSC Mainnet | \`0x6255fbd731272a517022e99f6CaCf6A5De9414Ee\` | 2,000,000 | | BSC Testnet | \`0x8e6C16Bf07022a7Da9398B543f38846E9355Bd70\` | 2,000,000 | \*\*Max Callback Gas:\*\* The maximum gas limit supported for an \`onDataReceived\` callback. ### How it works \`\`\`mermaid sequenceDiagram participant C as Consumer Contract participant O as FlapCandyBox participant B as Backend (off-chain) C->>O: requestData{value: fee}(subscriptionId, timestamp, offset, limit, "") O-->>B: emit FlapDataRequested(subscriptionId, requestId, ...) Note over B: resolves the list for this subscriptionId B->>O: fulfillData(requestId, encodedData) O->>C: onDataReceived(subscriptionId, requestId, data) callback O-->>B: emit FlapDataFulfilled(subscriptionId, requestId, success, ...) \`\`\` \*\*Step by step:\*\* 1. The consumer contract calls \`requestData()\`, paying the subscription fee and specifying the page (\`offset\`, \`limit\`) to fetch. 2. The oracle records the request and emits \`FlapDataRequested\`. 3. The backend queries the previous day's trade data and encodes it as a \`ResponseData\` struct. 4. The backend calls \`fulfillData(requestId, encodedData)\` on the oracle. 5. The oracle forwards the data to the consumer via \`onDataReceived()\` with a bounded gas limit. 6. The oracle emits \`FlapDataFulfilled\` with the callback outcome. ### Request status lifecycle \`\`\` PENDING → FULFILLED (callback succeeded) → FAILED (callback reverted — eligible for retry by operator) \`\`\` If the consumer's \`onDataReceived()\` reverts, the request status is set to \`FAILED\`. The oracle operator can retry delivery via \`retryFulfillData()\` once the consumer issue is resolved. \*\*\* ## Subscriptions Each subscription defines a dataset, its pricing, and the maximum page size. \`\`\`solidity IFlapCandyBox.Subscription\[\] memory subs = IFlapCandyBox(oracleAddress).getSubscriptions(); \`\`\` | Field | Type | Description | | ---------------- | --------- | ----------------------------------------------------- | | \`subscriptionId\` | \`bytes32\` | Pass to \`requestData()\` | | \`description\` | \`string\` | Human-readable rule | | \`responseStruct\` | \`string\` | Struct name for decoding \`data\` in \`onDataReceived()\` | | \`fee\` | \`uint256\` | Required \`msg.value\` per request (wei) | | \`maxLimit\` | \`uint32\` | Maximum records per page | > \*\*Current limit cap:\*\* \`maxLimit\` is \*\*145\*\* per request. This accounts for consumer contracts that store the returned records on-chain — larger payloads may exceed the callback gas budget. Use pagination for more records. ### Available subscriptions | Constant | \`subscriptionId\` | Description | \`responseStruct\` | | -------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | | \`FLAP\_TRADE\_BNB\_24H\` | \`keccak256("FLAP\_TRADE\_BNB\_24H")\` | Addresses based on their trading activity in the previous 24 hours — across both Bonding Curve and DEX — on tokens launched from Flap | \`ResponseData\` | Fees may change. Always call \`getFee(subscriptionId)\` on-chain immediately before sending \`requestData()\`. \*\*\* ## How to integrate ### Step 1 — Query the fee \`\`\`solidity uint256 fee = IFlapCandyBox(oracleAddress).getFee(subscriptionId); \`\`\` ### Step 2 — Call \`requestData()\` \`\`\`solidity bytes32 requestId = IFlapCandyBox(oracleAddress).requestData{value: fee}( subscriptionId, uint64(block.timestamp), // backend derives previous day's window from this offset, // zero-based page offset limit, // records per page, must be <= maxLimit "" // extraParams: leave empty ); \`\`\` > Pass \`block.timestamp\` directly — do not manually compute \`startTime\`/\`endTime\`. The backend derives the previous UTC day window from the request timestamp. \*\*Pagination example\*\* — fetching 3000 addresses in pages of 145: \`\`\`solidity // Page 0: offset=0, limit=145 // Page 1: offset=145, limit=145 // Page 2: offset=290, limit=145 // ... // Use ResponseData.totalSize from the first callback to calculate how many pages are needed. \`\`\` ### Step 3 — Implement the consumer The recommended approach is to inherit \`FlapCandyBoxConsumerBase\`, which provides the \`onlyFlapCandyBox\` modifier and hardcoded address resolution automatically. \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {IFlapCandyBox, FlapCandyBoxConsumerBase} from "src/interfaces/IFlapCandyBox.sol"; contract MyVault is FlapCandyBoxConsumerBase { bytes32 public constant SUBSCRIPTION\_BNB = keccak256("FLAP\_TRADE\_BNB\_24H"); mapping(bytes32 => bool) private \_fulfilled; function requestStats(uint32 offset, uint32 limit) external payable returns (bytes32 requestId) { IFlapCandyBox oracle = IFlapCandyBox(\_getFlapCandyBox()); uint256 fee = oracle.getFee(SUBSCRIPTION\_BNB); requestId = oracle.requestData{value: fee}( SUBSCRIPTION\_BNB, uint64(block.timestamp), offset, limit, "" ); } // Called by FlapCandyBox — protected by onlyFlapCandyBox modifier from base contract function \_onDataReceived( bytes32 subscriptionId, bytes32 requestId, bytes calldata data ) internal override { require(!\_fulfilled\[requestId\], "already fulfilled"); \_fulfilled\[requestId\] = true; // mark before any external calls if (subscriptionId == SUBSCRIPTION\_BNB) { IFlapCandyBox.ResponseData memory resp = abi.decode(data, (IFlapCandyBox.ResponseData)); // resp.totalSize — total qualifying addresses across all pages // resp.totalAmount — sum of tradeAmount across ALL matching records (not just this page) // resp.records — address-level records for this page // resp.startTime / resp.endTime — actual data window \_handleDistribution(resp); } } function \_handleDistribution(IFlapCandyBox.ResponseData memory resp) internal { for (uint256 i = 0; i < resp.records.length; i++) { // distribute proportionally using resp.records\[i\].wallet and resp.records\[i\].tradeAmount } } } \`\`\` Alternatively, implement \`IFlapCandyBoxConsumer\` directly and validate \`msg.sender\` yourself: \`\`\`solidity function onDataReceived(bytes32 subscriptionId, bytes32 requestId, bytes calldata data) external override { require(msg.sender == address(oracle), "Only FlapCandyBox"); // ... } \`\`\` \*\*\* ## ResponseData struct The \`data\` payload in \`onDataReceived()\` is ABI-encoded as \`ResponseData\`. Use \`getSubscription(subscriptionId).responseStruct\` to confirm the struct name for each subscription. \`\`\`solidity struct AddressRecord { address wallet; uint256 tradeAmount; // 1e18-scaled; BNB subscription → wei, USD subscription → 1e18-scaled USD } struct ResponseData { uint256 totalSize; // total records matching the query across all pages uint256 offset; // zero-based index of the first record in this page uint256 returnedCount; // actual number of records in this page (may be < limit on last page) bool isDesc; // true — records are sorted by tradeAmount descending uint64 startTime; // query window start (Unix timestamp, UTC 00:00:00) uint64 endTime; // query window end (Unix timestamp, UTC 23:59:59) uint256 totalAmount; // sum of tradeAmount across ALL matching records (not just this page) AddressRecord\[\] records; } \`\`\` > Use \`totalSize\` (not \`returnedCount\`) for proportional distribution calculations across pages. \*\*\* ## Security checklist | Item | Detail | | --------------------- | --------------------------------------------------------------------------------------------- | | Validate \`msg.sender\` | Use \`FlapCandyBoxConsumerBase\` (recommended) or \`require(msg.sender == address(oracle))\` | | Guard double-delivery | Track a \`fulfilled\` flag per \`requestId\` and revert on repeat | | Reentrancy | Mark fulfilled \*\*before\*\* any external calls or token transfers | | Callback gas | Keep \`onDataReceived()\` within \`getMaxCallbackGas()\`; check with \`oracle.getMaxCallbackGas()\` | | Fee freshness | Call \`getFee()\` immediately before sending \`requestData()\` — fees may change | | Page size | Use \`getMaxLimit(subscriptionId)\` to check the current cap before submitting | \*\*\* ## Reference ### Full interface \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; interface IFlapCandyBox { // ── Enums ──────────────────────────────────────────────────────────────── enum RequestStatus { PENDING, // 0 — created, waiting for fulfiller FULFILLED, // 1 — data delivered and consumer callback succeeded FAILED // 2 — data delivered but consumer callback reverted; eligible for retry } // ── Structs ────────────────────────────────────────────────────────────── struct Subscription { bytes32 subscriptionId; string description; string responseStruct; // name of the ResponseData struct, e.g. "ResponseData" uint256 fee; // required msg.value per requestData() call (wei) uint32 maxLimit; // maximum allowed limit per request bool active; } struct OracleRequest { address consumer; bytes32 subscriptionId; uint64 timestamp; // caller's block.timestamp; backend derives previous day's window uint32 offset; uint32 limit; bytes extraParams; uint128 feePaid; RequestStatus status; } struct AddressRecord { address wallet; uint256 tradeAmount; } struct ResponseData { uint256 totalSize; // total records matching the query across all pages uint256 offset; // zero-based index of the first record in this page uint256 returnedCount; // actual records in this page bool isDesc; // true if sorted by tradeAmount descending uint64 startTime; // query window start (Unix timestamp) uint64 endTime; // query window end (Unix timestamp) uint256 totalAmount; // sum of tradeAmount across ALL matching records (not just this page) AddressRecord\[\] records; } // ── Events ─────────────────────────────────────────────────────────────── event FlapSubscriptionRegistered(bytes32 indexed subscriptionId, string description, uint256 fee); event FlapSubscriptionFeeUpdated(bytes32 indexed subscriptionId, uint256 oldFee, uint256 newFee); event FlapSubscriptionLimitUpdated(bytes32 indexed subscriptionId, uint32 oldLimit, uint32 newLimit); event FlapDataRequested( bytes32 indexed subscriptionId, bytes32 indexed requestId, address indexed consumer, uint64 timestamp, uint32 offset, uint32 limit, uint256 feePaid, bytes extraParams ); event FlapDataFulfilled(bytes32 indexed subscriptionId, bytes32 indexed requestId, bool success, bytes returnData); event FlapFulfillerUpdated(address indexed fulfiller, bool authorized); // ── Errors ─────────────────────────────────────────────────────────────── error InvalidSubscription(bytes32 subscriptionId); error SubscriptionAlreadyExists(bytes32 subscriptionId); error InsufficientFee(uint256 required, uint256 provided); error LimitExceeded(uint32 requested, uint32 max); error AlreadyFulfilled(bytes32 requestId); error InvalidRequestId(bytes32 requestId); error OnlyFulfiller(); error RequestNotFailed(bytes32 requestId, RequestStatus status); error RetryFailed(bytes32 requestId, bytes returnData); // ── Write ──────────────────────────────────────────────────────────────── /// @notice Submit a data request. Emits FlapDataRequested for the backend to pick up. /// @param subscriptionId Identifies which dataset and pricing rule to query. /// @param timestamp Pass block.timestamp. Backend derives previous day's window from this. /// @param offset Zero-based page offset (first page = 0). /// @param limit Records per page. Must be <= getMaxLimit(subscriptionId). /// @param extraParams Reserved. Pass "". /// @return requestId Store this to correlate the callback. function requestData( bytes32 subscriptionId, uint64 timestamp, uint32 offset, uint32 limit, bytes calldata extraParams ) external payable returns (bytes32 requestId); // ── View ───────────────────────────────────────────────────────────────── function getSubscription(bytes32 subscriptionId) external view returns (Subscription memory); function getSubscriptions() external view returns (Subscription\[\] memory); function getFee(bytes32 subscriptionId) external view returns (uint256); function getMaxLimit(bytes32 subscriptionId) external view returns (uint32); function getRequest(bytes32 requestId) external view returns (OracleRequest memory); function getMaxCallbackGas() external view returns (uint256); } /// @notice Base contract for FlapCandyBox consumers. /// @dev Inherit this to get address resolution and the onlyFlapCandyBox modifier for free. abstract contract FlapCandyBoxConsumerBase { error FlapCandyBoxConsumerOnlyBox(); error FlapCandyBoxConsumerUnsupportedChain(uint256 chainId); modifier onlyFlapCandyBox() { if (msg.sender != \_getFlapCandyBox()) revert FlapCandyBoxConsumerOnlyBox(); \_; } /// @notice Returns the FlapCandyBox proxy address for the current chain. function \_getFlapCandyBox() internal view virtual returns (address) { uint256 id = block.chainid; if (id == 56) return 0x6255fbd731272a517022e99f6CaCf6A5De9414Ee; // BSC Mainnet if (id == 97) return 0x8e6C16Bf07022a7Da9398B543f38846E9355Bd70; // BSC Testnet revert FlapCandyBoxConsumerUnsupportedChain(id); } function onDataReceived(bytes32 subscriptionId, bytes32 requestId, bytes calldata data) external virtual onlyFlapCandyBox { \_onDataReceived(subscriptionId, requestId, data); } function \_onDataReceived(bytes32 subscriptionId, bytes32 requestId, bytes calldata data) internal virtual; } /// @notice Minimal interface for consumers that prefer not to use the base contract. interface IFlapCandyBoxConsumer { function onDataReceived(bytes32 subscriptionId, bytes32 requestId, bytes calldata data) external; } \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/token-launcher-developers/quick-start-token-launcher-developers.md). # Quick start for token launcher developers This page outlines the minimal steps to integrate with Flap for token launcher developers. Each step links to the detailed documentation. 1. \*\*Find the \`Portal\` entrypoint.\*\* Use the deployed addresses listed in \[Deployed Contract Addresses\](/flap/developers/token-launcher-developers/deployed-contract-addresses.md). 2. \*\*Launch a token through \`Portal\`.\*\* \`Portal\` is the core protocol for launching standard tokens and tax tokens. See \[Portal vs VaultPortal\](/flap/developers/basic-and-mechanism/portal-vs-vaultportal.md) for the conceptual difference, then follow \[Launch token through Portal\](/flap/developers/token-launcher-developers/launch-token-through-portal.md) for the implementation details. 3. \*\*Launch a token through \`VaultPortal\` (tax tokens with vaults).\*\* \`VaultPortal\` builds on top of \`Portal\` to add Vault functionality for tax tokens. Follow \[Launch token through VaultPortal\](/flap/developers/token-launcher-developers/launch-token-through-vaultportal.md). Each vault factory defines its own vault data schema—see \[Registered vaults\](/flap/developers/token-launcher-developers/registered-vaults.md) before encoding \`vaultData\`. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/preview/flap-trigger-service.md). # Flap Trigger Service ## Overview FlapTriggerService is a decentralized on-chain scheduler that allows smart contracts to request delayed or immediate function callbacks executed by a trusted backend. It bridges on-chain logic with off-chain coordination, enabling complex time-sensitive operations without requiring the caller to manage execution timing directly. \*\*Deployed addresses:\*\* | Network | Address | Max Gas Limit | | ----------- | -------------------------------------------- | ------------- | | BSC Mainnet | \`0xcf4EE25035CF883895110f367F5BA8172416a7F9\` | 2,000,000 | | BSC Testnet | \`0x560E9830926C9e0EB98a59c6b9902383Fc0D9Eb2\` | 2,000,000 | \*\*Max Gas Limit:\*\* The maximum gas limit supported for a \`trigger\` callback. \*\*Primary use cases:\*\* \* Time-delayed operations (vesting unlocks, periodic distributions, deferred settlements) \* Backend-coordinated operations requiring MEV protection \* Operations that need external computation before on-chain execution ### How it works \`\`\`mermaid sequenceDiagram participant R as Requester Contract participant S as FlapTriggerService participant B as Backend (off-chain) R->>S: requestTrigger{value: fee}(executeAfter) S-->>B: emit FlapTriggerRequested(requestId, requester, executeAfter, fee) Note over B: indexes event, waits until executeAfter B->>S: trigger(requestId) via MEV-protected RPC S->>R: trigger(requestId) \[callback, bounded gas\] S-->>B: emit FlapTriggerExecuted(requestId, success, data) \`\`\` \*\*Step by step:\*\* 1. The requester contract calls \`requestTrigger()\`, paying the required gas fee and specifying an \`executeAfter\` timestamp (or \`0\` for immediate execution). 2. The service records the request and emits a \`FlapTriggerRequested\` event. 3. The off-chain backend monitors for these events and indexes all pending requests. 4. When the scheduled time arrives, the backend submits a \`trigger(requestId)\` transaction via an MEV-protected RPC (e.g., Flashbots, BloXroute). 5. FlapTriggerService calls back \`requester.trigger(requestId)\` with a bounded gas limit. 6. The requester's callback uses the \`requestId\` to identify and execute the intended operation. ### Timing guarantees {% hint style="warning" %} \`executeAfter\` is a lower bound, not a hard deadline. The service only guarantees that execution happens \*\*after\*\* \`executeAfter\`. {% endhint %} Integrators \*\*must\*\* assume there can be an unpredictable delay due to: \* Network congestion \* Backend processing latency \* Block inclusion delays \* MEV protection overhead Requester contracts \*\*must\*\* be designed to handle late execution gracefully and \*\*must not\*\* rely on execution at a precise time. \*\*\* ## Trigger pricing A fixed native-currency fee (BNB on BSC) is charged per trigger request. The current price is \*\*0.0002 BNB per request\*\*. This fee covers: \* Gas costs for the backend's \`trigger()\` transaction \* Gas costs for the callback to the requester contract \* A small service fee \*\*Getting the current fee:\*\* \`\`\`solidity uint256 fee = IFlapTriggerService(triggerService).getFee(); \`\`\` The fee is paid upfront when calling \`requestTrigger()\` as \`msg.value\`. Excess payment above the required fee is accumulated as protocol fees — there is no refund for overpayment. \*\*Callback gas limit:\*\* Each trigger callback is forwarded at most \`getMaxCallbackGas()\` gas. Requester contracts must ensure their \`trigger()\` callback completes within this limit. Exceeding the limit will cause the callback to fail (status becomes \`FAILED\`). \`\`\`solidity uint256 maxGas = IFlapTriggerService(triggerService).getMaxCallbackGas(); \`\`\` \*\*Failed callbacks and retry:\*\* If a callback fails (e.g., out of gas or revert), the request status is set to \`FAILED\`. Anyone can retry a failed request by calling \`retryTrigger(requestId)\` — this forwards all available gas to the callback, allowing the caller to supply sufficient gas. The fee stored in the request is transferred to the fee receiver on successful retry. \*\*\* ## How to integrate ### Step 1 — Import the interfaces \`\`\`solidity import { IFlapTriggerService } from "src/misc/IFlapTriggerService.sol"; import { ITriggerReceiver } from "src/misc/IFlapTriggerService.sol"; \`\`\` ### Step 2 — Implement \`ITriggerReceiver\` Your contract must implement the \`trigger(uint256 requestId)\` callback. This function is called by FlapTriggerService when the scheduled time has passed. \*\*Security requirements for the callback:\*\* \* \*\*Must\*\* validate \`msg.sender == address(triggerService)\` \* \*\*Should\*\* implement a reentrancy guard if performing external calls or state changes \* \*\*Must\*\* complete within \`getMaxCallbackGas()\` gas \* \*\*Must not\*\* assume execution happens exactly at \`executeAfter\` — always assume potential delay ### Step 3 — Schedule a trigger Call \`requestTrigger()\` with the desired \`executeAfter\` timestamp. Store the returned \`requestId\` to map it back to the intended operation in your callback. ### Pseudo-code example \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import { IFlapTriggerService, ITriggerReceiver } from "src/misc/IFlapTriggerService.sol"; import { ReentrancyGuard } from "@openzeppelin/utils/ReentrancyGuard.sol"; contract MyScheduledVault is ITriggerReceiver, ReentrancyGuard { IFlapTriggerService public immutable triggerService; struct PendingAction { address recipient; uint256 amount; } mapping(uint256 => PendingAction) private pendingActions; constructor(address \_triggerService) { triggerService = IFlapTriggerService(\_triggerService); } /// @notice Schedule a delayed distribution 1 day from now. function scheduleDistribution(address recipient, uint256 amount) external payable { // 1. Get the required fee uint256 fee = triggerService.getFee(); // 2. Request the trigger (executeAfter = 1 day from now) uint256 requestId = triggerService.requestTrigger{value: fee}( uint64(block.timestamp + 1 days) ); // 3. Store the action data keyed by requestId pendingActions\[requestId\] = PendingAction({ recipient: recipient, amount: amount }); } /// @notice Called back by FlapTriggerService after executeAfter has passed. function trigger(uint256 requestId) external nonReentrant override { // SECURITY: only the trigger service can call this require(msg.sender == address(triggerService), "Only trigger service"); PendingAction memory action = pendingActions\[requestId\]; require(action.recipient != address(0), "Unknown requestId"); // Clean up before external call (checks-effects-interactions) delete pendingActions\[requestId\]; // Execute the intended operation // NOTE: Execution may happen well after the scheduled time — design accordingly \_distribute(action.recipient, action.amount); } function \_distribute(address recipient, uint256 amount) internal { // ... distribution logic ... } } \`\`\` ### Scheduling for immediate execution Pass \`0\` as \`executeAfter\` to request execution as soon as the backend processes the event: \`\`\`solidity uint256 requestId = triggerService.requestTrigger{value: fee}(0); \`\`\` ### Querying request status \`\`\`solidity // Get details about a specific request IFlapTriggerService.TriggerRequest memory req = triggerService.getRequest(requestId); // req.status: PENDING (0), EXECUTED (1), FAILED (2) // req.executeAfter: scheduled time // req.feePaid: fee paid (wei) // Check if a request is ready to execute right now bool ready = triggerService.isRequestReady(requestId); // Get all requests by your contract (paginated, newest first) (IFlapTriggerService.TriggerRequest\[\] memory page, uint256 total) = triggerService.getRequestsByRequesterPaginated(address(this), 0, 20); \`\`\` ### Retrying a failed trigger If a callback fails (e.g., the callback used more gas than the limit), anyone can retry: \`\`\`solidity triggerService.retryTrigger(requestId); \`\`\` \*\*\* ## Reference ### Full interface \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; interface IFlapTriggerService { // ── Enums ──────────────────────────────────────────────────────────────── enum TriggerStatus { PENDING, // 0 — Request created, waiting to be executed EXECUTED, // 1 — Successfully executed by the backend FAILED // 2 — Execution attempted but the callback reverted } // ── Structs ────────────────────────────────────────────────────────────── /// @notice Packed into two 32-byte storage slots. /// Slot 0: status (8 bits) | executeAfter (64 bits) | requester (160 bits) | 24 bits reserved /// Slot 1: feePaid (128 bits) struct TriggerRequest { address requester; // 160 bits — address that requested the trigger uint64 executeAfter; // 64 bits — Unix timestamp lower bound for execution TriggerStatus status; // 8 bits — current lifecycle status uint128 feePaid; // 128 bits — native fee paid in wei (slot 1) } // ── Events ─────────────────────────────────────────────────────────────── /// @notice Emitted when a new trigger request is created. event FlapTriggerRequested( uint256 requestId, address indexed requester, uint64 executeAfter, uint256 gasFeesPaid ); /// @notice Emitted when a trigger execution is attempted (success or failure). event FlapTriggerExecuted(uint256 requestId, bool success, bytes data); /// @notice Emitted when a trigger is skipped (invalid ID, wrong status, or not yet due). event FlapTriggerSkipped(uint256 requestId, string reason); /// @notice Emitted when the required gas fee is updated by admin. event FlapTriggerGasFeeUpdated(uint256 oldFee, uint256 newFee); /// @notice Emitted when the maximum callback gas limit is updated by admin. event FlapTriggerMaxCallbackGasUpdated(uint256 oldLimit, uint256 newLimit); // ── Errors ─────────────────────────────────────────────────────────────── /// @notice msg.value is below the required fee. error InsufficientGasFee(uint256 required, uint256 provided); /// @notice Request is not in PENDING status. error InvalidRequestStatus(uint256 requestId, TriggerStatus currentStatus); /// @notice block.timestamp is before executeAfter. error TooEarly(uint256 requestId, uint64 executeAfter, uint256 currentTime); /// @notice The provided requestId does not exist. error InvalidRequestId(uint256 requestId); /// @notice Caller does not have TRIGGER\_ROLE. error OnlyTriggerRole(); /// @notice Admin tried to set an invalid gas fee (e.g., zero). error InvalidGasFee(); /// @notice Admin tried to set an invalid gas limit (e.g., zero or too high). error InvalidGasLimit(); /// @notice Fee receiver address is invalid (e.g., zero address). error InvalidFeeReceiver(); /// @notice A retried trigger callback reverted. error RetryFailed(uint256 requestId, bytes data); /// @notice msg.value overflows uint128 and cannot be stored as feePaid. error FeePaidOverflow(uint256 provided); // ── Write methods ──────────────────────────────────────────────────────── /// @notice Request a trigger callback at or after a specified time. /// @param executeAfter Unix timestamp after which execution may happen. Pass 0 for immediate. /// @return requestId Unique ID for this trigger request. /// Requirements: msg.value >= getFee(); if executeAfter > 0, must be >= block.timestamp. function requestTrigger(uint64 executeAfter) external payable returns (uint256 requestId); /// @notice Execute a single pending trigger (backend only, requires TRIGGER\_ROLE). /// @param requestId The ID of the request to execute. function trigger(uint256 requestId) external; /// @notice Execute multiple pending triggers in one transaction (backend only, requires TRIGGER\_ROLE). /// @param requestIds Array of request IDs to execute. Invalid or already-done IDs are skipped. function triggerMultiple(uint256\[\] calldata requestIds) external; /// @notice Retry a previously failed trigger request. Callable by anyone. /// Forwards all available gas; reverts with RetryFailed on failure. /// @param requestId The ID of the FAILED request to retry. function retryTrigger(uint256 requestId) external; // ── View methods ───────────────────────────────────────────────────────── /// @notice The native-currency fee (wei) required as msg.value for requestTrigger(). function getFee() external view returns (uint256 gasFee); /// @notice Maximum gas forwarded to a requester's callback. Callbacks must fit within this. function getMaxCallbackGas() external view returns (uint256 maxGas); /// @notice Get details about a single trigger request. Reverts for unknown IDs. function getRequest(uint256 requestId) external view returns (TriggerRequest memory request); /// @notice Total number of trigger requests ever created (= next request ID). function getRequestCount() external view returns (uint256 count); /// @notice True if the request exists, is PENDING, and block.timestamp >= executeAfter. function isRequestReady(uint256 requestId) external view returns (bool ready); /// @notice Fetch multiple requests by ID in one call. Unknown IDs return zero-initialised structs. function getRequests(uint256\[\] calldata requestIds) external view returns (TriggerRequest\[\] memory requests); /// @notice Paginated list of all requests, newest first (descending by ID). /// @param offset Requests to skip from the newest. /// @param limit Maximum number to return. function getRequestsPaginated(uint256 offset, uint256 limit) external view returns (TriggerRequest\[\] memory requests, uint256 total); /// @notice Paginated list of requests for a specific requester address, newest first. /// @param requester Address whose requests to query. /// @param offset Requests to skip from the newest. /// @param limit Maximum number to return. function getRequestsByRequesterPaginated(address requester, uint256 offset, uint256 limit) external view returns (TriggerRequest\[\] memory requests, uint256 total); } /// @notice Interface that requester contracts must implement to receive trigger callbacks. interface ITriggerReceiver { /// @notice Called by FlapTriggerService when a scheduled trigger executes. /// @dev MUST validate msg.sender == triggerService address. /// SHOULD implement reentrancy guard. /// MUST complete within getMaxCallbackGas(). /// MUST NOT assume execution at exactly executeAfter — delays are possible. /// @param requestId ID of the trigger request being executed. function trigger(uint256 requestId) external; } \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/preview/worldcup-resolver.md). # World Cup Resolver {% hint style="warning" %} This is an older solution and can still be used. For new integrations, prefer \[worldcup-viewer.md\](/flap/developers/preview/worldcup-viewer.md). {% endhint %} ## Overview \`WorldCupResolver\` is a read-only smart contract deployed on BSC mainnet that reads \*\*2026 FIFA World Cup\*\* match outcomes from the on-chain oracle system and exposes the results to downstream consumers. \*\*Deployed address (BSC mainnet):\*\* \`0x134C6b9562E226096947e018ddEe4804c9146921\` The contract covers 43 entries: the 32 tournament teams, a selection of additional national teams with odds, and an "Other" catch-all. Each team maps to a unique oracle question: \*"Will \\\[Team\] win the 2026 FIFA World Cup?"\* The underlying oracle resolves each question as YES or NO on-chain. {% hint style="info" %} No result state is stored inside \`WorldCupResolver\`. Every call to \`resolveWinner()\` reads the oracle live, so the returned snapshot always reflects the latest state. {% endhint %} \*\*\* ## How It Works ### Oracle questions and team resolution For every team, there is exactly one oracle question: \*"Will this team win the World Cup?"\* | Oracle field | Meaning | | ------------------ | ------------------------------------------------------------- | | \`reportedAt > 0\` | The oracle has published a result for this team | | \`results == true\` | The result is \*\*YES\*\* — this team won | | \`results == false\` | The result is \*\*NO\*\* — this team did \*\*not\*\* win (eliminated) | | \`flaggedAt > 0\` | The result is currently under dispute; treat as unresolved | A team is confirmed as the \*\*final winner\*\* only when all three conditions hold simultaneously: 1. \`reportedAt > 0\` — oracle has reported. 2. \`results == true\` — result is YES. 3. \`flaggedAt == 0\` — result is \*\*not\*\* flagged/disputed. ### Resolving the final winner Call \`resolveWinner()\`. It scans all 43 entries and returns a \`WinnerResult\` struct: \* \`isResolved = true\` → a confirmed winning team was found; read \`winnerIndex\` and \`winnerName\`. \* \`isResolved = false\` → no confirmed winner yet; inspect \`statuses\[\]\` for per-team progress. ### Resolving a single match (team elimination) {% hint style="warning" %} \*\*Limitation — teams grouped under "Other" (index 42):\*\* \`WorldCupResolver\` does not run its own oracle. It reads data from an external UMA-based oracle, and the resolution data provided by that oracle only covers 42 individually named teams. All remaining qualified national teams that were not given their own oracle question are grouped into a single \`Other\` entry (index 42). As a result, if one of those unlisted teams is eliminated in a match, their elimination \*\*cannot\*\* be detected individually — the \`Other\` question stays unresolved until one of those teams wins the entire World Cup (at which point \`Other\` resolves YES). Per-team match resolution is only supported for the 42 named teams. {% endhint %} Because every oracle question is about the \*\*overall World Cup winner\*\*, an answer of NO for a team means that team has been \*\*definitively eliminated\*\* — they can never win the tournament. This makes it possible to infer match outcomes incrementally, well before the final is played: \*\*Example — Group stage match: Team A vs Team B\*\* 1. Call \`resolveWinner()\` and read \`statuses\[\]\`. 2. Check Team A's status: \`isReported = true\`, \`result = false\`, \`isFlagged = false\` → \*\*Team A is eliminated\*\* (they lost and are out of the tournament). 3. Check Team B's status: \`isReported = false\` → \*\*Team B is still in contention\*\* (their question has not yet been resolved). In this scenario Team A's result is fully settled — it can be used to pay out prediction positions — while Team B remains pending until a later match resolves their fate. {% hint style="warning" %} Only treat \`isReported = true\` \*\*and\*\* \`isFlagged = false\` as a settled result. A flagged result is under oracle dispute and must not be acted upon. {% endhint %} \*\*\* ## Interface \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @notice Per-team oracle status, returned by read-only query methods. struct TeamStatus { uint8 index; // 0-based team index (same order as getSupportedTeams()) string name; // human-readable team name bool isReported; // true when reportedAt > 0 on the oracle bool result; // true = YES (winner), false = NO or not yet reported bool isFlagged; // true when flaggedAt > 0 — result is disputed } /// @notice Return value of resolveWinner(). struct WinnerResult { bool isResolved; // true if a definitive YES answer was found uint8 winnerIndex; // set only when isResolved = true string winnerName; // set only when isResolved = true TeamStatus\[\] statuses; // full per-team snapshot at time of call } interface IWorldCupResolver { /// @notice Returns the full list of 43 supported team names in index order. /// names\[0\] = "Spain", names\[42\] = "Other". /// Safe to call at any time, including while the contract is paused. function getSupportedTeams() external view returns (string\[\] memory names); /// @notice Reads the oracle live and computes the World Cup winner. /// /// - isResolved = true → winnerIndex / winnerName identify the champion. /// - isResolved = false → no winner yet; inspect statuses\[\] for per-team progress. /// /// A team counts as winner only when: /// 1. reportedAt > 0 (oracle has reported) /// 2. results == true (oracle result is YES) /// 3. flaggedAt == 0 (result is not disputed) function resolveWinner() external view returns (WinnerResult memory result); } \`\`\` \*\*\* ## Supported Teams \`getSupportedTeams()\` returns 43 entries. The index is stable and matches the \`TeamStatus.index\` field returned by \`resolveWinner()\`. | Index | Team | Index | Team | | ----- | ----------- | ----- | ------------ | | 0 | Spain | 22 | Canada | | 1 | France | 23 | Scotland | | 2 | England | 24 | South Korea | | 3 | Argentina | 25 | Paraguay | | 4 | Brazil | 26 | Ivory Coast | | 5 | Portugal | 27 | Egypt | | 6 | Germany | 28 | Iran | | 7 | Netherlands | 29 | Ghana | | 8 | Norway | 30 | Algeria | | 9 | Italy | 31 | Tunisia | | 10 | Belgium | 32 | Austria | | 11 | USA | 33 | New Zealand | | 12 | Morocco | 34 | Haiti | | 13 | Colombia | 35 | Jordan | | 14 | Japan | 36 | Curacao | | 15 | Uruguay | 37 | Uzbekistan | | 16 | Croatia | 38 | South Africa | | 17 | Mexico | 39 | Cape Verde | | 18 | Switzerland | 40 | Qatar | | 19 | Ecuador | 41 | Saudi Arabia | | 20 | Senegal | 42 | Other | | 21 | Australia | | | \*\*\* ## Integration Examples ### Check for the final winner (Solidity) \`\`\`solidity IWorldCupResolver resolver = IWorldCupResolver(0x134C6b9562E226096947e018ddEe4804c9146921); WinnerResult memory r = resolver.resolveWinner(); if (r.isResolved) { // r.winnerName → e.g. "Brazil" // r.winnerIndex → e.g. 4 } \`\`\` ### Check whether a specific team has been eliminated (Solidity) \`\`\`solidity WinnerResult memory r = resolver.resolveWinner(); // Example: check if Spain (index 0) is eliminated TeamStatus memory spain = r.statuses\[0\]; if (spain.isReported && !spain.result && !spain.isFlagged) { // Spain has been definitively eliminated — safe to settle Spain positions } \`\`\` ### Read all statuses off-chain (ethers.js) \`\`\`js const resolver = new ethers.Contract( "0x134C6b9562E226096947e018ddEe4804c9146921", IWorldCupResolverABI, provider ); const result = await resolver.resolveWinner(); console.log("Is resolved:", result.isResolved); if (result.isResolved) { console.log("Winner:", result.winnerName, "(index", result.winnerIndex, ")"); } for (const status of result.statuses) { if (status.isReported && !status.isFlagged) { const outcome = status.result ? "WINNER" : "ELIMINATED"; console.log(\`${status.name}: ${outcome}\`); } else { console.log(\`${status.name}: pending\`); } } \`\`\` \*\*\* ## Deployment Details | Field | Value | | ---------------- | -------------------------------------------- | | Network | BSC Mainnet | | Contract address | \`0x134C6b9562E226096947e018ddEe4804c9146921\` | --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/skills.md). # OpenClaw Skills Flap's published skills are available on the ClawHub registry. You can invoke them directly from any OpenClaw-compatible agent. \*\*\* ## Launch a BNB Token on Flap \*\*Registry page:\*\* \[clawhub.ai/flapguy/launch-bnb-token-on-flap\](https://clawhub.ai/flapguy/launch-bnb-token-on-flap) This skill walks an AI assistant through every step required to launch a token on Flap (BNB Chain): uploading metadata to IPFS, mining a vanity salt, encoding calldata, and broadcasting the transaction. ### How to use Give your OpenClaw-enabled assistant a prompt like one of the examples below. The skill handles the rest. \*\*\* ### Example 1 — Tax token gifted to an X account (Gift Vault) Launch a tax token whose trading fees are gifted to the X account \`@elonmusk\` using the \*\*Gift Vault\*\*. No vault factory address is needed — just refer to it by name. \`\`\` Launch a tax token on Flap BNB called "Elon's Rocket" with symbol ROKYT. Use the Gift Vault and set the gift owner to the X account @elonmusk. Set buy tax and sell tax both to 5%. Spend 0.00001 BNB on the initial buy. \`\`\` \*\*\* ### Example 2 — Tax token with a custom vault factory Launch a tax token and route its fees through a specific vault factory you have already deployed. \`\`\` Launch a tax token on Flap BNB called "Alpha Fund" with symbol ALFX. Use the vault factory at address 0xAbCd1234AbCd1234AbCd1234AbCd1234AbCd1234 to set up the vault. Set buy tax to 3% and sell tax to 3%. Skip the initial buy. \`\`\` \*\*\* ### Example 3 — Tax token without a vault Launch a straightforward tax token with no vault. Fees go directly to a beneficiary address. \`\`\` Launch a tax token on Flap BNB called "Simple Tax" with symbol SMTX. No vault. Set buy tax to 0% and sell tax to 3%. Set the beneficiary to 0xYourBeneficiaryAddressHere. Spend 0.05 BNB on the initial buy. \`\`\` \*\*\* ### Example 4 — Non-tax token Launch a basic token with no taxes at all. This is the simplest type of token. \`\`\` Launch a non-tax token on Flap BNB called "Pure Coin" with symbol PURE. Spend 0.01 BNB on the initial buy. \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend.md). # Using an LP Token or Child Token as the Dividend Token {% hint style="warning" %} \*\*Preview.\*\* This document has not been reviewed yet. Content may be incomplete or inaccurate. {% endhint %} This tutorial walks you through implementing a vault factory that uses a \*\*computed dividend token\*\* — a token whose address cannot be known before the tax token is deployed, such as a Uniswap/PancakeSwap LP pair or a child token deployed deterministically from the tax token address. This feature was introduced in \*\*factory spec v2.3\*\*. \*\*\* ## The Problem When launching a token through VaultPortal, the caller must supply a \`dividendToken\` address — the ERC-20 token that vault holders receive as dividends. Most of the time this is straightforward (e.g. use WBNB, or USDT). But some vault designs require a dividend token whose address \*\*depends on the tax token address itself\*\*: \* \*\*LP pair tokens\*\* — the PancakeSwap pair for \`(taxToken, WBNB)\` has an address derived from \`taxToken\`. \* \*\*Child tokens / receipt tokens\*\* — a token deployed via CREATE2 keyed on \`taxToken\`. At launch time the tax token does not exist yet — VaultPortal predicts its address via CREATE2, but the actual deployment happens inside Portal. The \`dividendToken\` field is needed \*before\* that deployment, so there is no way to supply the real LP/child address upfront. \*\*\* ## The Solution: \`MAGIC\_DIVIDEND\_COMPUTED\` VaultPortal supports a special sentinel value for \`dividendToken\`: \`\`\`solidity // IPortal.sol address constant MAGIC\_DIVIDEND\_COMPUTED = address(0xC0Dec0dec0DeC0Dec0dEc0DEC0DEC0DEC0DEC0dE); \`\`\` When the launcher passes \`MAGIC\_DIVIDEND\_COMPUTED\` as \`dividendToken\`, VaultPortal will: 1. Compute the CREATE2-predicted tax token address. 2. Check that the selected vault factory implements \*\*spec v2.3\*\* (\`factorySpecVersion()\` returns \`"v2.3"\` or higher). 3. Call \`resolveDividendToken(predictedToken, launchVersion, launchParams)\` on the factory. 4. Use the returned address as the actual \`dividendToken\` forwarded to Portal. If the factory does not support v2.3, the launch reverts with \`FactoryDoesNotSupportComputedDividend()\`. \*\*\* ## Implementation Guide ### Step 1 — Declare spec v2.3 in your factory Your factory must extend \`VaultFactoryBaseV2\` and override \`factorySpecVersion()\` to return \`"v2.3"\` (or higher): \`\`\`solidity import {VaultFactoryBaseV2} from "flap-sh/FlapTaxVaults/src/interfaces/VaultFactoryBaseV2.sol"; import {IVaultFactoryDividendV23} from "flap-sh/FlapTaxVaults/src/interfaces/IVaultFactory.sol"; contract MyLPVaultFactory is VaultFactoryBaseV2, IVaultFactoryDividendV23 { function factorySpecVersion() external pure override returns (string memory) { return "v2.3"; } // ... rest of factory } \`\`\` {% hint style="warning" %} You \*\*must\*\* also implement \`IVaultFactoryDividendV23\`. Declaring \`"v2.3"\` in \`factorySpecVersion()\` but not implementing \`resolveDividendToken\` will cause every \`MAGIC\_DIVIDEND\_COMPUTED\` launch to revert. {% endhint %} \*\*\* ### Step 2 — Implement \`resolveDividendToken\` \`IVaultFactoryDividendV23\` requires a single function: \`\`\`solidity interface IVaultFactoryDividendV23 { function resolveDividendToken( address predictedToken, uint8 launchVersion, bytes calldata launchParams ) external view returns (address dividendToken); } \`\`\` | Parameter | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------- | | \`predictedToken\` | The CREATE2-predicted address of the tax token (not yet deployed) | | \`launchVersion\` | \`DIVIDEND\_TOKEN\_LAUNCH\_VERSION\_V6\` or \`DIVIDEND\_TOKEN\_LAUNCH\_VERSION\_V7\` depending on the launch wrapper used | | \`launchParams\` | ABI-encoded \`NewTokenV6WithVaultParams\` or \`NewTokenV7WithVaultParams\` | VaultPortal calls this as a \*\*\`staticcall\`\*\*, so the function must be \`view\` (no state writes). #### Example — LP pair dividend This example resolves the PancakeSwap V2 LP pair of \`(predictedToken, WBNB)\` as the dividend token: \`\`\`solidity import {IUniswapV2Factory} from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; address constant PANCAKE\_FACTORY = 0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73; // BSC mainnet address constant WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; function resolveDividendToken( address predictedToken, uint8 /\* launchVersion \*/, bytes calldata /\* launchParams \*/ ) external view override returns (address dividendToken) { // The LP pair is deterministically computable from the two token addresses. // PancakeSwap V2 uses the same CREATE2 formula as Uniswap V2. dividendToken = IUniswapV2Factory(PANCAKE\_FACTORY).getPair(predictedToken, WBNB); // If the pair does not exist yet, compute it via CREATE2. if (dividendToken == address(0)) { dividendToken = \_computePairAddress(predictedToken, WBNB); } } function \_computePairAddress(address tokenA, address tokenB) internal pure returns (address) { (address t0, address t1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); bytes32 salt = keccak256(abi.encodePacked(t0, t1)); // PancakeSwap V2 init code hash on BSC bytes32 initCodeHash = 0x00fb7f630766e6a796048ea87d01acd3068e8ff67d078148a3fa3f4a84f69bd5; return address(uint160(uint256(keccak256(abi.encodePacked( hex"ff", PANCAKE\_FACTORY, salt, initCodeHash ))))); } \`\`\` #### Example — Child token dividend If your vault deploys a child token deterministically from the tax token address: \`\`\`solidity function resolveDividendToken( address predictedToken, uint8 /\* launchVersion \*/, bytes calldata /\* launchParams \*/ ) external view override returns (address dividendToken) { // Child token is deployed via CREATE2 with \`predictedToken\` as the salt dividendToken = address(uint160(uint256(keccak256(abi.encodePacked( hex"ff", address(this), // deployer is the factory itself bytes32(uint256(uint160(predictedToken))), CHILD\_TOKEN\_INIT\_HASH ))))); } \`\`\` \*\*\* ### Step 3 — Declare a policy so the UI pre-fills the sentinel \`tokenCreationPolicies()\` lets the UI know that \`dividendToken\` \*\*must\*\* equal \`MAGIC\_DIVIDEND\_COMPUTED\`. The UI reads this policy and pre-fills (or locks) the field before the user submits the launch form. \`\`\`solidity import {MAGIC\_DIVIDEND\_COMPUTED} from "./interfaces/IPortal.sol"; import {FactoryPolicy} from "./interfaces/IVaultSchemasV1.sol"; function tokenCreationPolicies() public pure override returns (FactoryPolicy\[\] memory policies) { policies = new FactoryPolicy\[\](1); policies\[0\] = FactoryPolicy({ target: "dividendToken", operator: "eq", value: abi.encode(MAGIC\_DIVIDEND\_COMPUTED), description: "Dividend token must be set to MAGIC\_DIVIDEND\_COMPUTED. " "The factory will resolve the real LP token address on-chain." }); } \`\`\` {% hint style="info" %} Policies are \*\*advisory hints for the UI only\*\* — they do not enforce anything on-chain. Always pair a policy with the corresponding \`\_validateBeforeLaunch\` check (Step 4) to enforce the rule on-chain. {% endhint %} \*\*\* ### Step 4 — Enforce the sentinel in \`\_validateBeforeLaunch\` Override \`\_validateBeforeLaunch\` to reject any launch that does not carry \`MAGIC\_DIVIDEND\_COMPUTED\`. This is the on-chain enforcement that backs the UI policy declared above. \`\`\`solidity import {LaunchValidationDataV1} from "./interfaces/IVaultFactory.sol"; function \_validateBeforeLaunch(LaunchValidationDataV1 memory data) internal pure override returns (bool success, string memory reason) { if (data.dividendToken != MAGIC\_DIVIDEND\_COMPUTED) { return ( false, "dividendToken must be MAGIC\_DIVIDEND\_COMPUTED for this vault type." ); } return (true, ""); } \`\`\` \`VaultPortal\` calls \`onBeforeLaunch(bytes)\` (which decodes into \`\_validateBeforeLaunch\`) \*\*before\*\* it calls \`resolveDividendToken\`. This means the hook sees \`dividendToken == MAGIC\_DIVIDEND\_COMPUTED\` — the raw sentinel, not yet the resolved LP address. That is the correct value to check against. {% hint style="warning" %} Your factory must return \`"v2.3"\` from \`factorySpecVersion()\` for \`onBeforeLaunch\` to be reached. If you return \`"v2.1"\` or lower, \`VaultPortal\` will use the legacy \`onBeforeNewTokenV6WithVault\` path instead. {% endhint %} \*\*\* ### Step 5 — Launch with \`MAGIC\_DIVIDEND\_COMPUTED\` On the caller side, set \`dividendToken\` to the sentinel when building the launch params: \`\`\`solidity import {MAGIC\_DIVIDEND\_COMPUTED} from "flap-sh/FlapTaxVaults/src/interfaces/IPortal.sol"; IVaultPortal.NewTokenV6WithVaultParams memory params = IVaultPortal.NewTokenV6WithVaultParams({ // ... other fields dividendToken: MAGIC\_DIVIDEND\_COMPUTED, // <-- the key field factory: address(myLPVaultFactory), vaultData: abi.encode(/\* your vault config \*/), // ... }); vaultPortal.newTokenV6WithVault{value: msg.value}(params); \`\`\` VaultPortal will call \`myLPVaultFactory.resolveDividendToken(...)\` and substitute the result before forwarding the launch to Portal. \*\*\* ## What Happens On-Chain (Flow Summary) \`\`\` Caller │ dividendToken = MAGIC\_DIVIDEND\_COMPUTED ▼ VaultPortal.newTokenV6WithVault / newTokenV7WithVault │ 1. Predict tax token address (CREATE2) │ 2. Check factory spec >= v2.3 │ 3. staticcall factory.resolveDividendToken(predictedToken, ...) │ └─ factory returns real LP / child token address │ 4. Replace dividendToken with resolved address │ 5. newVault(predictedToken, quoteToken, creator, vaultData) │ 6. Forward to Portal.newTokenV6WithVault(params with real dividendToken) ▼ Portal deploys tax token, links vault \`\`\` \*\*\* ## Version Gating | \`factorySpecVersion()\` | \`MAGIC\_DIVIDEND\_COMPUTED\` supported? | | ---------------------- | ------------------------------------------------------ | | \`""\` (legacy / V1) | ❌ reverts with \`FactoryDoesNotSupportComputedDividend\` | | \`"v2.1"\`, \`"v2.2"\` | ❌ reverts | | \`"v2.3"\` or higher | ✅ \`resolveDividendToken\` is called | Factories that do not implement v2.3 are completely unaffected — the new sentinel only triggers for launches that explicitly pass \`MAGIC\_DIVIDEND\_COMPUTED\`. \*\*\* ## Checklist \* \[ \] Factory extends \`VaultFactoryBaseV2\` and implements \`IVaultFactoryDividendV23\` \* \[ \] \`factorySpecVersion()\` returns \`"v2.3"\` or higher \* \[ \] \`resolveDividendToken\` is \`view\` and always returns a non-zero address \* \[ \] \`tokenCreationPolicies()\` declares \`dividendToken eq MAGIC\_DIVIDEND\_COMPUTED\` \* \[ \] \`\_validateBeforeLaunch\` rejects any launch where \`dividendToken != MAGIC\_DIVIDEND\_COMPUTED\` \* \[ \] Launcher passes \`MAGIC\_DIVIDEND\_COMPUTED\` as \`dividendToken\` \* \[ \] Launcher selects the v2.3 factory in the launch params --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/vault-developers.md). # Vault Developers - \[Quick start for Vault Developers\](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers.md) - \[Deployed Contract Addresses\](https://docs.flap.sh/flap/developers/vault-developers/deployed-contract-addresses.md) - \[Vault & VaultFactory Specification\](https://docs.flap.sh/flap/developers/vault-developers/vault-and-vaultfactory-specification.md) - \[Flap Tax Vault\](https://docs.flap.sh/flap/developers/vault-developers/flap-tax-vault.md) - \[Gift Vault (FlapXVault)\](https://docs.flap.sh/flap/developers/vault-developers/gift-vault.md) - \[Gift Vault V2\](https://docs.flap.sh/flap/developers/vault-developers/gift-vault-v2.md) - \[Building Upgradeable Vaults — Beacon Proxy Pattern\](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault.md) - \[Tutorials\](https://docs.flap.sh/flap/developers/vault-developers/tutorials.md) - \[Using an LP Token or Child Token as the Dividend Token\](https://docs.flap.sh/flap/developers/vault-developers/tutorials/lp-token-as-dividend.md) --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-migration.md). # Token Migration When a token's circulating supply reaches its dexSupplyThresh, it will be listed on a DEX, and a new pool will be created for the token. The event \`LaunchedToDEX\` is emitted to indicate that the token is listed on DEX. \`\`\`solidity /// @notice emitted when adding liquidity to DEX /// @param token The address of the token /// @param pool The address of the pool /// @param amount The amount of token added /// @param eth The amount of quote Token added event LaunchedToDEX(address token, address pool, uint256 amount, uint256 eth); \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/vault-developers/case-study-beacon-upgradeable-vault.md). # Building Upgradeable Vaults — Beacon Proxy Pattern This guide explains how to build an upgradeable vault for the Flap protocol. The recommended pattern is \*\*OpenZeppelin \`BeaconProxy\` + \`UpgradeableBeacon\`\*\*, with the upgrade authority gated to the \*\*Flap Guardian\*\* for risk-control reasons. The reference implementation lives in the \[FlapVaultExample\](https://github.com/flap-sh/FlapVaultExample) repository: \* Source: \[\`src/FreeCoinBeacon.sol\`\](https://github.com/flap-sh/FlapVaultExample/blob/main/src/FreeCoinBeacon.sol) \* Beacon-specific tests: \[\`test/FreeCoinBeacon.mainnet.t.sol\`\](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoinBeacon.mainnet.t.sol) \* Bonding-curve and post-DEX dispatch tests (non-beacon variant, identical protocol surface): \[\`test/FreeCoin.mainnet.t.sol\`\](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoin.mainnet.t.sol) \* Deployment scripts: \[\`script/mainnet/bnb/DeployFreeCoinBeacon.s.sol\`\](https://github.com/flap-sh/FlapVaultExample/blob/main/script/mainnet/bnb/DeployFreeCoinBeacon.s.sol), \[\`script/testnet/bnb/DeployFreeCoinBeacon.s.sol\`\](https://github.com/flap-sh/FlapVaultExample/blob/main/script/testnet/bnb/DeployFreeCoinBeacon.s.sol) ## Overview For a new vault factory that will deploy more than a handful of vault instances, the recommended setup is: 1. Implement the vault as an \*\*upgradeable implementation contract\*\* with \`\_disableInitializers()\` in the constructor and an \`initialize(...)\` function gated by the \`initializer\` modifier. 2. In the factory's constructor, deploy the implementation and an \`UpgradeableBeacon\` pointing at it. The factory becomes the beacon's owner. 3. In \`newVault(...)\`, deploy a \`BeaconProxy\` and pass \`abi.encodeCall(YourVault.initialize, (...))\` as the proxy's initialization calldata. 4. Expose \`upgradeVaultImplementation(address)\` and \`lockVaultUpgrades()\` on the factory, \*\*gated to the Flap Guardian only\*\*. 5. Cover bonding-curve dispatch, post-DEX dispatch, the Guardian-only upgrade path, and the irreversible lock with integration tests. The rest of this guide walks through the rationale and the reference code. ## Why beacon proxies For production vaults, deploy the vault implementation behind an \*\*\`UpgradeableBeacon\`\*\* and create one \*\*\`BeaconProxy\`\*\* per token. Compared with deploying many immutable vault instances: 1. \*\*Operational flexibility\*\* — if a bug or protocol change shows up after launch, upgrading a single beacon implementation rolls the fix out to every existing and future vault of this type. No redeployment, no migration ask of token creators. 2. \*\*Cleaner factory design\*\* — the factory ABI-encodes initialization data and hands it to a fresh \`BeaconProxy\`, rather than duplicating constructor logic into every deployment path. 3. \*\*Consistent multi-vault upgrades\*\* — one beacon coordinates upgrades across every vault instance of the same type. 4. \*\*Smaller audit surface\*\* — auditors and integrators reason about a single implementation contract plus a single beacon authority model. If the vault is a one-instance-per-token, immutable-for-life design, inherit \`VaultBaseV2\` directly and skip the proxy. The beacon pattern in this guide is the default recommendation for \*\*factories that deploy many vault instances\*\*. ## Why upgrade authority stays with the Guardian Once a vault is upgradeable, the implementation address becomes the highest-risk asset in the setup. Anyone who can swap it can drain the vault, rewrite its accounting, or break dispatch. Gating that authority exclusively to the Flap Guardian is a risk-control requirement: \* \*\*Centralized incident response.\*\* If a vulnerability is discovered post-launch — through monitoring, audit follow-up, or an incident on a sibling vault — the Guardian path enables an immediate fix without coordinating with each token creator. \* \*\*Fewer privileged keys for vault developers to maintain.\*\* No HSM, multisig, or deployer EOA needs to remain online for the lifetime of the token on the developer's side. The upgrade key cannot be lost or compromised by the vault developer. \* \*\*Single trust assumption for users.\*\* Users already trust the Flap protocol. A separate non-Guardian upgrade owner widens the trust surface without a corresponding benefit. \* \*\*Optional irreversible immutability.\*\* When the project is ready, the Guardian can call \`lockVaultUpgrades()\` to renounce beacon ownership permanently — making the immutability commitment on-chain and verifiable. In practice this means: the factory must not retain a separate non-Guardian owner, proxy admin, beacon owner, upgrader role, deployer EOA, or external multisig with equivalent power. The factory should expose exactly two privileged entrypoints — \`upgradeVaultImplementation\` and \`lockVaultUpgrades\` — both gated to \`\_getGuardian()\`. {% hint style="warning" %} A factory that keeps any additional upgrade authority alongside the Guardian will not pass Flap's verification. The risk-control posture described above relies on the Guardian being the only path that can swap the implementation. {% endhint %} ## How to build it ### The implementation contract The implementation is the logic contract that all \`BeaconProxy\` instances delegate to. It looks like a normal vault except for three upgradeable-pattern obligations: \`\`\`solidity contract FreeCoinVaultUpgradeable is Initializable, VaultBaseV2, ReentrancyGuardUpgradeable { address public taxToken; uint256 public maxReward; uint256 public cooldown; // ... /// @custom:oz-upgrades-unsafe-allow constructor constructor() { \_disableInitializers(); } function initialize(address \_taxToken, uint256 \_maxReward, uint256 \_cooldown) external initializer { \_\_ReentrancyGuard\_init(); taxToken = \_taxToken; maxReward = \_maxReward; cooldown = \_cooldown; } \`\`\` Three things to check off: \* \*\*\`\_disableInitializers()\` in the constructor\*\* — locks the bare implementation so nobody can call \`initialize\` on it directly. Only proxies pointing at it can be initialized. \* \*\*\`initialize(...)\` replaces the constructor\*\* — every freshly deployed \`BeaconProxy\` calls this exactly once with the configuration the factory chose. The \`initializer\` modifier (from OpenZeppelin's \`Initializable\`) makes the second call revert. \* \*\*Use OpenZeppelin's upgradeable variants\*\* — \`ReentrancyGuardUpgradeable\` instead of \`ReentrancyGuard\`, plus the matching \`\_\_\*\_init()\` calls inside \`initialize\`. The rest of the contract (\`claim()\`, \`description()\`, \`vaultUISchema()\`, etc.) is shaped exactly like a non-upgradeable vault. Full source is in \[\`src/FreeCoinBeacon.sol\`\](https://github.com/flap-sh/FlapVaultExample/blob/main/src/FreeCoinBeacon.sol). ### The beacon factory The factory extends \`VaultFactoryBaseV2\` and owns the \`UpgradeableBeacon\` for this vault type: \`\`\`solidity contract FreeCoinVaultBeaconFactory is VaultFactoryBaseV2 { address public immutable beacon; constructor() { FreeCoinVaultUpgradeable impl = new FreeCoinVaultUpgradeable(); beacon = address(new UpgradeableBeacon(address(impl))); } function newVault(address taxToken, address /\*quoteToken\*/, address /\*creator\*/, bytes calldata vaultData) external override returns (address vault) { require(msg.sender == \_getVaultPortal(), unicode"Only VaultPortal / 仅限 VaultPortal 调用"); (uint256 maxReward, uint256 cooldown) = abi.decode(vaultData, (uint256, uint256)); vault = address( new BeaconProxy( beacon, abi.encodeCall(FreeCoinVaultUpgradeable.initialize, (taxToken, maxReward, cooldown)) ) ); } \`\`\` What's happening: 1. \*\*Constructor\*\* deploys the implementation and an \`UpgradeableBeacon\` pointing at it. The factory becomes the beacon's owner — the only account that can later call \`upgradeTo\` on the underlying \`Ownable\` beacon. 2. \*\*\`newVault(...)\`\*\* is invoked by VaultPortal during token launch. It deploys a fresh \`BeaconProxy\`, passing \`abi.encodeCall(initialize, (...))\` as the proxy's initialization calldata. The proxy delegates to the beacon's current implementation. 3. \*\*Quote-token whitelist\*\* is enforced by \`isQuoteTokenSupported\` and the V2.2 validation hook \`\_validateBeforeLaunch\` — this factory accepts native BNB only. ### Wiring the upgrade authority to the Guardian Although the factory technically owns the beacon, the only externally callable upgrade path goes through Guardian-gated functions: \`\`\`solidity function upgradeVaultImplementation(address newImplementation) external { require(msg.sender == \_getGuardian(), unicode"Only Guardian / 仅限 Guardian"); UpgradeableBeacon(beacon).upgradeTo(newImplementation); } function lockVaultUpgrades() external { require(msg.sender == \_getGuardian(), unicode"Only Guardian / 仅限 Guardian"); UpgradeableBeacon(beacon).renounceOwnership(); } function isVaultUpgradesLocked() external view returns (bool locked) { locked = UpgradeableBeacon(beacon).owner() == address(0); } \`\`\` Two details worth highlighting: \* After \`lockVaultUpgrades()\`, the beacon's \`owner()\` is \`address(0)\`. From that point forward, \*\*\`upgradeTo\` reverts at the beacon's own \`onlyOwner\` check\*\* — even when the Guardian re-enters \`upgradeVaultImplementation\`, the call bottoms out in \`Ownable: caller is not the owner\`. The lock is on-chain irreversible. \* \`isVaultUpgradesLocked()\` derives from the beacon's owner, so it stays accurate even if the factory is later replaced or local tooling forgets state. \`\_getGuardian()\` is provided by \`VaultFactoryBaseV2\` and resolves to the Flap Guardian address per chain — the address does not need to be hardcoded. Any additional permissioned function added to the factory or vault later should be gated the same way. ### Deployment Both reference scripts construct the factory; the implementation and beacon are deployed inline: \`\`\`solidity // script/mainnet/bnb/DeployFreeCoinBeacon.s.sol function run() external { vm.startBroadcast(); FreeCoinVaultBeaconFactory factory = new FreeCoinVaultBeaconFactory(); console.log("FreeCoinVaultBeaconFactory deployed at:", address(factory)); console.log("UpgradeableBeacon deployed at: ", factory.beacon()); console.log("Beacon implementation deployed at: ", factory.beaconImplementation()); vm.stopBroadcast(); } \`\`\` Run them with: \`\`\`bash # Mainnet forge script script/mainnet/bnb/DeployFreeCoinBeacon.s.sol:DeployFreeCoinBeacon \\ --rpc-url https://bsc-dataseed.bnbchain.org --broadcast --verify --private-key # Testnet forge script script/testnet/bnb/DeployFreeCoinBeacon.s.sol:DeployFreeCoinBeacon \\ --rpc-url https://bsc-testnet-dataseed.bnbchain.org --broadcast --verify --private-key \`\`\` VaultPortal is permissionless, so the factory does not need to be registered anywhere after deployment. To clear the warning flag on Flap.sh, contact the Flap team to schedule the third-party audit. ## Required tests For an upgradeable vault, the test surface is bigger than for a plain vault: alongside the usual logic and dispatch tests, the upgrade-authority model must be asserted directly. The reference test file \[\`test/FreeCoinBeacon.mainnet.t.sol\`\](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoinBeacon.mainnet.t.sol) covers the proxy / upgrade / lock surface. The bonding-curve and post-DEX dispatch tests are in \[\`test/FreeCoin.mainnet.t.sol\`\](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoin.mainnet.t.sol) and apply unchanged here, because the integration surface is identical (same VaultPortal, same TaxProcessor dispatch). Minimum coverage expected before audit: | # | Scenario | What to assert | Reference test | | - | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | 1 | \*\*Registration\*\* — factory deploys an initialized proxy when called by VaultPortal | \`taxToken\`, \`maxReward\`, \`cooldown\` set; non-VaultPortal callers rejected | \`test\_beaconFactoryDeploysInitializedProxyVault\`, \`test\_factoryRejectsNonVaultPortalCaller\` | | 2 | \*\*Initializer is locked\*\* — implementation cannot be re-initialized | \`initialize()\` reverts on both proxy and bare implementation after first call | \`test\_beaconProxyClaimAndInitializerLock\` | | 3 | \*\*Bonding-curve buy → dispatch → vault\*\* | Buy on BC, call \`dispatch()\`, assert vault BNB balance increased | \`test\_buyOnBCAndDispatch\` (in \`FreeCoin.mainnet.t.sol\`) | | 4 | \*\*DEX graduation → sell → dispatch → vault\*\* | Buy until graduation (\`status == 4\`), sell on DEX, dispatch, assert vault received BNB | \`test\_graduateAndDispatchPostDEX\` (in \`FreeCoin.mainnet.t.sol\`) | | 5 | \*\*Guardian can upgrade the beacon\*\* | Non-Guardian callers revert; Guardian successfully changes \`beaconImplementation()\` | \`test\_guardianCanUpgradeBeaconImplementation\`, \`test\_nonGuardianCannotUpgradeBeaconImplementation\` | | 6 | \*\*Guardian can lock the beacon, and lock is irreversible\*\* | After \`lockVaultUpgrades()\`, \`isVaultUpgradesLocked() == true\` and any further upgrade reverts at the beacon's \`onlyOwner\` check | \`test\_guardianCanLockVaultUpgrades\`, \`test\_lockingIsIrreversibleAndIdempotentlyReverts\` | | 7 | \*\*Vault logic & gating\*\* | Vault's own business rules (claim, cooldown, double-claim, failed transfer rollback, getters) | \`test\_claimCooldownAndDoubleClaimReverts\`, \`test\_claimRevertsWhenRecipientRejectsNativeTransfer\`, etc. | | 8 | \*\*Schema sanity\*\* | \`vaultUISchema()\` and \`vaultDataSchema()\` return the shape the UI expects; \`factorySpecVersion()\` is \`"v2.2"\`; \`onBeforeLaunch(bytes)\` rejects non-native quote tokens with the correct reason | \`test\_vaultUISchemaMetadata\`, \`test\_factoryMetadataAndValidationHelpers\` | ### Why both bonding-curve and DEX tests are required The vault's \`receive()\` is invoked indirectly through \`TaxProcessor.dispatch()\`. The dispatch pipeline behaves slightly differently in each phase: \* \*\*Bonding curve.\*\* Each buy or sell on Portal applies the buy or sell tax, accumulates BNB on the \`TaxProcessor\`, and \`dispatch()\` flushes it to the vault. Tests verify that dispatch reaches the vault and that \`receive()\` does not revert under that gas budget. \* \*\*Post-DEX (graduated).\*\* Once the bonding curve crosses the \`FOUR\_FIFTHS\` threshold the token graduates to DEX. Sell-side tax is then collected through the token's own swap-and-distribute path, accumulated on the \`TaxProcessor\`, and dispatched. Tests verify that the same vault still receives BNB after graduation, since this is the path that runs for the entire post-launch lifetime of the token. Both phases must be covered. A vault that handles bonding-curve dispatch correctly but reverts after DEX graduation is broken in production, and that bug is invisible without a graduation test. A condensed sketch of the integration tests (full code in \[\`test/FreeCoin.mainnet.t.sol\`\](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoin.mainnet.t.sol)): \`\`\`solidity // 1. Bonding curve function test\_buyOnBCAndDispatch() public { uint256 vaultBefore = address(vault).balance; vm.startPrank(user1); \_buyOnBC(token, 5 ether); vm.stopPrank(); \_dispatchTax(token); // flush TaxProcessor → vault assertGt(address(vault).balance, vaultBefore); } // 2. Post-DEX function test\_graduateAndDispatchPostDEX() public { vm.startPrank(user1); \_buyOnBC(token, 5 ether); vm.stopPrank(); \_dispatchTax(token); vm.startPrank(user2); \_buyOnBC(token, 15 ether); vm.stopPrank(); // ...buy more if not yet graduated... assertEq(portal.getTokenV8Safe(token).status, 4, "should have graduated"); uint256 vaultBefore = address(vault).balance; vm.startPrank(user1); require(IERC20(token).transfer(token, 400\_000 \* 1e18)); // seed liquidation threshold \_sell(token, IERC20(token).balanceOf(user1) - 400\_000 \* 1e18); vm.stopPrank(); \_dispatchTax(token); assertGt(address(vault).balance, vaultBefore); } \`\`\` {% hint style="warning" %} Always wrap multi-call helpers like \`\_sell()\` and \`\_buyOnBC()\` in \`vm.startPrank(...)\` / \`vm.stopPrank()\`. \`vm.prank()\` only covers the \*\*next\*\* external call, so the second internal call (e.g. \`swapExactInput\` after \`approve\`) silently runs as the wrong sender and produces confusing reverts. See the \[FlapVaultExample README\](https://github.com/flap-sh/FlapVaultExample#prank-convention-in-tests) for the full convention. {% endhint %} ### Tests for the upgrade and lock authority Excerpts from \[\`test/FreeCoinBeacon.mainnet.t.sol\`\](https://github.com/flap-sh/FlapVaultExample/blob/main/test/FreeCoinBeacon.mainnet.t.sol): \`\`\`solidity function test\_guardianCanUpgradeBeaconImplementation() public { FreeCoinVaultUpgradeable nextImplementation = new FreeCoinVaultUpgradeable(); vm.prank(TESTNET\_GUARDIAN); factory.upgradeVaultImplementation(address(nextImplementation)); assertEq(factory.beaconImplementation(), address(nextImplementation)); } function test\_nonGuardianCannotUpgradeBeaconImplementation() public { FreeCoinVaultUpgradeable nextImplementation = new FreeCoinVaultUpgradeable(); vm.expectRevert(bytes(unicode"Only Guardian / 仅限 Guardian")); vm.prank(USER); factory.upgradeVaultImplementation(address(nextImplementation)); } function test\_guardianCanLockVaultUpgrades() public { assertTrue(!factory.isVaultUpgradesLocked()); vm.prank(TESTNET\_GUARDIAN); factory.lockVaultUpgrades(); assertTrue(factory.isVaultUpgradesLocked()); // Further upgrades fail at the beacon's onlyOwner check, even when called by the Guardian FreeCoinVaultUpgradeable nextImplementation = new FreeCoinVaultUpgradeable(); vm.expectRevert(bytes("Ownable: caller is not the owner")); vm.prank(TESTNET\_GUARDIAN); factory.upgradeVaultImplementation(address(nextImplementation)); } \`\`\` These three tests are the minimum set demonstrating that the authority model holds: the Guardian is the only path in, non-Guardian callers are rejected, and locking is genuinely irreversible. ## Pre-audit checklist \* \[ \] Implementation calls \`\_disableInitializers()\` in its constructor. \* \[ \] \`initialize(...)\` is the only place that sets vault state, and it has the \`initializer\` modifier. \* \[ \] Factory deploys the implementation and \`UpgradeableBeacon\` in its own constructor; the beacon owner is the factory itself. \* \[ \] \`upgradeVaultImplementation(address)\` is gated to \`\_getGuardian()\` — no other caller passes. \* \[ \] \`lockVaultUpgrades()\` is gated to \`\_getGuardian()\` and renounces beacon ownership. \* \[ \] No other upgrade path exists on the factory (no admin EOA, no proxy admin, no extra owner, no parallel multisig). \* \[ \] Tests assert: registration via VaultPortal, initializer lockdown, bonding-curve dispatch, post-DEX dispatch, Guardian upgrade success, non-Guardian upgrade rejection, lock irreversibility. \* \[ \] All tests pass against a BSC fork (\`forge test --fork-url https://bsc-dataseed.bnbchain.org\`). \* \[ \] The \[\`flap-vault-spec-checker\`\](https://github.com/flap-sh/FlapVaultExample/tree/main/.agents/skills/flap-vault-spec-checker) Copilot skill reports no ❌ findings. Once those are green, contact the Flap team to schedule the third-party audit and clear the warning flag on Flap.sh. See the \[Vault & VaultFactory specification\](/flap/developers/vault-developers/vault-and-vaultfactory-specification.md#get-your-vault-verified) page for the verification process. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/audit-reports.md). # Audit Reports \* The Flap Launchpad Protocol V2, V4 and Flap Tax Token V1 were audited by Certik. Check the audit report on\[ Certik Skynet\](https://skynet.certik.com/projects/flap). \* Flap Tax Token V1 (Part of the V4 Protocol) passed a comprehensive security audit by BlockSec. \* Flap Launchpad Protocol V5 (including Flap Tax Token V2 and Flap PreLaunch V1) passed a comprehensive security audit by BlockSec. {% file src="/files/CWIFdhUbnZ19pU4znsUe" %} {% file src="/files/P8NIjZbfzFTXxOTvSgGo" %} --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/vault-developers/deployed-contract-addresses.md). # Deployed Contract Addresses See \[Deployed Contract Addresses\](/flap/developers/deployed-contract-addresses.md). --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/wallet-and-terminal-and-bot-developers/token-version-specification.md). # Token Version Specification ### Overview The \`TokenVersion\` field is an enum that indicates which token implementation is being used for a specific token. This field is crucial for determining the capabilities and features available for each token launched through the Portal contract. ### TokenVersion Enum Values \`\`\`solidity /// @dev Token version /// Which token implementation is used enum TokenVersion { TOKEN\_LEGACY\_MINT\_NO\_PERMIT, TOKEN\_LEGACY\_MINT\_NO\_PERMIT\_DUPLICATE, // for historical reasons, both 0 and 1 are the same: TOKEN\_LEGACY\_MINT\_NO\_PERMIT TOKEN\_V2\_PERMIT, // 2 TOKEN\_GOPLUS, // 3 TOKEN\_TAXED, // 4: The original tax token (FlapTaxToken) TOKEN\_TAXED\_V2, // 5: The new advanced tax token (FlapTaxTokenV2) TOKEN\_TAXED\_V3, // 6: The next-generation tax token with asymmetric buy/sell rates (FlapTaxTokenV3) TOKEN\_V3\_PERMIT // 7: Non-tax token using TokenV3 implementation with permit support } \`\`\` The \`TokenVersion\` has the following values: | Value | Name | Description | | ----- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | 0 | \`TOKEN\_LEGACY\_MINT\_NO\_PERMIT\` | Legacy token implementation without permit functionality | | 1 | \`TOKEN\_LEGACY\_MINT\_NO\_PERMIT\_DUPLICATE\` | Historical duplicate (identical to value 0) | | 2 | \`TOKEN\_V2\_PERMIT\` | V2 token implementation with EIP-2612 permit support | | 3 | \`TOKEN\_GOPLUS\` | Token implementation with GoPlus security integration | | 4 | \`TOKEN\_TAXED\` | Original tax token implementation (FlapTaxToken) | | 5 | \`TOKEN\_TAXED\_V2\` | Advanced tax token implementation (FlapTaxTokenV2) | | 6 | \`TOKEN\_TAXED\_V3\` | Next-generation tax token with asymmetric buy/sell rates (FlapTaxTokenV3) | | 7 | \`TOKEN\_V3\_PERMIT\` | Non-tax TokenV3 implementation with permit support, designed for PancakeSwap Infinity CL or Uniswap v4 style integrations | #### Version Details \* \*\*TOKEN\\\_LEGACY\\\_MINT\\\_NO\\\_PERMIT (0 & 1)\*\*: The obsolete token implementation. Due to historical reasons, both values 0 and 1 represent the same implementation type. \* \*\*TOKEN\\\_V2\\\_PERMIT (2)\*\*: Enhanced token with permit functionality allowing gasless approvals via signed messages (EIP-2612 standard). \* \*\*TOKEN\\\_GOPLUS (3)\*\*: Token integrated with GoPlus security features for enhanced safety and verification. (Not Used in current deployments) \* \*\*TOKEN\\\_TAXED (4)\*\*: The first generation tax token (\`FlapTaxToken\`) that implements tax mechanisms on transfers. \* \*\*TOKEN\\\_TAXED\\\_V2 (5)\*\*: The second generation tax token (\`FlapTaxTokenV2\`) with advanced tax features and improvements over the original tax token. \* \*\*TOKEN\\\_TAXED\\\_V3 (6)\*\*: The third generation tax token (\`FlapTaxTokenV3\`) with asymmetric buy/sell tax rates, commission receiver support, and bidirectional dynamic liquidation threshold. Launched via \`newTokenV6\`. This is the recommended token type for all new integrations. \* \*\*TOKEN\\\_V3\\\_PERMIT (7)\*\*: A zero-tax token (\`taxRate = 0\`) using the TokenV3 implementation with permit support. This version is intended for launches that combine with PancakeSwap Infinity CL or Uniswap v4 style liquidity flows, and is created via \`newTokenV7\`. ### How to Get Token Version There are two primary methods to retrieve the token version for a specific token: #### Method 1: Index from Events The protocol emits a \`TokenVersionSet\` event whenever a token's version is set or updated. You can listen to this event or query historical events to determine a token's version. \*\*Event Definition:\*\* \`\`\`solidity event TokenVersionSet(address token, TokenVersion version); \`\`\` \*\*Example Usage:\*\* \* Listen for \`TokenVersionSet\` events on the Portal contract \* Filter events by the token address \* The \`version\` parameter contains the \`TokenVersion\` enum value #### Method 2: Using getTokenV\\\* Methods The Portal contract provides multiple view functions to query token state, each returning progressively more fields. All of these methods include the \`tokenVersion\` field in their return values. \*\*Available Methods:\*\* 1. \*\*getTokenV5\*\* - Returns \`TokenStateV5\` structure \`\`\`solidity function getTokenV5(address token) external view returns (TokenStateV5 memory state); \`\`\` 2. \*\*getTokenV6\*\* - Returns \`TokenStateV6\` structure \`\`\`solidity function getTokenV6(address token) external view returns (TokenStateV6 memory state); \`\`\` 3. \*\*getTokenV7\*\* - Returns \`TokenStateV7\` structure \`\`\`solidity function getTokenV7(address token) external view returns (TokenStateV7 memory state); \`\`\` 4. \*\*getTokenV8\*\* - Returns \`TokenStateV8\` structure with asymmetric \`buyTaxRate\` and \`sellTaxRate\` (BNB mainnet & testnet only) \`\`\`solidity function getTokenV8(address token) external view returns (TokenStateV8 memory state); \`\`\` 5. \*\*getTokenV8Safe\*\* - Returns \`TokenStateV8Safe\` with enum fields as \`uint8\` for forward compatibility (BNB mainnet & testnet only) \`\`\`solidity function getTokenV8Safe(address token) external view returns (TokenStateV8Safe memory state); \`\`\` \*\*All TokenState structures include:\*\* \* \`TokenVersion tokenVersion\` - The version of the token implementation \*\*Example Usage:\*\* \`\`\`solidity // Using getTokenV8 on BNB mainnet/testnet (recommended — includes asymmetric tax rates) TokenStateV8 memory state = portal.getTokenV8(tokenAddress); TokenVersion version = state.tokenVersion; uint256 buyTax = state.buyTaxRate; uint256 sellTax = state.sellTaxRate; // Using getTokenV7 on other networks TokenStateV7 memory state = portal.getTokenV7(tokenAddress); TokenVersion version = state.tokenVersion; \`\`\` --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/vault-developers/quick-start-vault-developers.md). # Quick start for Vault Developers Welcome to the Vault Developers guide! This section will help you get started with developing and integrating with Flap's vault system. ## Overview Flap's vault system allows you to build a smart contract for managing and distributing BNB revenue generated from tax tokens. Vaults can be customized to fit various use cases, such as splitting revenue among multiple recipients or routing funds based on social proof, or funding a game treasury. The only limitation is your creativity! {% hint style="info" %} \*\*Permissionless vault creation\*\* — You can now create and deploy your own vaults and vault factories \*\*without any permission or registration\*\*. Vault factories no longer need to be registered in the VaultPortal before they can be used to launch tokens. {% endhint %} {% hint style="success" %} Work through the \[FlapVaultExample repository\](https://github.com/flap-sh/FlapVaultExample) for a complete working example of a vault and vault factory implementation. It is the fastest way to understand the patterns you need to follow. {% endhint %} {% hint style="info" %} Once you have written your vault, use the \[FlapVaultSpecChecker\](https://github.com/flap-sh/FlapVaultSpecChecker) AI toolkit to automatically verify that your implementation correctly follows the specification before deploying. {% endhint %} ## Available vault types Flap currently supports the following official vault types: 1. \*\*Split Vault\*\* - Distributes BNB revenue across multiple recipients based on configurable basis points (percentage shares) 2. \*\*Gift Vault (FlapXVault)\*\* - Routes revenue based on X (Twitter) proof verification with fallback to snowball buyback ## Next steps \* Review the \[Vault & VaultFactory specification\](/flap/developers/vault-developers/vault-and-vaultfactory-specification.md) to understand how to build your own vaults and vault factories \* Read the \[Flap Tax Vault\](/flap/developers/vault-developers/flap-tax-vault.md) documentation for an overview of the vault concept and VaultPortal integration \* Explore registered (i.e, verified by Flap) vaults in the \[Registered Vaults\](/flap/developers/token-launcher-developers/registered-vaults.md) page to see available vault factories and their configuration schemas \* Learn about the \[Gift Vault (FlapXVault)\](/flap/developers/vault-developers/gift-vault.md) and how to integrate X proof-based revenue routing ## Common workflows 1. \*\*Launch a token with a custom vault\*\* — You can launch a token and set the fund recipient wallet to your smart contract. If it follows the spec defined in the \[Vault & VaultFactory specification\](/flap/developers/vault-developers/vault-and-vaultfactory-specification.md), its info will be automatically available on our website and partner websites. No registration is required. 2. \*\*Build a vault factory for others\*\* — If you have an idea to build a vault to be used by others, you can build a \`VaultFactoryBaseV2\` and take a portion of the tax as a commission in each vault you create. Implement \`vaultDataSchema()\` so the UI can automatically render a launch form for your vault type. See the \[Vault & VaultFactory specification\](/flap/developers/vault-developers/vault-and-vaultfactory-specification.md) for full details. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/preview.md). # Preview This section contains previews of upcoming products and features by Flap. Content here is subject to change before final release. --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/token-launcher-developers/registered-vaults.md). # Registered vaults ## Registered vaults This page lists registered vault factories and the expected \`vaultData\` schema for each factory. ### BNB mainnet | name | description | is official (yes/no) | vaultFactory address | vaultData schema description | interface | | ----------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Split Vault | Splits received BNB across up to 10 recipients based on basis points and allows dispatch/claim. | yes | 0xfab75Dc774cB9B38b91749B8833360B46a52345F | ABI-encode \`SplitVault.Recipient\[\]\` where each item is \`{address recipient, uint16 bps}\`. \`recipient\` is the payout address (must be non-zero and unique across the array). \`bps\` is the share in basis points (1% = 100, 100% = 10,000). The array length must be 1–10 and total bps must sum to 10,000. | \[misc/interfaces/vaults/SplitVault.sol\](https://github.com/flap-sh/gitbook/blob/main/developers/token-launcher-developers/misc/interfaces/vaults/SplitVault.sol) | | Gift Vault (FlapXVault) | A gift vault that routes tax token revenue based on X (Twitter) proof and can fall back to snowball buyback. | yes | 0x025549F52B03cF36f9e1a337c02d3AA7Af66ab32 | ABI-encode \`VaultData\` with \`{string xHandle}\` (the fee manager’s X handle). The \`xHandle\` must be all lowercase. | \[Gift Vault Guide\](/flap/developers/vault-developers/gift-vault.md) | ### BNB testnet | name | description | is official (yes/no) | vaultFactory address | vaultData schema description | interface | | ----------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Split Vault | Splits received BNB across up to 10 recipients based on basis points and allows dispatch/claim. | yes | 0x1ae091F75D593eb7dC6539600a185C8A6076A424 | ABI-encode \`SplitVault.Recipient\[\]\` where each item is \`{address recipient, uint16 bps}\`. \`recipient\` is the payout address (must be non-zero and unique across the array). \`bps\` is the share in basis points (1% = 100, 100% = 10,000). The array length must be 1–10 and total bps must sum to 10,000. | \[misc/interfaces/vaults/SplitVault.sol\](https://github.com/flap-sh/gitbook/blob/main/developers/token-launcher-developers/misc/interfaces/vaults/SplitVault.sol) | | Gift Vault (FlapXVault) | A gift vault that routes tax token revenue based on X (Twitter) proof and can fall back to snowball buyback. | yes | 0xa02DA44D67DB6D692efa7f751b5952bd670d5326 | ABI-encode \`VaultData\` with \`{string xHandle}\` (the fee manager’s X handle). The \`xHandle\` must be all lowercase. | \[Gift Vault Guide\](/flap/developers/vault-developers/gift-vault.md) | ## Interfaces ### Split Vault interface \`\`\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /// @title ISplitVault /// @notice Interface for the Split Vault implementation. /// @dev Derived from the on-chain SplitVault contract. interface ISplitVault { /// @notice A payout recipient with a basis-point share. /// @param recipient The payout address (non-zero and unique). /// @param bps Share in basis points (1% = 100, 100% = 10,000). struct Recipient { address recipient; uint16 bps; } /// @notice Balance tracking for a recipient. /// @param accumulated Total BNB credited to the user. /// @param claimed Total BNB claimed by the user. struct UserBalance { uint128 accumulated; uint128 claimed; } /// @notice View structure returned by \`getRecipientsInfo()\`. /// @param recipient The payout address. /// @param bps Share in basis points. /// @param accumulated Total BNB credited to the user. /// @param claimed Total BNB claimed by the user. struct RecipientInfo { address recipient; uint16 bps; uint128 accumulated; uint128 claimed; } /// @notice Emitted when BNB is received and split across recipients. /// @param amount The total amount distributed. event FlapSplitVaultDistributed(uint256 amount); /// @notice Emitted when a recipient is dispatched funds. /// @param recipient The recipient address. /// @param amount The amount dispatched. event FlapSplitVaultDispatched(address recipient, uint256 amount); /// @notice Emitted when a recipient claims funds manually. /// @param user The recipient address. /// @param amount The amount claimed. event FlapSplitVaultClaimed(address user, uint256 amount); /// @notice Error when number of recipients exceeds the limit. error TooManyRecipients(); /// @notice Error when the total basis points is not 10,000. error InvalidBpsSum(); /// @notice Error when a recipient address is zero. error ZeroRecipient(); /// @notice Error when no recipients are provided. error NoRecipients(); /// @notice Error when recipients are duplicated. error DuplicateRecipient(); /// @notice Initialize the vault after cloning. /// @param \_taxToken The associated tax token address. /// @param \_recipients Array of recipients and their shares. function initialize(address \_taxToken, Recipient\[\] calldata \_recipients) external; /// @notice Dispatch claimable balances to all recipients. function dispatch() external; /// @notice Claim the caller’s accumulated balance. /// @param user The recipient address to claim for. function claim(address user) external; /// @notice Return detailed information for all recipients. /// @return info Array of recipient info entries. function getRecipientsInfo() external view returns (RecipientInfo\[\] memory info); /// @notice Retrieve a recipient by index. /// @param index The recipient index. /// @return recipient The recipient address. /// @return bps The recipient share in basis points. function recipients(uint256 index) external view returns (address recipient, uint16 bps); /// @notice Retrieve user balance totals for a recipient. /// @param user The recipient address. /// @return accumulated Total BNB credited to the user. /// @return claimed Total BNB claimed by the user. function userBalances(address user) external view returns (uint128 accumulated, uint128 claimed); /// @notice The associated tax token address. function taxToken() external view returns (address); /// @notice The factory address that created this vault. function factory() external view returns (address); /// @notice The total basis points constant (10,000). function TOTAL\_BPS() external view returns (uint256); } \`\`\` ### Gift Vault (FlapXVault) interface For detailed API integration guide and the complete interface, see the \[Gift Vault Guide\](/flap/developers/vault-developers/gift-vault.md). --- # Unknown \> For the complete documentation index, see \[llms.txt\](https://docs.flap.sh/flap/llms.txt). Markdown versions of documentation pages are available by appending \`.md\` to page URLs; this page is available as \[Markdown\](https://docs.flap.sh/flap/developers/preview/worldcup-viewer.md). # World Cup Viewer ## Overview \`WorldCupViewer\` is a read-only smart contract deployed on BSC mainnet that provides a clean, high-level view of \*\*2026 FIFA World Cup\*\* match results. Users and integrators can query \`WorldCupViewer\` to find out the winner of the entire tournament or the winner of any specific group, without needing to understand the underlying oracle structure. {% hint style="info" %} \`WorldCupViewer\` is a read-only contract. All state — including match results and team names — is either stored in the contract or read live from the oracle. No transactions are required to query match results. {% endhint %} \*\*\* ## Deployed address | Network | Address | | ----------- | -------------------------------------------- | | BSC mainnet | \`0x00036192958C2aaAF9F445d3Cdc2979995EA333e\` | \*\*\* ## Methods All query methods return a \`MatchViewResult\` struct: \`\`\`solidity struct MatchViewResult { uint256 matchId; // the match ID queried string matchName; // human-readable name of the match bool isResolved; // true if the oracle has settled this match uint256 teamId; // ID of the winning team (0 if not yet resolved; 50 = draw/tie; 49 = others) string teamName; // name of the winning team ("" if not yet resolved; "draw" for ties; "others" for unlisted teams) } \`\`\` {% hint style="info" %} \`teamId\` uses two reserved values: \`49\` (\`others\`) for any team not individually tracked by the oracle, and \`50\` (\`draw\`) when a match ends in a tie. {% endhint %} ### \`getWorldCupWinner()\` \`\`\`solidity function getWorldCupWinner() external view returns (MatchViewResult memory); \`\`\` Returns the winner of the 2026 FIFA World Cup (match ID \`1\`). \* If \`isResolved = false\`, the tournament winner has not yet been determined. \* If \`isResolved = true\`, \`teamId\` and \`teamName\` identify the champion. {% hint style="info" %} If the champion is a team not individually tracked by the oracle (i.e., grouped under "Others"), the contract resolves the winner using the separately configured \`othersWinner\` field, which is set by the contract owner. {% endhint %} \*\*\* ### \`getGroupMatchWinners(uint256 matchId)\` \`\`\`solidity function getGroupMatchWinners(uint256 matchId) external view returns (MatchViewResult memory); \`\`\` Returns the winner of a group stage match. Pass the match ID for the group you want to query (e.g., \`2\` for Group A, \`3\` for Group B, and so on up to \`13\` for Group L). \* If \`isResolved = false\`, the group winner has not yet been determined. \* If \`isResolved = true\`, \`teamId\` and \`teamName\` identify the group winner. \*\*\* ### \`getMatchResult(uint256 matchId)\` \`\`\`solidity function getMatchResult(uint256 matchId) external view returns (MatchViewResult memory); \`\`\` Returns the resolved result for any match ID directly from the underlying oracle mapping. \* If \`isResolved = false\`, the match is not settled yet and the result remains pending. \* If \`isResolved = true\`, \`teamId\` identifies the winning outcome mapped from oracle request IDs. \* For a draw outcome (\`teamId = 50\`), \`teamName\` is returned as a tie message in the format \`TeamA(teamIdA) and TeamB(teamIdB) are tied\`. \* For non-draw outcomes, \`teamName\` is the mapped team name for that \`teamId\`. \*\*\* ### \`getTeamName(uint256 teamId)\` \`\`\`solidity function getTeamName(uint256 teamId) external view returns (string memory); \`\`\` Looks up the name for a given \`teamId\`. The standard query range is \`1\`-\`48\` (listed teams). If \`teamId = 49\`, it represents \`"others"\`; if \`teamId = 50\`, it represents \`"draw"\`. Unknown IDs return an empty string. \*\*\* ## Reference data ### Team ID mapping | teamId | Team | | ------ | -------------------------- | | 1 | Mexico | | 2 | South Africa | | 3 | South Korea | | 4 | Czechia | | 5 | Canada | | 6 | Bosnia and Herzegovina | | 7 | Qatar | | 8 | Switzerland | | 9 | Brazil | | 10 | Morocco | | 11 | Haiti | | 12 | Scotland | | 13 | USA | | 14 | Paraguay | | 15 | Australia | | 16 | Türkiye | | 17 | Germany | | 18 | Curaçao | | 19 | Ivory Coast | | 20 | Ecuador | | 21 | Netherlands | | 22 | Japan | | 23 | Sweden | | 24 | Tunisia | | 25 | Belgium | | 26 | Egypt | | 27 | Iran | | 28 | New Zealand | | 29 | Spain | | 30 | Cape Verde | | 31 | Saudi Arabia | | 32 | Uruguay | | 33 | France | | 34 | Senegal | | 35 | Iraq | | 36 | Norway | | 37 | Argentina | | 38 | Algeria | | 39 | Austria | | 40 | Jordan | | 41 | Portugal | | 42 | DR Congo | | 43 | Uzbekistan | | 44 | Colombia | | 45 | England | | 46 | Croatia | | 47 | Ghana | | 48 | Panama | | 49 | Others (any unlisted team) | | 50 | Draw | \*\*\* ### Match ID reference {% hint style="info" %} More matches will be added to this document as the tournament progresses. {% endhint %} #### Chapter 1: winner (Match 1) #### Match 1 — 2026 FIFA World Cup Winner {% hint style="warning" %} Do \*\*not\*\* use \`getMatchResult(1)\` to determine the 2026 FIFA World Cup champion.\\ Instead, always use \`getWorldCupWinner()\`, which fully resolves the tournament winner even if the oracle data merges several teams under "Others".\\ Using \`getMatchResult(1)\` returns the raw oracle grouping and may not identify the actual team; \`getWorldCupWinner()\` always provides the true winner team ID and name. {% endhint %} Resolves to the team that wins the entire 2026 FIFA World Cup. This match tracks all individually listed teams plus an "Others" catch-all for any team not given their own oracle question. | teamId | Team | Oracle question title | | ------ | ------------ | ---------------------------------------------------------- | | 29 | Spain | Will Spain win the 2026 FIFA World Cup? | | 33 | France | Will France win the 2026 FIFA World Cup? | | 45 | England | Will England win the 2026 FIFA World Cup? | | 37 | Argentina | Will Argentina win the 2026 FIFA World Cup? | | 9 | Brazil | Will Brazil win the 2026 FIFA World Cup? | | 41 | Portugal | Will Portugal win the 2026 FIFA World Cup? | | 17 | Germany | Will Germany win the 2026 FIFA World Cup? | | 21 | Netherlands | Will Netherlands win the 2026 FIFA World Cup? | | 36 | Norway | Will Norway win the 2026 FIFA World Cup? | | 25 | Belgium | Will Belgium win the 2026 FIFA World Cup? | | 13 | USA | Will USA win the 2026 FIFA World Cup? | | 10 | Morocco | Will Morocco win the 2026 FIFA World Cup? | | 44 | Colombia | Will Colombia win the 2026 FIFA World Cup? | | 22 | Japan | Will Japan win the 2026 FIFA World Cup? | | 32 | Uruguay | Will Uruguay win the 2026 FIFA World Cup? | | 46 | Croatia | Will Croatia win the 2026 FIFA World Cup? | | 1 | Mexico | Will Mexico win the 2026 FIFA World Cup? | | 8 | Switzerland | Will Switzerland win the 2026 FIFA World Cup? | | 20 | Ecuador | Will Ecuador win the 2026 FIFA World Cup? | | 34 | Senegal | Will Senegal win the 2026 FIFA World Cup? | | 15 | Australia | Will Australia win the 2026 FIFA World Cup? | | 5 | Canada | Will Canada win the 2026 FIFA World Cup? | | 12 | Scotland | Will Scotland win the 2026 FIFA World Cup? | | 3 | South Korea | Will South Korea win the 2026 FIFA World Cup? | | 14 | Paraguay | Will Paraguay win the 2026 FIFA World Cup? | | 19 | Ivory Coast | Will Ivory Coast win the 2026 FIFA World Cup? | | 26 | Egypt | Will Egypt win the 2026 FIFA World Cup? | | 27 | Iran | Will Iran win the 2026 FIFA World Cup? | | 47 | Ghana | Will Ghana win the 2026 FIFA World Cup? | | 38 | Algeria | Will Algeria win the 2026 FIFA World Cup? | | 24 | Tunisia | Will Tunisia win the 2026 FIFA World Cup? | | 39 | Austria | Will Austria win the 2026 FIFA World Cup? | | 28 | New Zealand | Will New Zealand win the 2026 FIFA World Cup? | | 11 | Haiti | Will Haiti win the 2026 FIFA World Cup? | | 40 | Jordan | Will Jordan win the 2026 FIFA World Cup? | | 18 | Curaçao | Will Curaçao win the 2026 FIFA World Cup? | | 43 | Uzbekistan | Will Uzbekistan win the 2026 FIFA World Cup? | | 2 | South Africa | Will South Africa win the 2026 FIFA World Cup? | | 30 | Cape Verde | Will Cape Verde win the 2026 FIFA World Cup? | | 7 | Qatar | Will Qatar win the 2026 FIFA World Cup? | | 31 | Saudi Arabia | Will Saudi Arabia win the 2026 FIFA World Cup? | | — | Others | Will none of the listed teams win the 2026 FIFA World Cup? | \*\*\* {% hint style="info" %} For all group winner matches (Match 2–Match 13), you can use either \`getMatchResult(matchId)\` or \`getGroupMatchWinners(matchId)\`. Both will return the same result. {% endhint %} #### Chapter 2: group match winners (Match 2-13) #### Match 2 — Group A Winner Resolves to the team that wins Group A in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | ------------ | -------------------------------------------------------------------------------------------------------------------------- | | 1 | Mexico | Will Mexico win Group A in the 2026 FIFA World Cup? | | 2 | South Africa | Will South Africa win Group A in the 2026 FIFA World Cup? | | 3 | South Korea | Will South Korea win Group A in the 2026 FIFA World Cup? | | 4 | Czechia | Will the winner of the Czechia/Denmark/North Macedonia/Republic of Ireland playoff win Group A in the 2026 FIFA World Cup? | \*\*\* #### Match 3 — Group B Winner Resolves to the team that wins Group B in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------- | | 5 | Canada | Will Canada win Group B in the 2026 World Cup? | | 6 | Bosnia and Herzegovina | Will the winner of the Bosnia and Herzegovina/Italy/Northern Ireland/Wales playoff win Group B in the 2026 World Cup? | | 7 | Qatar | Will Qatar win Group B in the 2026 World Cup? | | 8 | Switzerland | Will Switzerland win Group B in the 2026 World Cup? | \*\*\* #### Match 4 — Group C Winner Resolves to the team that wins Group C in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | -------- | ------------------------------------------------ | | 9 | Brazil | Will Brazil win Group C in the 2026 World Cup? | | 10 | Morocco | Will Morocco win Group C in the 2026 World Cup? | | 11 | Haiti | Will Haiti win Group C in the 2026 World Cup? | | 12 | Scotland | Will Scotland win Group C in the 2026 World Cup? | \*\*\* #### Match 5 — Group D Winner Resolves to the team that wins Group D in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | --------- | ------------------------------------------------------------------------------------------------- | | 13 | USA | Will USA win Group D in the 2026 World Cup? | | 14 | Paraguay | Will Paraguay win Group D in the 2026 World Cup? | | 15 | Australia | Will Australia win Group D in the 2026 World Cup? | | 16 | Türkiye | Will the winner of the Kosovo/Romania/Slovakia/Türkiye playoff win Group D in the 2026 World Cup? | \*\*\* #### Match 6 — Group E Winner Resolves to the team that wins Group E in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | ----------- | --------------------------------------------------- | | 17 | Germany | Will Germany win Group E in the 2026 World Cup? | | 18 | Curaçao | Will Curaçao win Group E in the 2026 World Cup? | | 19 | Ivory Coast | Will Ivory Coast win Group E in the 2026 World Cup? | | 20 | Ecuador | Will Ecuador win Group E in the 2026 World Cup? | \*\*\* #### Match 7 — Group F Winner Resolves to the team that wins Group F in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | ----------- | ----------------------------------------------------------------------------------------------- | | 21 | Netherlands | Will Netherlands win Group F in the 2026 World Cup? | | 22 | Japan | Will Japan win Group F in the 2026 World Cup? | | 23 | Sweden | Will the winner of the Albania/Poland/Sweden/Ukraine playoff win Group F in the 2026 World Cup? | | 24 | Tunisia | Will Tunisia win Group F in the 2026 World Cup? | \*\*\* #### Match 8 — Group G Winner Resolves to the team that wins Group G in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | ----------- | --------------------------------------------------- | | 25 | Belgium | Will Belgium win Group G in the 2026 World Cup? | | 26 | Egypt | Will Egypt win Group G in the 2026 World Cup? | | 27 | Iran | Will Iran win Group G in the 2026 World Cup? | | 28 | New Zealand | Will New Zealand win Group G in the 2026 World Cup? | \*\*\* #### Match 9 — Group H Winner Resolves to the team that wins Group H in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | ------------ | ---------------------------------------------------- | | 29 | Spain | Will Spain win Group H in the 2026 World Cup? | | 30 | Cape Verde | Will Cape Verde win Group H in the 2026 World Cup? | | 31 | Saudi Arabia | Will Saudi Arabia win Group H in the 2026 World Cup? | | 32 | Uruguay | Will Uruguay win Group H in the 2026 World Cup? | \*\*\* #### Match 10 — Group I Winner Resolves to the team that wins Group I in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | ------- | --------------------------------------------------------------------------------------- | | 33 | France | Will France win Group I in the 2026 World Cup? | | 34 | Senegal | Will Senegal win Group I in the 2026 World Cup? | | 35 | Iraq | Will the winner of the Bolivia/Iraq/Suriname playoff win Group I in the 2026 World Cup? | | 36 | Norway | Will Norway win Group I in the 2026 World Cup? | \*\*\* #### Match 11 — Group J Winner Resolves to the team that wins Group J in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | --------- | ------------------------------------------------- | | 37 | Argentina | Will Argentina win Group J in the 2026 World Cup? | | 38 | Algeria | Will Algeria win Group J in the 2026 World Cup? | | 39 | Austria | Will Austria win Group J in the 2026 World Cup? | | 40 | Jordan | Will Jordan win Group J in the 2026 World Cup? | \*\*\* #### Match 12 — Group K Winner Resolves to the team that wins Group K in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | ---------- | -------------------------------------------------------------------------------------------------------------------- | | 41 | Portugal | Will Portugal win Group K in the 2026 World Cup? | | 42 | DR Congo | Will the winner of the Democratic Republic of Congo/Jamaica/New Caledonia playoff win Group K in the 2026 World Cup? | | 43 | Uzbekistan | Will Uzbekistan win Group K in the 2026 World Cup? | | 44 | Colombia | Will Colombia win Group K in the 2026 World Cup? | \*\*\* #### Match 13 — Group L Winner Resolves to the team that wins Group L in the 2026 FIFA World Cup group stage. | teamId | Team | Oracle question title | | ------ | ------- | ----------------------------------------------- | | 45 | England | Will England win Group L in the 2026 World Cup? | | 46 | Croatia | Will Croatia win Group L in the 2026 World Cup? | | 47 | Ghana | Will Ghana win Group L in the 2026 World Cup? | | 48 | Panama | Will Panama win Group L in the 2026 World Cup? | \*\*\* #### Chapter 3: group matches (Match 14-85) #### Match 14 — Mexico vs. South Africa Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------------ | --------------------------------------------------------------------------------------------- | | 1 | Mexico | Will Mexico win on 2026-06-11 in Group A of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Mexico vs. South Africa end in a draw in Group A of the 2026 FIFA World Cup group stage? | | 2 | South Africa | Will South Africa win on 2026-06-11 in Group A of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 15 — South Korea vs. Czechia Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | ------------------------------------------------------------------------------------------------ | | 3 | South Korea | Will Korea Republic win on 2026-06-11 in Group A of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Korea Republic vs. Czechia end in a draw in Group A of the 2026 FIFA World Cup group stage? | | 4 | Czechia | Will Czechia win on 2026-06-11 in Group A of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 16 — Canada vs. Bosnia and Herzegovina Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ---------------------- | ------------------------------------------------------------------------------------------------------- | | 5 | Canada | Will Canada win on 2026-06-12 in Group B of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Canada vs. Bosnia and Herzegovina end in a draw in Group B of the 2026 FIFA World Cup group stage? | | 6 | Bosnia and Herzegovina | Will Bosnia and Herzegovina win on 2026-06-12 in Group B of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 17 — USA vs. Paraguay Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | -------- | ------------------------------------------------------------------------------------------------ | | 13 | USA | Will United States win on 2026-06-12 in Group D of the 2026 FIFA World Cup group stage? | | 50 | draw | Will United States vs. Paraguay end in a draw in Group D of the 2026 FIFA World Cup group stage? | | 14 | Paraguay | Will Paraguay win on 2026-06-12 in Group D of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 18 — Qatar vs. Switzerland Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | ------------------------------------------------------------------------------------------- | | 7 | Qatar | Will Qatar win on 2026-06-13 in Group B of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Qatar vs. Switzerland end in a draw in Group B of the 2026 FIFA World Cup group stage? | | 8 | Switzerland | Will Switzerland win on 2026-06-13 in Group B of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 19 — Brazil vs. Morocco Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ---------------------------------------------------------------------------------------- | | 9 | Brazil | Will Brazil win on 2026-06-13 in Group C of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Brazil vs. Morocco end in a draw in Group C of the 2026 FIFA World Cup group stage? | | 10 | Morocco | Will Morocco win on 2026-06-13 in Group C of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 20 — Haiti vs. Scotland Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | -------- | ---------------------------------------------------------------------------------------- | | 11 | Haiti | Will Haiti win on 2026-06-13 in Group C of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Haiti vs. Scotland end in a draw in Group C of the 2026 FIFA World Cup group stage? | | 12 | Scotland | Will Scotland win on 2026-06-13 in Group C of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 21 — Australia vs. Türkiye Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | --------- | ------------------------------------------------------------------------------------------- | | 15 | Australia | Will Australia win on 2026-06-14 in Group D of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Australia vs. Türkiye end in a draw in Group D of the 2026 FIFA World Cup group stage? | | 16 | Türkiye | Will Türkiye win on 2026-06-14 in Group D of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 22 — Germany vs. Curaçao Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ----------------------------------------------------------------------------------------- | | 17 | Germany | Will Germany win on 2026-06-14 in Group E of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Germany vs. Curaçao end in a draw in Group E of the 2026 FIFA World Cup group stage? | | 18 | Curaçao | Will Curaçao win on 2026-06-14 in Group E of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 23 — Netherlands vs. Japan Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | ------------------------------------------------------------------------------------------- | | 21 | Netherlands | Will Netherlands win on 2026-06-14 in Group F of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Netherlands vs. Japan end in a draw in Group F of the 2026 FIFA World Cup group stage? | | 22 | Japan | Will Japan win on 2026-06-14 in Group F of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 24 — Ivory Coast vs. Ecuador Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | ----------------------------------------------------------------------------------------------- | | 19 | Ivory Coast | Will Côte d'Ivoire win on 2026-06-14 in Group E of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Côte d'Ivoire vs. Ecuador end in a draw in Group E of the 2026 FIFA World Cup group stage? | | 20 | Ecuador | Will Ecuador win on 2026-06-14 in Group E of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 25 — Sweden vs. Tunisia Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ---------------------------------------------------------------------------------------- | | 23 | Sweden | Will Sweden win on 2026-06-14 in Group F of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Sweden vs. Tunisia end in a draw in Group F of the 2026 FIFA World Cup group stage? | | 24 | Tunisia | Will Tunisia win on 2026-06-14 in Group F of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 26 — Spain vs. Cape Verde Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ---------- | ------------------------------------------------------------------------------------------ | | 29 | Spain | Will Spain win on 2026-06-15 in Group H of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Spain vs. Cabo Verde end in a draw in Group H of the 2026 FIFA World Cup group stage? | | 30 | Cape Verde | Will Cabo Verde win on 2026-06-15 in Group H of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 27 — Belgium vs. Egypt Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | --------------------------------------------------------------------------------------- | | 25 | Belgium | Will Belgium win on 2026-06-15 in Group G of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Belgium vs. Egypt end in a draw in Group G of the 2026 FIFA World Cup group stage? | | 26 | Egypt | Will Egypt win on 2026-06-15 in Group G of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 28 — Saudi Arabia vs. Uruguay Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------------ | ---------------------------------------------------------------------------------------------- | | 31 | Saudi Arabia | Will Saudi Arabia win on 2026-06-15 in Group H of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Saudi Arabia vs. Uruguay end in a draw in Group H of the 2026 FIFA World Cup group stage? | | 32 | Uruguay | Will Uruguay win on 2026-06-15 in Group H of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 29 — Iran vs. New Zealand Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | --------------------------------------------------------------------------------------------- | | 27 | Iran | Will IR Iran win on 2026-06-15 in Group G of the 2026 FIFA World Cup group stage? | | 50 | draw | Will IR Iran vs. New Zealand end in a draw in Group G of the 2026 FIFA World Cup group stage? | | 28 | New Zealand | Will New Zealand win on 2026-06-15 in Group G of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 30 — France vs. Senegal Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ---------------------------------------------------------------------------------------- | | 33 | France | Will France win on 2026-06-16 in Group I of the 2026 FIFA World Cup group stage? | | 50 | draw | Will France vs. Senegal end in a draw in Group I of the 2026 FIFA World Cup group stage? | | 34 | Senegal | Will Senegal win on 2026-06-16 in Group I of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 31 — Iraq vs. Norway Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------ | ------------------------------------------------------------------------------------- | | 35 | Iraq | Will Iraq win on 2026-06-16 in Group I of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Iraq vs. Norway end in a draw in Group I of the 2026 FIFA World Cup group stage? | | 36 | Norway | Will Norway win on 2026-06-16 in Group I of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 32 — Argentina vs. Algeria Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | --------- | ------------------------------------------------------------------------------------------- | | 37 | Argentina | Will Argentina win on 2026-06-16 in Group J of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Argentina vs. Algeria end in a draw in Group J of the 2026 FIFA World Cup group stage? | | 38 | Algeria | Will Algeria win on 2026-06-16 in Group J of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 33 — Austria vs. Jordan Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ---------------------------------------------------------------------------------------- | | 39 | Austria | Will Austria win on 2026-06-17 in Group J of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Austria vs. Jordan end in a draw in Group J of the 2026 FIFA World Cup group stage? | | 40 | Jordan | Will Jordan win on 2026-06-17 in Group J of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 34 — Portugal vs. DR Congo Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | -------- | ------------------------------------------------------------------------------------------- | | 41 | Portugal | Will Portugal win on 2026-06-17 in Group K of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Portugal vs. DR Congo end in a draw in Group K of the 2026 FIFA World Cup group stage? | | 42 | DR Congo | Will DR Congo win on 2026-06-17 in Group K of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 35 — England vs. Croatia Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ----------------------------------------------------------------------------------------- | | 45 | England | Will England win on 2026-06-17 in Group L of the 2026 FIFA World Cup group stage? | | 50 | draw | Will England vs. Croatia end in a draw in Group L of the 2026 FIFA World Cup group stage? | | 46 | Croatia | Will Croatia win on 2026-06-17 in Group L of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 36 — Ghana vs. Panama Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------ | -------------------------------------------------------------------------------------- | | 47 | Ghana | Will Ghana win on 2026-06-17 in Group L of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Ghana vs. Panama end in a draw in Group L of the 2026 FIFA World Cup group stage? | | 48 | Panama | Will Panama win on 2026-06-17 in Group L of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 37 — Uzbekistan vs. Colombia Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ---------- | --------------------------------------------------------------------------------------------- | | 43 | Uzbekistan | Will Uzbekistan win on 2026-06-17 in Group K of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Uzbekistan vs. Colombia end in a draw in Group K of the 2026 FIFA World Cup group stage? | | 44 | Colombia | Will Colombia win on 2026-06-17 in Group K of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 38 — Czechia vs. South Africa Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------------ | ---------------------------------------------------------------------------------------------- | | 4 | Czechia | Will Czechia win on 2026-06-18 in Group A of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Czechia vs. South Africa end in a draw in Group A of the 2026 FIFA World Cup group stage? | | 2 | South Africa | Will South Africa win on 2026-06-18 in Group A of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 39 — Switzerland vs. Bosnia and Herzegovina Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ---------------------- | ------------------------------------------------------------------------------------------------------------ | | 8 | Switzerland | Will Switzerland win on 2026-06-18 in Group B of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Switzerland vs. Bosnia and Herzegovina end in a draw in Group B of the 2026 FIFA World Cup group stage? | | 6 | Bosnia and Herzegovina | Will Bosnia and Herzegovina win on 2026-06-18 in Group B of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 40 — Canada vs. Qatar Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------ | -------------------------------------------------------------------------------------- | | 5 | Canada | Will Canada win on 2026-06-18 in Group B of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Canada vs. Qatar end in a draw in Group B of the 2026 FIFA World Cup group stage? | | 7 | Qatar | Will Qatar win on 2026-06-18 in Group B of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 41 — Mexico vs. South Korea Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | ----------------------------------------------------------------------------------------------- | | 1 | Mexico | Will Mexico win on 2026-06-18 in Group A of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Mexico vs. Korea Republic end in a draw in Group A of the 2026 FIFA World Cup group stage? | | 3 | South Korea | Will Korea Republic win on 2026-06-18 in Group A of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 42 — USA vs. Australia Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | --------- | ------------------------------------------------------------------------------------------------- | | 13 | USA | Will United States win on 2026-06-19 in Group D of the 2026 FIFA World Cup group stage? | | 50 | draw | Will United States vs. Australia end in a draw in Group D of the 2026 FIFA World Cup group stage? | | 15 | Australia | Will Australia win on 2026-06-19 in Group D of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 43 — Scotland vs. Morocco Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | -------- | ------------------------------------------------------------------------------------------ | | 12 | Scotland | Will Scotland win on 2026-06-19 in Group C of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Scotland vs. Morocco end in a draw in Group C of the 2026 FIFA World Cup group stage? | | 10 | Morocco | Will Morocco win on 2026-06-19 in Group C of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 44 — Brazil vs. Haiti Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------ | -------------------------------------------------------------------------------------- | | 9 | Brazil | Will Brazil win on 2026-06-19 in Group C of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Brazil vs. Haiti end in a draw in Group C of the 2026 FIFA World Cup group stage? | | 11 | Haiti | Will Haiti win on 2026-06-19 in Group C of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 45 — Türkiye vs. Paraguay Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | -------- | ------------------------------------------------------------------------------------------ | | 16 | Türkiye | Will Türkiye win on 2026-06-19 in Group D of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Türkiye vs. Paraguay end in a draw in Group D of the 2026 FIFA World Cup group stage? | | 14 | Paraguay | Will Paraguay win on 2026-06-19 in Group D of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 46 — Netherlands vs. Sweden Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | -------------------------------------------------------------------------------------------- | | 21 | Netherlands | Will Netherlands win on 2026-06-20 in Group F of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Netherlands vs. Sweden end in a draw in Group F of the 2026 FIFA World Cup group stage? | | 23 | Sweden | Will Sweden win on 2026-06-20 in Group F of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 47 — Germany vs. Ivory Coast Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | ----------------------------------------------------------------------------------------------- | | 17 | Germany | Will Germany win on 2026-06-20 in Group E of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Germany vs. Côte d'Ivoire end in a draw in Group E of the 2026 FIFA World Cup group stage? | | 19 | Ivory Coast | Will Côte d'Ivoire win on 2026-06-20 in Group E of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 48 — Ecuador vs. Curaçao Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ----------------------------------------------------------------------------------------- | | 20 | Ecuador | Will Ecuador win on 2026-06-20 in Group E of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Ecuador vs. Curaçao end in a draw in Group E of the 2026 FIFA World Cup group stage? | | 18 | Curaçao | Will Curaçao win on 2026-06-20 in Group E of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 49 — Tunisia vs. Japan Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | --------------------------------------------------------------------------------------- | | 24 | Tunisia | Will Tunisia win on 2026-06-21 in Group F of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Tunisia vs. Japan end in a draw in Group F of the 2026 FIFA World Cup group stage? | | 22 | Japan | Will Japan win on 2026-06-21 in Group F of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 50 — Spain vs. Saudi Arabia Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------------ | -------------------------------------------------------------------------------------------- | | 29 | Spain | Will Spain win on 2026-06-21 in Group H of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Spain vs. Saudi Arabia end in a draw in Group H of the 2026 FIFA World Cup group stage? | | 31 | Saudi Arabia | Will Saudi Arabia win on 2026-06-21 in Group H of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 51 — Belgium vs. Iran Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ----------------------------------------------------------------------------------------- | | 25 | Belgium | Will Belgium win on 2026-06-21 in Group G of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Belgium vs. IR Iran end in a draw in Group G of the 2026 FIFA World Cup group stage? | | 27 | Iran | Will IR Iran win on 2026-06-21 in Group G of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 52 — Uruguay vs. Cape Verde Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ---------- | -------------------------------------------------------------------------------------------- | | 32 | Uruguay | Will Uruguay win on 2026-06-21 in Group H of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Uruguay vs. Cabo Verde end in a draw in Group H of the 2026 FIFA World Cup group stage? | | 30 | Cape Verde | Will Cabo Verde win on 2026-06-21 in Group H of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 53 — New Zealand vs. Egypt Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | ------------------------------------------------------------------------------------------- | | 28 | New Zealand | Will New Zealand win on 2026-06-21 in Group G of the 2026 FIFA World Cup group stage? | | 50 | draw | Will New Zealand vs. Egypt end in a draw in Group G of the 2026 FIFA World Cup group stage? | | 26 | Egypt | Will Egypt win on 2026-06-21 in Group G of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 54 — Argentina vs. Austria Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | --------- | ------------------------------------------------------------------------------------------- | | 37 | Argentina | Will Argentina win on 2026-06-22 in Group J of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Argentina vs. Austria end in a draw in Group J of the 2026 FIFA World Cup group stage? | | 39 | Austria | Will Austria win on 2026-06-22 in Group J of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 55 — France vs. Iraq Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------ | ------------------------------------------------------------------------------------- | | 33 | France | Will France win on 2026-06-22 in Group I of the 2026 FIFA World Cup group stage? | | 50 | draw | Will France vs. Iraq end in a draw in Group I of the 2026 FIFA World Cup group stage? | | 35 | Iraq | Will Iraq win on 2026-06-22 in Group I of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 56 — Norway vs. Senegal Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ---------------------------------------------------------------------------------------- | | 36 | Norway | Will Norway win on 2026-06-22 in Group I of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Norway vs. Senegal end in a draw in Group I of the 2026 FIFA World Cup group stage? | | 34 | Senegal | Will Senegal win on 2026-06-22 in Group I of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 57 — Jordan vs. Algeria Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ---------------------------------------------------------------------------------------- | | 40 | Jordan | Will Jordan win on 2026-06-22 in Group J of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Jordan vs. Algeria end in a draw in Group J of the 2026 FIFA World Cup group stage? | | 38 | Algeria | Will Algeria win on 2026-06-22 in Group J of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 58 — Portugal vs. Uzbekistan Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ---------- | --------------------------------------------------------------------------------------------- | | 41 | Portugal | Will Portugal win on 2026-06-23 in Group K of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Portugal vs. Uzbekistan end in a draw in Group K of the 2026 FIFA World Cup group stage? | | 43 | Uzbekistan | Will Uzbekistan win on 2026-06-23 in Group K of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 59 — England vs. Ghana Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | --------------------------------------------------------------------------------------- | | 45 | England | Will England win on 2026-06-23 in Group L of the 2026 FIFA World Cup group stage? | | 50 | draw | Will England vs. Ghana end in a draw in Group L of the 2026 FIFA World Cup group stage? | | 47 | Ghana | Will Ghana win on 2026-06-23 in Group L of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 60 — Panama vs. Croatia Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ---------------------------------------------------------------------------------------- | | 48 | Panama | Will Panama win on 2026-06-23 in Group L of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Panama vs. Croatia end in a draw in Group L of the 2026 FIFA World Cup group stage? | | 46 | Croatia | Will Croatia win on 2026-06-23 in Group L of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 61 — Colombia vs. DR Congo Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | -------- | ------------------------------------------------------------------------------------------- | | 44 | Colombia | Will Colombia win on 2026-06-23 in Group K of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Colombia vs. DR Congo end in a draw in Group K of the 2026 FIFA World Cup group stage? | | 42 | DR Congo | Will DR Congo win on 2026-06-23 in Group K of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 62 — Bosnia and Herzegovina vs. Qatar Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ---------------------- | ------------------------------------------------------------------------------------------------------ | | 6 | Bosnia and Herzegovina | Will Bosnia and Herzegovina win on 2026-06-24 in Group B of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Bosnia and Herzegovina vs. Qatar end in a draw in Group B of the 2026 FIFA World Cup group stage? | | 7 | Qatar | Will Qatar win on 2026-06-24 in Group B of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 63 — Switzerland vs. Canada Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | -------------------------------------------------------------------------------------------- | | 8 | Switzerland | Will Switzerland win on 2026-06-24 in Group B of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Switzerland vs. Canada end in a draw in Group B of the 2026 FIFA World Cup group stage? | | 5 | Canada | Will Canada win on 2026-06-24 in Group B of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 64 — Morocco vs. Haiti Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | --------------------------------------------------------------------------------------- | | 10 | Morocco | Will Morocco win on 2026-06-24 in Group C of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Morocco vs. Haiti end in a draw in Group C of the 2026 FIFA World Cup group stage? | | 11 | Haiti | Will Haiti win on 2026-06-24 in Group C of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 65 — Scotland vs. Brazil Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | -------- | ----------------------------------------------------------------------------------------- | | 12 | Scotland | Will Scotland win on 2026-06-24 in Group C of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Scotland vs. Brazil end in a draw in Group C of the 2026 FIFA World Cup group stage? | | 9 | Brazil | Will Brazil win on 2026-06-24 in Group C of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 66 — Czechia vs. Mexico Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ---------------------------------------------------------------------------------------- | | 4 | Czechia | Will Czechia win on 2026-06-24 in Group A of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Czechia vs. Mexico end in a draw in Group A of the 2026 FIFA World Cup group stage? | | 1 | Mexico | Will Mexico win on 2026-06-24 in Group A of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 67 — South Africa vs. South Korea Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------------ | ----------------------------------------------------------------------------------------------------- | | 2 | South Africa | Will South Africa win on 2026-06-24 in Group A of the 2026 FIFA World Cup group stage? | | 50 | draw | Will South Africa vs. Korea Republic end in a draw in Group A of the 2026 FIFA World Cup group stage? | | 3 | South Korea | Will Korea Republic win on 2026-06-24 in Group A of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 68 — Curaçao vs. Ivory Coast Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | ----------------------------------------------------------------------------------------------- | | 18 | Curaçao | Will Curaçao win on 2026-06-25 in Group E of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Curaçao vs. Côte d'Ivoire end in a draw in Group E of the 2026 FIFA World Cup group stage? | | 19 | Ivory Coast | Will Côte d'Ivoire win on 2026-06-25 in Group E of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 69 — Ecuador vs. Germany Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ----------------------------------------------------------------------------------------- | | 20 | Ecuador | Will Ecuador win on 2026-06-25 in Group E of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Ecuador vs. Germany end in a draw in Group E of the 2026 FIFA World Cup group stage? | | 17 | Germany | Will Germany win on 2026-06-25 in Group E of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 70 — Japan vs. Sweden Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------ | -------------------------------------------------------------------------------------- | | 22 | Japan | Will Japan win on 2026-06-25 in Group F of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Japan vs. Sweden end in a draw in Group F of the 2026 FIFA World Cup group stage? | | 23 | Sweden | Will Sweden win on 2026-06-25 in Group F of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 71 — Tunisia vs. Netherlands Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | --------------------------------------------------------------------------------------------- | | 24 | Tunisia | Will Tunisia win on 2026-06-25 in Group F of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Tunisia vs. Netherlands end in a draw in Group F of the 2026 FIFA World Cup group stage? | | 21 | Netherlands | Will Netherlands win on 2026-06-25 in Group F of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 72 — Türkiye vs. USA Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ----------------------------------------------------------------------------------------------- | | 16 | Türkiye | Will Türkiye win on 2026-06-25 in Group D of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Türkiye vs. United States end in a draw in Group D of the 2026 FIFA World Cup group stage? | | 13 | USA | Will United States win on 2026-06-25 in Group D of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 73 — Paraguay vs. Australia Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | --------- | -------------------------------------------------------------------------------------------- | | 14 | Paraguay | Will Paraguay win on 2026-06-25 in Group D of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Paraguay vs. Australia end in a draw in Group D of the 2026 FIFA World Cup group stage? | | 15 | Australia | Will Australia win on 2026-06-25 in Group D of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 74 — Senegal vs. Iraq Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | -------------------------------------------------------------------------------------- | | 34 | Senegal | Will Senegal win on 2026-06-26 in Group I of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Senegal vs. Iraq end in a draw in Group I of the 2026 FIFA World Cup group stage? | | 35 | Iraq | Will Iraq win on 2026-06-26 in Group I of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 75 — Norway vs. France Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------ | --------------------------------------------------------------------------------------- | | 36 | Norway | Will Norway win on 2026-06-26 in Group I of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Norway vs. France end in a draw in Group I of the 2026 FIFA World Cup group stage? | | 33 | France | Will France win on 2026-06-26 in Group I of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 76 — Cape Verde vs. Saudi Arabia Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------------ | ------------------------------------------------------------------------------------------------- | | 30 | Cape Verde | Will Cabo Verde win on 2026-06-26 in Group H of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Cabo Verde vs. Saudi Arabia end in a draw in Group H of the 2026 FIFA World Cup group stage? | | 31 | Saudi Arabia | Will Saudi Arabia win on 2026-06-26 in Group H of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 77 — Uruguay vs. Spain Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | --------------------------------------------------------------------------------------- | | 32 | Uruguay | Will Uruguay win on 2026-06-26 in Group H of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Uruguay vs. Spain end in a draw in Group H of the 2026 FIFA World Cup group stage? | | 29 | Spain | Will Spain win on 2026-06-26 in Group H of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 78 — Egypt vs. Iran Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----- | --------------------------------------------------------------------------------------- | | 26 | Egypt | Will Egypt win on 2026-06-26 in Group G of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Egypt vs. IR Iran end in a draw in Group G of the 2026 FIFA World Cup group stage? | | 27 | Iran | Will IR Iran win on 2026-06-26 in Group G of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 79 — New Zealand vs. Belgium Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ----------- | --------------------------------------------------------------------------------------------- | | 28 | New Zealand | Will New Zealand win on 2026-06-26 in Group G of the 2026 FIFA World Cup group stage? | | 50 | draw | Will New Zealand vs. Belgium end in a draw in Group G of the 2026 FIFA World Cup group stage? | | 25 | Belgium | Will Belgium win on 2026-06-26 in Group G of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 80 — Croatia vs. Ghana Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | --------------------------------------------------------------------------------------- | | 46 | Croatia | Will Croatia win on 2026-06-27 in Group L of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Croatia vs. Ghana end in a draw in Group L of the 2026 FIFA World Cup group stage? | | 47 | Ghana | Will Ghana win on 2026-06-27 in Group L of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 81 — Panama vs. England Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ---------------------------------------------------------------------------------------- | | 48 | Panama | Will Panama win on 2026-06-27 in Group L of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Panama vs. England end in a draw in Group L of the 2026 FIFA World Cup group stage? | | 45 | England | Will England win on 2026-06-27 in Group L of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 82 — Colombia vs. Portugal Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | -------- | ------------------------------------------------------------------------------------------- | | 44 | Colombia | Will Colombia win on 2026-06-27 in Group K of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Colombia vs. Portugal end in a draw in Group K of the 2026 FIFA World Cup group stage? | | 41 | Portugal | Will Portugal win on 2026-06-27 in Group K of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 83 — DR Congo vs. Uzbekistan Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ---------- | --------------------------------------------------------------------------------------------- | | 42 | DR Congo | Will DR Congo win on 2026-06-27 in Group K of the 2026 FIFA World Cup group stage? | | 50 | draw | Will DR Congo vs. Uzbekistan end in a draw in Group K of the 2026 FIFA World Cup group stage? | | 43 | Uzbekistan | Will Uzbekistan win on 2026-06-27 in Group K of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 84 — Jordan vs. Argentina Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | --------- | ------------------------------------------------------------------------------------------ | | 40 | Jordan | Will Jordan win on 2026-06-27 in Group J of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Jordan vs. Argentina end in a draw in Group J of the 2026 FIFA World Cup group stage? | | 37 | Argentina | Will Argentina win on 2026-06-27 in Group J of the 2026 FIFA World Cup group stage? | \*\*\* #### Match 85 — Algeria vs. Austria Resolves to the winner of this match in the 2026 FIFA World Cup. | teamId | Team | Oracle question title | | ------ | ------- | ----------------------------------------------------------------------------------------- | | 38 | Algeria | Will Algeria win on 2026-06-27 in Group J of the 2026 FIFA World Cup group stage? | | 50 | draw | Will Algeria vs. Austria end in a draw in Group J of the 2026 FIFA World Cup group stage? | | 39 | Austria | Will Austria win on 2026-06-27 in Group J of the 2026 FIFA World Cup group stage? | \*\*\* ---