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.
1) Prepare token metadata
Token metadata is stored on IPFS. You can upload your image and metadata JSON through the Flap upload 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.
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:
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 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.
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 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:
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.
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().
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
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):
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.
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.
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.
/// @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);
/// 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,
};
}
// 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);
}