BNB Price: $619.07 (+2.34%)
 

Overview

BNB Balance

BNB Smart Chain LogoBNB Smart Chain LogoBNB Smart Chain Logo0 BNB

BNB Value

$0.00

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To

There are no matching entries

> 10 Internal Transactions and > 10 Token Transfers found.

Latest 25 internal transactions (View All)

Parent Transaction Hash Block From To
925234762026-04-14 17:06:064 mins ago1776186366
0xC5098ac7...F1D8B9F2A
0.00024984 BNB
925234762026-04-14 17:06:064 mins ago1776186366
0xC5098ac7...F1D8B9F2A
0.00002776 BNB
925234762026-04-14 17:06:064 mins ago1776186366
0xC5098ac7...F1D8B9F2A
0.0002776 BNB
925218212026-04-14 16:53:4017 mins ago1776185620
0xC5098ac7...F1D8B9F2A
0.00058668 BNB
925218212026-04-14 16:53:4017 mins ago1776185620
0xC5098ac7...F1D8B9F2A
0.00006518 BNB
925218212026-04-14 16:53:4017 mins ago1776185620
0xC5098ac7...F1D8B9F2A
0.00065187 BNB
925212562026-04-14 16:49:2621 mins ago1776185366
0xC5098ac7...F1D8B9F2A
0.00002691 BNB
925212562026-04-14 16:49:2621 mins ago1776185366
0xC5098ac7...F1D8B9F2A
0.00000299 BNB
925212562026-04-14 16:49:2621 mins ago1776185366
0xC5098ac7...F1D8B9F2A
0.0000299 BNB
925209442026-04-14 16:47:0523 mins ago1776185225
0xC5098ac7...F1D8B9F2A
0.00029713 BNB
925209442026-04-14 16:47:0523 mins ago1776185225
0xC5098ac7...F1D8B9F2A
0.00003301 BNB
925209442026-04-14 16:47:0523 mins ago1776185225
0xC5098ac7...F1D8B9F2A
0.00033015 BNB
925207432026-04-14 16:45:3525 mins ago1776185135
0xC5098ac7...F1D8B9F2A
0.00570683 BNB
925207432026-04-14 16:45:3525 mins ago1776185135
0xC5098ac7...F1D8B9F2A
0.00063409 BNB
925207432026-04-14 16:45:3525 mins ago1776185135
0xC5098ac7...F1D8B9F2A
0.00634092 BNB
925202432026-04-14 16:41:5028 mins ago1776184910
0xC5098ac7...F1D8B9F2A
0.0044887 BNB
925202432026-04-14 16:41:5028 mins ago1776184910
0xC5098ac7...F1D8B9F2A
0.00049874 BNB
925202432026-04-14 16:41:5028 mins ago1776184910
0xC5098ac7...F1D8B9F2A
0.00498745 BNB
925185932026-04-14 16:29:2641 mins ago1776184166
0xC5098ac7...F1D8B9F2A
0.00004015 BNB
925185932026-04-14 16:29:2641 mins ago1776184166
0xC5098ac7...F1D8B9F2A
0.00000446 BNB
925185932026-04-14 16:29:2641 mins ago1776184166
0xC5098ac7...F1D8B9F2A
0.00004461 BNB
925184812026-04-14 16:28:3642 mins ago1776184116
0xC5098ac7...F1D8B9F2A
0.00186151 BNB
925184812026-04-14 16:28:3642 mins ago1776184116
0xC5098ac7...F1D8B9F2A
0.00020683 BNB
925184812026-04-14 16:28:3642 mins ago1776184116
0xC5098ac7...F1D8B9F2A
0.00206834 BNB
925184422026-04-14 16:28:1842 mins ago1776184098
0xC5098ac7...F1D8B9F2A
0.00004764 BNB
View All Internal Transactions
Cross-Chain Transactions
Loading...
Loading

Minimal Proxy Contract for 0xafd086148c75714a0646397b557a12ad63f8348c

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0x9375bdDD...647BCcF44
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
TaxProcessor

Compiler Version
v0.8.24+commit.e11b9ed9

Optimization Enabled:
Yes with 99999 runs

Other Settings:
cancun EvmVersion, MIT license

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 20 : TaxProcessor.sol
// SPDX-License-Identifier: MIT

pragma solidity =0.8.24;

import "@openzeppelin-contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin-contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {IDividend} from "src/interfaces/Tax/IDividend.sol";
import {IPortal, IPortalTradeV2, IPortalTypes} from "src/interfaces/IPortal.sol";
import {BuyBackGuardUpgradeable} from "./BuyBackGuardUpgradeable.sol";
import {ISwapRegistry} from "src/interfaces/Tax/ISwapRegistry.sol";
import {IMultiDexRouter} from "src/interfaces/IMultiDexRouter.sol";
import {
    ITaxProcessor,
    TaxProcessorInitParams,
    PackedFeeConfig,
    PackedFeeConfigV2
} from "src/interfaces/Tax/ITaxProcessor.sol";

interface IWETH {
    function withdraw(uint256) external;
    function deposit() external payable;
}

interface IUniswapV2Factory {
    function getPair(address tokenA, address tokenB) external view returns (address pair);
}

interface IUniswapRouter02 {
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);

    function getAmountsOut(uint256 amountIn, address[] calldata path)
        external
        view
        returns (uint256[] memory amounts);

    function factory() external pure returns (address);
}

/// @title TaxProcessorBase
/// @notice Abstract base contract containing shared state, events, modifiers and constructor
///         for TaxProcessor and TaxProcessorDispatchImpl.
abstract contract TaxProcessorBase is OwnableUpgradeable, ReentrancyGuardUpgradeable, BuyBackGuardUpgradeable {
    using SafeERC20 for IERC20;

    // --- Constants ---
    /// @notice Dead address for burning deflation tokens
    address public constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;

    // --- Immutable Storage ---
    /// @notice WETH address for ETH conversion (immutable)
    address public immutable weth;

    /// @notice FlapBlackHole address (immutable)
    address public immutable flapBlackHole;

    /// @notice Portal address for swapping (immutable)
    address public immutable portal;

    /// @notice SwapRegistry address for dividend token swap path lookup (immutable).
    /// @dev Set in constructor so all clones share the same registry address.
    ///      Required when dividendToken != quoteToken and dividendToken != taxToken (Case 3).
    ///      May be address(0) for deployments that do not support custom dividend tokens.
    address public immutable swapRegistry;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor(address weth_, address flapBlackHole_, address portal_, address swapRegistry_) {
        require(weth_ != address(0), "TaxProcessor: zero WETH address");
        require(portal_ != address(0), "TaxProcessor: zero portal address");
        weth = weth_;
        flapBlackHole = flapBlackHole_;
        portal = portal_;
        swapRegistry = swapRegistry_;
        _disableInitializers();
    }

    // --- Storage ---
    /// @notice Quote token (WETH or other ERC20)
    address public quoteToken;

    /// @notice Tax token (the token that calls processing)
    address public taxToken;

    /// @notice Uniswap V2 router address
    address public router;

    /// @notice Fee receiver address
    address public feeReceiver;

    /// @notice Market receiver address (only set if marketBps > 0)
    address public marketAddress;

    /// @notice Dividend contract address (only set if dividendBps > 0)
    address public dividendAddress;

    /// @notice Accumulated quote token balance for fee
    uint256 public feeQuoteBalance;

    /// @notice Accumulated quote token balance for lp
    uint256 public lpQuoteBalance;

    /// @notice Accumulated quote token balance for market
    uint256 public marketQuoteBalance;

    /// @notice Accumulated quote token balance pending conversion to dividend token
    uint256 public pendingDividendQuoteTokenBalance;

    /// @notice Internal gas-optimized struct containing all fee configuration including commission
    /// @dev Packed into a single 256-bit storage slot for gas efficiency.
    /// Field breakdown:
    ///   - marketBps:     16 bits (basis points, max 65535)
    ///   - deflationBps:  16 bits (basis points, max 65535)
    ///   - lpBps:         16 bits (basis points, max 65535)
    ///   - dividendBps:   16 bits (basis points, max 65535)
    ///   - feeRate:       16 bits (basis points, max 65535)
    ///   - isWeth:         8 bits (boolean, padded to 8 bits for alignment)
    ///   - commissionBps: 16 bits (basis points, max 65535)
    /// Total: 16 + 16 + 16 + 16 + 16 + 8 + 16 = 104 bits (fits in one storage slot)
    struct PackedFeeConfigInternal {
        uint16 marketBps;
        uint16 deflationBps;
        uint16 lpBps;
        uint16 dividendBps;
        uint16 feeRate;
        bool isWeth;
        uint16 commissionBps;
    }

    /// @notice All fee-related configuration (including commission) packed into a single storage slot
    PackedFeeConfigInternal internal _feeConfig;

    /// @notice Total dividend tokens deposited to dividend contract
    uint256 public totalDividendTokenSent;

    /// @notice Total quote token added to liquidity
    uint256 public totalQuoteAddedToLiquidity;

    /// @notice Total tax token added to liquidity
    uint256 public totalTokenAddedToLiquidity;

    /// @notice Total quote token sent to marketing wallet
    uint256 public totalQuoteSentToMarketing;

    /// @notice Pre-bonding burn funds (deflation portion from bonding curve tax)
    uint256 public preBondBurnFunds;

    /// @notice Minimum buy back quote amount threshold
    uint256 public minBuyBackQuote;

    /// @notice Maximum gas limit for buy back operations
    uint256 public maxBuyBackGasLimit;

    // --- V3 Storage ---
    /// @notice The dividend token address.
    ///   address(0)  = use quoteToken (V2 behaviour).
    ///   taxToken    = self-dividend (Case 1): accumulated tax tokens sent directly.
    ///   other ERC20 = custom (Case 3): quoteToken is swapped via converter before deposit.
    address public dividendToken;

    /// @notice Optional commission receiver. address(0) = disabled.
    address public commissionReceiver;

    /// @notice Accumulated quote-token commission waiting to be dispatched.
    uint256 public commissionQuoteBalance;

    /// @notice MEV-protected converter address for Case 3 dividend swaps.
    /// @dev Only calls originating from this address may trigger the swap.
    address public converter;

    /// @notice Reference output amount used to determine liquidation-threshold direction.
    uint256 public liqExpectedOutputAmount;

    /// @notice Accumulated dividend tokens waiting to be deposited to dividend contract.
    /// @dev Holds the actual dividend token (taxToken for Case 1, quoteToken for Case 2 after conversion).
    uint256 public dividendTokenBalance;

    // --- Events ---
    event FlapTaxProcessorDispatchExecuted(
        address indexed taxToken, uint256 feeAmount, uint256 marketAmount, uint256 dividendAmount
    );
    event FlapTaxProcessorBurnExecuted(address indexed taxToken, uint256 quoteAmount, uint256 tokensBurned);
    event FlapTaxProcessorBuyBackSkipped(address indexed taxToken, uint256 quoteAmount, uint256 gasLeft, string reason);
    event FlapTaxProcessorPortalRefund(address indexed taxToken, uint256 refundAmount, bool isWeth);
    event FlapTaxProcessorDividendDepositSkipped(address indexed taxToken, uint256 amount, string reason);
    event FlapTaxProcessorTokensBurned(address indexed taxToken, uint256 amount);
    event FlapTaxProcessorBondingCurveTax(address indexed taxToken, uint256 quoteAmount);
    event FlapTaxProcessorProcessTaxTokens(address indexed taxToken, uint256 taxAmount);
    event FlapTaxProcessorMaxBuyBackGasLimitUpdated(uint256 oldLimit, uint256 newLimit);
    event FlapTaxProcessorMinBuyBackQuoteUpdated(uint256 oldAmount, uint256 newAmount);
    event FlapTaxProcessorCommissionPaid(address receiver, uint256 amount);
    event FlapTaxProcessorDividendConverted(address dividendToken, uint256 quoteIn, uint256 dividendOut);
    event FlapTaxProcessorConverterUpdated(address oldConverter, address newConverter);
    event FlapTaxProcessorCommissionConfigUpdated(address receiver, uint16 bps);

    // --- Modifiers ---
    modifier onlyTaxToken() {
        require(msg.sender == taxToken, "TaxProcessor: caller is not the tax token");
        _;
    }
}

/// @title TaxProcessorDispatchImpl
/// @notice Implementation contract for the dispatch logic, deployed by TaxProcessor and called via delegatecall.
///         Inherits TaxProcessorBase for shared state layout and helper access.
contract TaxProcessorDispatchImpl is TaxProcessorBase {
    using SafeERC20 for IERC20;

    constructor(address weth_, address flapBlackHole_, address portal_, address swapRegistry_)
        TaxProcessorBase(weth_, flapBlackHole_, portal_, swapRegistry_)
    {}

    /// @notice Executes the dispatch logic. Called via delegatecall from TaxProcessor.dispatch().
    /// @dev nonReentrant is enforced by the caller (TaxProcessor.dispatch). Do not add it here.
    ///      Direct calls to this function on the TaxProcessorDispatchImpl instance are safe but
    ///      meaningless, since the impl holds no relevant persistent state (all state lives in the
    ///      TaxProcessor proxy that delegatecalls into this contract).
    function executeDispatch() external {
        // fee and optional channel commission
        uint256 feeAmount = feeQuoteBalance;
        uint256 commissionAmount = commissionQuoteBalance;
        // marketing wallet
        uint256 marketAmount = marketQuoteBalance;
        // burn amount
        uint256 burnAmount = preBondBurnFunds;
        // dividend in quote (if the dividend token is not quote, this amount will be swapped to dividend token)
        uint256 pendingDividendQuote = pendingDividendQuoteTokenBalance;
        // dividend in the actual dividend token, will be sent to dividend contract directly
        uint256 dividendTokenAmt = dividendTokenBalance;

        // Save snapshot for event (before local modifications)
        uint256 dividendAmountForEvent = pendingDividendQuote;

        // Clear balances first (checks-effects-interactions pattern)
        if (feeAmount > 0) feeQuoteBalance = 0;
        if (marketAmount > 0) marketQuoteBalance = 0;
        if (pendingDividendQuote > 0) pendingDividendQuoteTokenBalance = 0;
        if (burnAmount > 0) preBondBurnFunds = 0;
        if (commissionAmount > 0) commissionQuoteBalance = 0;
        if (dividendTokenAmt > 0) dividendTokenBalance = 0;

        PackedFeeConfigInternal memory config = _feeConfig;

        // Dispatch fee + market + commission (dividend handled separately below)
        if (config.isWeth) {
            _dispatchETH(feeAmount, marketAmount, commissionAmount);
        } else {
            _dispatchERC20(feeAmount, marketAmount, commissionAmount);
        }

        // Check token state once — shared by both dividend (Case 1) and burn logic below
        IPortalTradeV2.TokenStateV7 memory state = IPortal(portal).getTokenV7(taxToken);
        bool isOnDex = state.status == IPortalTypes.TokenStatus.DEX;

        // --- Dividend handling ---
        // Convert pending quote tokens to dividend tokens, then deposit all to dividend contract.
        // Case 1: dividendToken == taxToken — pending quote swapped to tax tokens via Portal (bonding
        //         curve) or DEX router (graduated); tax tokens added to dividendTokenAmt.
        // Case 2: dividendToken == quoteToken — merge pending quote into dividendTokenAmt (same token).
        // Case 3: dividendToken == custom ERC20 — converter swaps pending quote to dividend token.
        {
            address dvToken = dividendToken;

            if (dvToken == quoteToken) {
                // Case 2: quoteToken IS the dividend token — merge pending into dividend tokens
                dividendTokenAmt += pendingDividendQuote;
                pendingDividendQuote = 0;
            } else if (dvToken == taxToken) {
                // Case 1: dividend in tax tokens; convert pending quote to tax tokens
                if (pendingDividendQuote > 0) {
                    uint256 quoteIn = pendingDividendQuote;
                    pendingDividendQuote = 0;
                    uint256 converted;
                    if (isOnDex) {
                        // Token graduated to DEX — use DEX router (no threshold required)
                        converted = _swapQuoteForTokens(quoteIn);
                    } else if (quoteIn >= minBuyBackQuote) {
                        // Token still on bonding curve — use Portal when threshold is met
                        converted = _swapQuoteForTaxTokenViaPortal(quoteIn);
                    }
                    if (converted > 0) {
                        dividendTokenAmt += converted;
                        emit FlapTaxProcessorDividendConverted(dvToken, quoteIn, converted);
                    } else {
                        pendingDividendQuoteTokenBalance += quoteIn;
                    }
                }
            } else {
                // Case 3: custom dividend token — only converter can trigger the swap
                if (msg.sender == converter && pendingDividendQuote > 0 && dividendAddress != address(0)) {
                    uint256 converted = _convertQuoteToDividendToken(pendingDividendQuote, dvToken);
                    if (converted > 0) {
                        dividendTokenAmt += converted;
                        emit FlapTaxProcessorDividendConverted(dvToken, pendingDividendQuote, converted);
                        pendingDividendQuote = 0;
                    }
                }
            }

            // Restore any unconsumed pending quote tokens
            if (pendingDividendQuote > 0) {
                pendingDividendQuoteTokenBalance += pendingDividendQuote;
            }

            // Deposit accumulated dividend tokens to dividend contract
            if (dividendTokenAmt > 0 && dividendAddress != address(0)) {
                if (!_sendToDividendContract(dividendTokenAmt, dvToken)) {
                    // Restore failed deposit to appropriate balance
                    if (dvToken == quoteToken) {
                        pendingDividendQuoteTokenBalance += dividendTokenAmt;
                    } else {
                        dividendTokenBalance += dividendTokenAmt;
                    }
                }
            }
        }

        // Process burn funds based on token state
        if (burnAmount > 0) {
            // TokenState: Tradable=BondingCurve, DEX=Graduated
            if (isOnDex) {
                // Token is on DEX, liquidate all remaining funds regardless of threshold
                // Use DEX router to swap quote tokens for tax tokens and burn
                uint256 taxTokensReceived = _swapQuoteForTokens(burnAmount);
                if (taxTokensReceived > 0) {
                    IERC20(taxToken).safeTransfer(flapBlackHole, taxTokensReceived);
                    emit FlapTaxProcessorBurnExecuted(taxToken, burnAmount, taxTokensReceived);
                }
            } else {
                // Token still on bonding curve, only process if exceeds threshold
                if (burnAmount >= minBuyBackQuote) {
                    uint256 taxTokensReceived = _swapQuoteForTaxTokenViaPortal(burnAmount);
                    if (taxTokensReceived > 0) {
                        IERC20(taxToken).safeTransfer(flapBlackHole, taxTokensReceived);
                        emit FlapTaxProcessorBurnExecuted(taxToken, burnAmount, taxTokensReceived);
                    } else {
                        // Swap returned zero tokens — accumulate for next dispatch
                        preBondBurnFunds += burnAmount;
                        emit FlapTaxProcessorBuyBackSkipped(taxToken, burnAmount, gasleft(), "Swap failed");
                    }
                } else {
                    // Below threshold — accumulate for next dispatch
                    preBondBurnFunds += burnAmount;
                }
            }
        }

        // Reconcile any untracked balance (refunds from portal swaps, donations, etc.)
        _reconcileBalance();

        emit FlapTaxProcessorDispatchExecuted(taxToken, feeAmount, marketAmount, dividendAmountForEvent);
    }

    /// @notice Dispatch ETH (when quote token is WETH) with optimized unwrapping
    function _dispatchETH(uint256 feeAmount, uint256 marketAmount, uint256 commissionAmount) internal {
        uint256 totalETHNeeded = 0;

        // Calculate total ETH needed for external addresses (fee + market + commission)
        if (feeAmount > 0) totalETHNeeded += feeAmount;
        if (marketAmount > 0 && marketAddress != address(0)) totalETHNeeded += marketAmount;
        if (commissionAmount > 0 && commissionReceiver != address(0)) totalETHNeeded += commissionAmount;

        // Unwrap all needed ETH at once
        if (totalETHNeeded > 0) {
            IWETH(weth).withdraw(totalETHNeeded);
        }

        uint256 ethUsed = 0;

        // Send fee
        if (feeAmount > 0) {
            (bool success,) = payable(feeReceiver).call{value: feeAmount}("");
            if (success) {
                ethUsed += feeAmount;
            }
        }

        // Send market
        if (marketAmount > 0 && marketAddress != address(0)) {
            (bool success,) = payable(marketAddress).call{value: marketAmount}("");
            if (success) {
                ethUsed += marketAmount;
                totalQuoteSentToMarketing += marketAmount;
            }
        }

        // Send commission
        if (commissionAmount > 0 && commissionReceiver != address(0)) {
            (bool success,) = payable(commissionReceiver).call{value: commissionAmount, gas: 100_000}("");
            if (success) {
                ethUsed += commissionAmount;
                emit FlapTaxProcessorCommissionPaid(commissionReceiver, commissionAmount);
            }
        }

        // If any ETH sends failed, wrap the remaining ETH back to WETH and add to fee balance
        uint256 remainingETH = totalETHNeeded - ethUsed;
        if (remainingETH > 0) {
            IWETH(weth).deposit{value: remainingETH}();
            feeQuoteBalance += remainingETH; // Add failed amounts to fee balance
        }
    }

    /// @notice Dispatch ERC20 tokens (when quote token is not WETH)
    function _dispatchERC20(uint256 feeAmount, uint256 marketAmount, uint256 commissionAmount) internal {
        // Send fee
        if (feeAmount > 0) {
            IERC20(quoteToken).safeTransfer(feeReceiver, feeAmount);
        }

        // Send market
        if (marketAmount > 0 && marketAddress != address(0)) {
            totalQuoteSentToMarketing += marketAmount;
            IERC20(quoteToken).safeTransfer(marketAddress, marketAmount);
        }

        // Send commission
        if (commissionAmount > 0 && commissionReceiver != address(0)) {
            IERC20(quoteToken).safeTransfer(commissionReceiver, commissionAmount);
            emit FlapTaxProcessorCommissionPaid(commissionReceiver, commissionAmount);
        }
    }

    /// @notice Swap quote tokens for tax tokens (reverse swap, used for DEX burn)
    function _swapQuoteForTokens(uint256 quoteAmount) internal returns (uint256 taxTokensReceived) {
        IERC20(quoteToken).safeApprove(router, 0);
        IERC20(quoteToken).safeApprove(router, quoteAmount);

        address[] memory path = new address[](2);
        path[0] = quoteToken;
        path[1] = taxToken;

        uint256 taxTokenBefore = IERC20(taxToken).balanceOf(address(this));

        IUniswapRouter02(router).swapExactTokensForTokensSupportingFeeOnTransferTokens(
            quoteAmount,
            0, // Accept any amount
            path,
            address(this),
            block.timestamp
        );
        taxTokensReceived = IERC20(taxToken).balanceOf(address(this)) - taxTokenBefore;
    }

    /// @notice Deposit tokens to Dividend contract
    function _sendToDividendContract(uint256 amount, address token) internal returns (bool success) {
        IERC20(token).safeApprove(dividendAddress, 0);
        IERC20(token).safeApprove(dividendAddress, amount);

        success = IDividend(dividendAddress).deposit(amount);
        if (success) {
            totalDividendTokenSent += amount;
        } else {
            emit FlapTaxProcessorDividendDepositSkipped(taxToken, amount, "Deposit failed or no shareholders");
        }
    }

    /// @notice Swap quote tokens to dividendToken via SwapRegistry (Case 3 only).
    /// @dev Only called by executeDispatch() when msg.sender == converter.
    ///      The converter MUST use a MEV-protected RPC to avoid sandwich attacks.
    function _convertQuoteToDividendToken(uint256 quoteAmount, address dvToken) internal returns (uint256 dvReceived) {
        // Case 3: custom dividend token — use SwapRegistry
        ISwapRegistry.SwapInfo memory info = ISwapRegistry(swapRegistry).getSwapInfo(quoteToken, dvToken);
        if (!info.supported) {
            return 0;
        }

        address routerAddr = ISwapRegistry(swapRegistry).multiDexRouter();
        require(routerAddr != address(0), "TaxProcessor: no router in registry");

        IERC20(quoteToken).safeApprove(routerAddr, 0);
        IERC20(quoteToken).safeApprove(routerAddr, quoteAmount);

        uint256 dvBefore = IERC20(dvToken).balanceOf(address(this));

        if (info.poolType == ISwapRegistry.PoolType.V3) {
            IMultiDexRouter.ExactInputSingleParams memory p = IMultiDexRouter.ExactInputSingleParams({
                tokenIn: quoteToken,
                tokenOut: dvToken,
                fee: info.feeTier,
                recipient: address(this),
                amountIn: quoteAmount,
                amountOutMinimum: 1,
                sqrtPriceLimitX96: 0
            });
            try IMultiDexRouter(routerAddr).exactInputSingle(info.dexId, p) {}
            catch {
                return 0;
            }
        } else {
            address[] memory path = new address[](2);
            path[0] = quoteToken;
            path[1] = dvToken;
            try IMultiDexRouter(routerAddr).swapExactTokensForTokens(info.dexId, quoteAmount, 1, path, address(this)) {}
            catch {
                return 0;
            }
        }

        dvReceived = IERC20(dvToken).balanceOf(address(this)) - dvBefore;
    }

    /// @notice Swap quote tokens for tax tokens via Portal (pure buyback, no burn logic).
    /// @dev duringBuyBack modifier: any tax collected during the swap goes to preBondBurnFunds.
    ///      Uses minOutputAmount: 1 so a legitimate zero-output result causes the Portal to revert,
    ///      meaning a return value of 0 from this function always means the swap was caught and failed.
    ///      Refunds are NOT handled here — _reconcileBalance() catches any untracked balances.
    /// @param quoteAmount The amount of quote tokens to swap.
    /// @return tokensBought The number of tax tokens received from the swap (0 if swap fails and is caught).
    function _swapQuoteForTaxTokenViaPortal(uint256 quoteAmount)
        internal
        duringBuyBack
        returns (uint256 tokensBought)
    {
        if (_feeConfig.isWeth) {
            IWETH(weth).withdraw(quoteAmount);

            IPortalTradeV2.ExactInputParams memory params = IPortalTradeV2.ExactInputParams({
                inputToken: address(0), // Native token (BNB/ETH)
                outputToken: taxToken,
                inputAmount: quoteAmount,
                minOutputAmount: 1,
                permitData: ""
            });

            try IPortal(portal).swapExactInput{value: quoteAmount, gas: maxBuyBackGasLimit}(params) returns (
                uint256 received
            ) {
                tokensBought = received;

                uint256 ethBalance = address(this).balance;
                if (ethBalance > 0) {
                    IWETH(weth).deposit{value: ethBalance}();
                }
            } catch {
                IWETH(weth).deposit{value: quoteAmount}();
                return 0;
            }
        } else {
            IERC20(quoteToken).safeApprove(portal, 0);
            IERC20(quoteToken).safeApprove(portal, quoteAmount);

            IPortalTradeV2.ExactInputParams memory params = IPortalTradeV2.ExactInputParams({
                inputToken: quoteToken,
                outputToken: taxToken,
                inputAmount: quoteAmount,
                minOutputAmount: 1,
                permitData: ""
            });

            try IPortal(portal).swapExactInput{gas: maxBuyBackGasLimit}(params) returns (uint256 received) {
                tokensBought = received;
                IERC20(quoteToken).safeApprove(portal, 0); // clear residual allowance after success
            } catch {
                IERC20(quoteToken).safeApprove(portal, 0); // clear allowance on failure
                return 0;
            }
        }
    }

    /// @notice Reconcile actual token balance with tracked balances
    /// @dev Any untracked balance (from refunds, donations, etc.) goes to preBondBurnFunds
    function _reconcileBalance() internal {
        uint256 actualBalance = IERC20(quoteToken).balanceOf(address(this));
        uint256 trackedBalance = feeQuoteBalance + marketQuoteBalance + pendingDividendQuoteTokenBalance
            + lpQuoteBalance + preBondBurnFunds + commissionQuoteBalance;

        if (actualBalance > trackedBalance) {
            uint256 untracked = actualBalance - trackedBalance;
            preBondBurnFunds += untracked;
            emit FlapTaxProcessorPortalRefund(taxToken, untracked, _feeConfig.isWeth);
        }
    }
}

/// @title TaxProcessor
/// @notice Main TaxProcessor contract. Handles all tax processing logic except dispatch,
///         which is delegated to TaxProcessorDispatchImpl to stay within the contract size limit.
contract TaxProcessor is TaxProcessorBase {
    using SafeERC20 for IERC20;

    /// @notice Address of the deployed TaxProcessorDispatchImpl used for dispatch delegation
    address public immutable DISPATCH_IMPL;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor(address weth_, address flapBlackHole_, address portal_, address swapRegistry_)
        TaxProcessorBase(weth_, flapBlackHole_, portal_, swapRegistry_)
    {
        DISPATCH_IMPL = address(new TaxProcessorDispatchImpl(weth_, flapBlackHole_, portal_, swapRegistry_));
    }

    // --- Initialization ---
    function initialize(TaxProcessorInitParams memory params) external initializer {
        // --- Input validation (all checks up front) ---
        require(params.router != address(0), "TaxProcessor: zero router");
        require(params.feeReceiver != address(0), "TaxProcessor: zero fee receiver");
        require(params.taxToken != address(0), "TaxProcessor: zero tax token");
        require(params.quoteToken != address(0), "TaxProcessor: zero quote token");
        require(params.dividendToken != address(0), "TaxProcessor: zero dividend token");
        require(params.feeRate <= 10000, "TaxProcessor: feeRate must be <= 10000");
        uint256 totalBps = uint256(params.marketBps) + uint256(params.deflationBps) + uint256(params.lpBps)
            + uint256(params.dividendBps);
        require(totalBps == 10000, "TaxProcessor: distribution bps must sum to 10000");
        if (params.marketBps > 0) require(params.marketAddress != address(0), "TaxProcessor: zero market address");
        if (params.dividendBps > 0) {
            require(params.dividendAddress != address(0), "TaxProcessor: zero dividend address");
        }

        // --- Initialize base contracts ---
        __Ownable_init();
        __ReentrancyGuard_init();
        __BuyBackGuard_init();

        // --- Basic addresses ---
        router = params.router;
        feeReceiver = params.feeReceiver;
        taxToken = params.taxToken;

        // --- Quote token ---
        bool isWeth = params.quoteToken == weth;
        quoteToken = params.quoteToken;

        // --- Optional addresses (only stored when their allocation is non-zero) ---
        if (params.marketBps > 0) marketAddress = params.marketAddress;
        if (params.dividendBps > 0) dividendAddress = params.dividendAddress;

        // --- Fee configuration + commission packed into a single storage slot ---
        _feeConfig = PackedFeeConfigInternal({
            marketBps: params.marketBps,
            deflationBps: params.deflationBps,
            lpBps: params.lpBps,
            dividendBps: params.dividendBps,
            feeRate: params.feeRate,
            isWeth: isWeth,
            commissionBps: params.commissionReceiver != address(0) ? params.commissionBps : 0
        });

        // --- Buy-back thresholds ---
        minBuyBackQuote = _calculateMinBuyBackQuote(isWeth, params.quoteToken);
        maxBuyBackGasLimit = 500_000;

        // --- Commission (optional; address(0) = disabled) ---
        if (params.commissionReceiver != address(0)) {
            commissionReceiver = params.commissionReceiver;
        }

        // --- Dividend token ---
        // Case 1: dividendToken == taxToken   → self-dividend, accumulate tax tokens directly.
        // Case 2: dividendToken == quoteToken → standard quote-token dividend, no swap needed.
        // Case 3: anything else               → custom ERC20, requires swapRegistry + converter.
        if (params.dividendToken != params.taxToken && params.dividendToken != params.quoteToken) {
            require(swapRegistry != address(0), "TaxProcessor: swapRegistry required for custom dividend token");
            require(
                ISwapRegistry(swapRegistry).isSwapSupported(params.quoteToken, params.dividendToken),
                "TaxProcessor: swap path not supported for dividend token"
            );
            require(params.converter != address(0), "TaxProcessor: converter required for custom dividend token");
            converter = params.converter;
        }
        dividendToken = params.dividendToken;
        liqExpectedOutputAmount = params.liqExpectedOutputAmount;
    }

    // --- External Functions ---

    /// @notice Get the quote token address
    function getQuoteToken() external view returns (address) {
        return quoteToken;
    }

    /// @notice Process bonding curve tax in quote tokens
    /// @param quoteAmount The amount of quote tokens to process
    /// @dev Splits quote tokens into fee, market, dividend, lp, and deflation based on configured ratios
    ///      LP portion is added to lpQuoteBalance for later processing
    ///      Deflation portion is added to preBondBurnFunds for buyback and burn
    ///      During buyback (_isBuyingBack=true), all tax goes to preBondBurnFunds to avoid balance pollution
    function processBondingCurveTax(uint256 quoteAmount) external {
        require(quoteAmount > 0, "TaxProcessor: zero amount");

        IERC20(quoteToken).safeTransferFrom(msg.sender, address(this), quoteAmount);

        if (_isBuyingBack()) {
            preBondBurnFunds += quoteAmount;
            emit FlapTaxProcessorBondingCurveTax(taxToken, quoteAmount);
            return;
        }

        PackedFeeConfigInternal memory config = _feeConfig;

        uint256 fee = (quoteAmount * uint256(config.feeRate)) / 10000;
        uint256 remaining = quoteAmount - fee;

        if (config.commissionBps > 0 && commissionReceiver != address(0)) {
            uint256 commission = (remaining * uint256(config.commissionBps)) / 10000;
            if (commission > 0) {
                commissionQuoteBalance += commission;
                remaining -= commission;
            }
        }

        uint256 market = (remaining * uint256(config.marketBps)) / 10000;
        uint256 deflation = (remaining * uint256(config.deflationBps)) / 10000;
        uint256 lp = (remaining * uint256(config.lpBps)) / 10000;
        uint256 dividend = remaining - market - deflation - lp;

        feeQuoteBalance += fee;
        marketQuoteBalance += market;
        pendingDividendQuoteTokenBalance += dividend;
        lpQuoteBalance += lp;
        preBondBurnFunds += deflation;

        emit FlapTaxProcessorBondingCurveTax(taxToken, quoteAmount);
    }

    /// @notice Process tax tokens: compute fees, split amounts, burn deflation, swap and distribute
    /// @param taxAmount The total amount of tax tokens to process
    /// @return liqThresholdDirection Direction to adjust liquidation threshold:
    ///         +1 = swap output below reference (price lower, raise threshold to liquidate less often),
    ///          0 = no reference set or exact match,
    ///         -1 = swap output exceeded reference (price higher, lower threshold to liquidate smaller amounts).
    function processTaxTokens(uint256 taxAmount) external onlyTaxToken returns (int8 liqThresholdDirection) {
        require(taxAmount > 0, "TaxProcessor: zero amount");

        IERC20(taxToken).safeTransferFrom(msg.sender, address(this), taxAmount);

        PackedFeeConfigInternal memory config = _feeConfig;

        uint256 fee = (taxAmount * uint256(config.feeRate)) / 10000;
        uint256 remaining = taxAmount - fee;

        uint256 commission = 0;
        if (config.commissionBps > 0 && commissionReceiver != address(0)) {
            commission = (remaining * uint256(config.commissionBps)) / 10000;
            if (commission > 0) {
                remaining -= commission;
            }
        }

        uint256 market = (remaining * uint256(config.marketBps)) / 10000;
        uint256 deflation = (remaining * uint256(config.deflationBps)) / 10000;
        uint256 lp = (remaining * uint256(config.lpBps)) / 10000;
        uint256 dividend = remaining - (market + deflation + lp);

        if (deflation > 0) {
            IERC20(taxToken).safeTransfer(flapBlackHole, deflation);
            emit FlapTaxProcessorTokensBurned(taxToken, deflation);
        }

        uint256 dividendToSwap = dividend;
        {
            address dvToken = dividendToken;
            if (dvToken == taxToken && dividend > 0) {
                dividendTokenBalance += dividend;
                dividendToSwap = 0;
            }
        }

        uint256 totalQuoteReceived = _processTokenDistribution(fee, market, lp, dividendToSwap, commission);

        emit FlapTaxProcessorProcessTaxTokens(taxToken, taxAmount);

        uint256 refAmount = liqExpectedOutputAmount;
        if (refAmount == 0 || totalQuoteReceived == 0) {
            liqThresholdDirection = 0;
        } else if (totalQuoteReceived > refAmount) {
            // Price is higher than expected: lower the threshold so liquidations sell smaller
            // amounts more frequently, avoiding a large single dump that nukes the price.
            liqThresholdDirection = -1;
        } else if (totalQuoteReceived < refAmount) {
            // Price is lower than expected: raise the threshold so the contract waits for more
            // tokens to accumulate before selling, reducing sell pressure during weak markets.
            liqThresholdDirection = 1;
        } else {
            liqThresholdDirection = 0;
        }
    }

    /// @notice Process token distribution: add liquidity first, then swap remaining tokens
    function _processTokenDistribution(uint256 fee, uint256 market, uint256 lp, uint256 dividend, uint256 commission)
        internal
        returns (uint256 totalQuoteReceived)
    {
        uint256 lpTaxToSwap = lp;

        if (lp > 0 && lpQuoteBalance > 0) {
            (uint256 actualTokenUsed, uint256 actualQuoteUsed) = _addLiquidity(lp, lpQuoteBalance);
            lpTaxToSwap = lp >= actualTokenUsed ? lp - actualTokenUsed : 0;
            lpQuoteBalance = lpQuoteBalance >= actualQuoteUsed ? lpQuoteBalance - actualQuoteUsed : 0;
        }

        uint256 totalToSwap = lpTaxToSwap + fee + commission + market + dividend;

        if (totalToSwap > 0) {
            uint256 quoteReceived = _swapTokensForQuote(totalToSwap);
            totalQuoteReceived = quoteReceived;

            if (quoteReceived > 0) {
                uint256 feeShare = fee > 0 ? (quoteReceived * fee) / totalToSwap : 0;
                uint256 commissionShare = commission > 0 ? (quoteReceived * commission) / totalToSwap : 0;
                uint256 marketShare = market > 0 ? (quoteReceived * market) / totalToSwap : 0;
                uint256 dividendShare = dividend > 0 ? (quoteReceived * dividend) / totalToSwap : 0;
                uint256 lpShare = lpTaxToSwap > 0 ? (quoteReceived * lpTaxToSwap) / totalToSwap : 0;

                feeQuoteBalance += feeShare;
                commissionQuoteBalance += commissionShare;
                marketQuoteBalance += marketShare;
                pendingDividendQuoteTokenBalance += dividendShare;
                lpQuoteBalance += lpShare;

                // Credit any wei remainder from integer-division rounding to feeQuoteBalance
                // so that no quote tokens are left untracked.
                uint256 distributed = feeShare + commissionShare + marketShare + dividendShare + lpShare;
                if (distributed < quoteReceived) {
                    feeQuoteBalance += quoteReceived - distributed;
                }
            }
        }
    }

    /// @notice Dispatch accumulated quote tokens to receivers.
    ///         The actual dispatch logic lives in TaxProcessorDispatchImpl and is called via delegatecall.
    function dispatch() external nonReentrant {
        (bool success, bytes memory result) =
            DISPATCH_IMPL.delegatecall(abi.encodeWithSelector(TaxProcessorDispatchImpl.executeDispatch.selector));
        if (!success) {
            assembly {
                revert(add(result, 32), mload(result))
            }
        }
    }

    /// @notice Owner can update receivers
    function setReceivers(address feeReceiver_, address marketAddress_, address dividendAddress_) external onlyOwner {
        require(feeReceiver_ != address(0), "TaxProcessor: zero fee receiver");
        feeReceiver = feeReceiver_;

        PackedFeeConfigInternal memory config = _feeConfig;

        if (config.marketBps > 0) {
            require(marketAddress_ != address(0), "TaxProcessor: zero market address");
            marketAddress = marketAddress_;
        }

        if (config.dividendBps > 0) {
            require(dividendAddress_ != address(0), "TaxProcessor: zero dividend address");
            dividendAddress = dividendAddress_;
        }
    }

    /// @notice Owner can update tax configuration
    function setTaxConfig(uint16 feeRate_, uint16 marketBps_, uint16 deflationBps_, uint16 lpBps_, uint16 dividendBps_)
        external
        onlyOwner
    {
        require(feeRate_ <= 10000, "TaxProcessor: feeRate must be <= 10000");
        uint256 totalBps = uint256(marketBps_) + uint256(deflationBps_) + uint256(lpBps_) + uint256(dividendBps_);
        require(totalBps == 10000, "TaxProcessor: distribution bps must sum to 10000");

        if (marketBps_ > 0) {
            require(marketAddress != address(0), "TaxProcessor: market address not set");
        }
        if (dividendBps_ > 0) {
            require(dividendAddress != address(0), "TaxProcessor: dividend address not set");
        }

        PackedFeeConfigInternal memory config = _feeConfig;
        config.feeRate = feeRate_;
        config.marketBps = marketBps_;
        config.deflationBps = deflationBps_;
        config.lpBps = lpBps_;
        config.dividendBps = dividendBps_;
        _feeConfig = config;
    }

    /// @notice Owner can update maximum buy back gas limit
    function setMaxBuyBackGasLimit(uint256 newLimit) external onlyOwner {
        require(newLimit > 0, "TaxProcessor: zero maxBuyBackGasLimit");
        uint256 oldLimit = maxBuyBackGasLimit;
        maxBuyBackGasLimit = newLimit;
        emit FlapTaxProcessorMaxBuyBackGasLimitUpdated(oldLimit, newLimit);
    }

    /// @notice Owner can update minimum buy back quote amount
    function setMinBuyBackQuote(uint256 newAmount) external onlyOwner {
        require(newAmount > 0, "TaxProcessor: zero minBuyBackQuote");
        uint256 oldAmount = minBuyBackQuote;
        minBuyBackQuote = newAmount;
        emit FlapTaxProcessorMinBuyBackQuoteUpdated(oldAmount, newAmount);
    }

    // --- V3 Setter Functions ---

    /// @notice Owner can update commission configuration
    function setCommissionConfig(address receiver, uint16 bps) external onlyOwner {
        commissionReceiver = receiver;
        _feeConfig.commissionBps = bps;
        emit FlapTaxProcessorCommissionConfigUpdated(receiver, bps);
    }

    /// @notice Owner can update the dividend token
    function setDividendToken(address token) external onlyOwner {
        require(token != address(0), "TaxProcessor: dividendToken cannot be zero");
        // If the new dividend token is a custom ERC20 (not quoteToken and not taxToken),
        // validate that a swap path exists in the SwapRegistry and a converter is configured.
        if (token != quoteToken && token != taxToken) {
            require(swapRegistry != address(0), "TaxProcessor: swapRegistry required for custom dividend token");
            require(
                ISwapRegistry(swapRegistry).isSwapSupported(quoteToken, token),
                "TaxProcessor: swap path not supported for dividend token"
            );
            require(converter != address(0), "TaxProcessor: converter required for custom dividend token");
        }
        dividendToken = token;
    }

    /// @notice Owner can update the converter address (for Case 3 MEV-protected swaps)
    function setConverter(address converter_) external onlyOwner {
        // Prevent setting converter to zero when a custom dividend token (Case 3) is configured.
        if (converter_ == address(0)) {
            address dvToken = dividendToken;
            require(
                dvToken == quoteToken || dvToken == taxToken,
                "TaxProcessor: converter cannot be zero when custom dividend token is set"
            );
        }
        address old = converter;
        converter = converter_;
        emit FlapTaxProcessorConverterUpdated(old, converter_);
    }

    /// @notice Owner can recalibrate the reference expected output for liquidation threshold direction
    function setLiqExpectedOutputAmount(uint256 amount) external onlyOwner {
        liqExpectedOutputAmount = amount;
    }

    // --- V3 View Functions ---

    /// @notice Returns true if this TaxProcessor requires a MEV-protected RPC for the converter
    function requiresMEVProtection() external view returns (bool) {
        address dvToken = dividendToken;
        return dvToken != quoteToken && dvToken != taxToken;
    }

    /// @notice Get packed fee configuration (backward-compatible view satisfying ITaxProcessor interface)
    function feeConfig() external view returns (PackedFeeConfig memory) {
        PackedFeeConfigInternal memory c = _feeConfig;
        return PackedFeeConfig({
            marketBps: c.marketBps,
            deflationBps: c.deflationBps,
            lpBps: c.lpBps,
            dividendBps: c.dividendBps,
            feeRate: c.feeRate,
            isWeth: c.isWeth
        });
    }

    /// @notice Get the commission in basis points (taken from remainder after protocol fee)
    function commissionBps() external view returns (uint16) {
        return _feeConfig.commissionBps;
    }

    /// @notice Returns the full fee configuration including commission bps
    function feeConfigV2() external view returns (PackedFeeConfigV2 memory result) {
        PackedFeeConfigInternal memory config = _feeConfig;
        result = PackedFeeConfigV2({
            marketBps: config.marketBps,
            deflationBps: config.deflationBps,
            lpBps: config.lpBps,
            dividendBps: config.dividendBps,
            feeRate: config.feeRate,
            isWeth: config.isWeth,
            commissionBps: config.commissionBps,
            dividendToken: dividendToken
        });
    }

    /// @notice Backward-compatible view for total dividend tokens sent.
    function totalQuoteSentToDividend() external view returns (uint256) {
        return totalDividendTokenSent;
    }

    /// @notice Backward-compatible view for pending dividend quote token balance.
    function dividendQuoteBalance() external view returns (uint256) {
        return pendingDividendQuoteTokenBalance;
    }

    // --- Internal Functions ---

    /// @notice Calculate minimum buy back quote amount based on chain and quote token
    function _calculateMinBuyBackQuote(bool isWeth, address quoteTokenAddr) internal view returns (uint256) {
        uint256 chainId = block.chainid;
        uint256 decimals = IERC20Metadata(quoteTokenAddr).decimals();

        if (chainId == 56 || chainId == 97) {
            if (isWeth) {
                return 0.05 ether;
            } else if (quoteTokenAddr == 0x000Ae314E2A2172a039B26378814C252734f556A) {
                return 100 ether;
            } else if (quoteTokenAddr == 0x924fa68a0FC644485b8df8AbfA0A41C2e7744444) {
                return 300 ether;
            } else {
                return 50 * (10 ** decimals);
            }
        } else if (chainId == 196) {
            if (isWeth) {
                return 0.3 ether;
            } else {
                return 50 * (10 ** decimals);
            }
        } else {
            return 0.1 ether;
        }
    }

    /// @notice Swap tax tokens for quote tokens
    function _swapTokensForQuote(uint256 amount) internal returns (uint256 quoteReceived) {
        IERC20(taxToken).safeApprove(router, 0);
        IERC20(taxToken).safeApprove(router, amount);

        address[] memory path = new address[](2);
        path[0] = taxToken;
        path[1] = quoteToken;

        uint256 quoteBefore = IERC20(quoteToken).balanceOf(address(this));

        IUniswapRouter02(router).swapExactTokensForTokensSupportingFeeOnTransferTokens(
            amount, 0, path, address(this), block.timestamp
        );
        quoteReceived = IERC20(quoteToken).balanceOf(address(this)) - quoteBefore;
    }

    /// @notice Add liquidity to DEX
    function _addLiquidity(uint256 tokenAmount, uint256 quoteAmount)
        internal
        returns (uint256 actualTokenUsed, uint256 actualQuoteUsed)
    {
        IERC20(taxToken).safeApprove(router, 0);
        IERC20(taxToken).safeApprove(router, tokenAmount);
        IERC20(quoteToken).safeApprove(router, 0);
        IERC20(quoteToken).safeApprove(router, quoteAmount);

        (uint256 amountA, uint256 amountB,) = IUniswapRouter02(router).addLiquidity(
            taxToken, quoteToken, tokenAmount, quoteAmount, 0, 0, address(DEAD_ADDRESS), block.timestamp
        );

        actualTokenUsed = amountA;
        actualQuoteUsed = amountB;

        totalTokenAddedToLiquidity += actualTokenUsed;
        totalQuoteAddedToLiquidity += actualQuoteUsed;
    }

    // --- Emergency Functions ---

    /// @notice Withdraw all remaining tokens to a specified address
    /// @param token Token address to withdraw (use address(0) for quote token or native ETH)
    /// @param to Recipient address
    function withdrawAll(address token, address to) external onlyOwner {
        require(to != address(0), "TaxProcessor: zero address");

        if (token == address(0)) {
            if (_feeConfig.isWeth) {
                uint256 balance = address(this).balance;
                if (balance > 0) {
                    (bool success,) = payable(to).call{value: balance}("");
                    require(success, "TaxProcessor: ETH transfer failed");
                }
            } else {
                uint256 balance = IERC20(quoteToken).balanceOf(address(this));
                if (balance > 0) {
                    IERC20(quoteToken).safeTransfer(to, balance);
                }
            }
        } else {
            uint256 balance = IERC20(token).balanceOf(address(this));
            if (balance > 0) {
                IERC20(token).safeTransfer(to, balance);
            }
        }
    }

    // --- Fallback ---
    /// @notice Allow contract to receive native ETH
    receive() external payable {}
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    function __Ownable_init() internal onlyInitializing {
        __Ownable_init_unchained();
    }

    function __Ownable_init_unchained() internal onlyInitializing {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

/// @notice Interface for the Dividend distribution contract
interface IDividend {
    // --- Events ---
    /// @notice Emitted when a user's share is updated
    /// @param taxToken The tax token contract address
    /// @param user The user whose share was changed
    /// @param newShare The new share amount for the user
    /// @param totalShares The total shares after the change
    event FlapDividendShareChanged(
        address indexed taxToken, address indexed user, uint256 newShare, uint256 totalShares
    );

    /// @notice Emitted when dividends are deposited into the contract
    /// @param taxToken The tax token contract address
    /// @param amount The amount of dividend tokens deposited
    /// @param magnifiedDividendPerShare The updated magnified dividend per share
    event FlapDividendDeposited(address indexed taxToken, uint256 amount, uint256 magnifiedDividendPerShare);

    /// @notice Emitted when dividends are distributed to a user
    /// @param taxToken The tax token contract address
    /// @param user The user who received the dividends
    /// @param amount The amount of dividends distributed
    event FlapDividendDistributed(address indexed taxToken, address user, uint256 amount);

    /// @notice Emitted when an address is excluded from receiving dividends
    /// @param taxToken The tax token contract address
    /// @param addr The address that was excluded
    event FlapDividendAddressExcluded(address indexed taxToken, address addr);

    /// @notice Emitted when a dividend withdrawal fails
    /// @param taxToken The tax token contract address
    /// @param user The user for whom the withdrawal failed
    /// @param amount The amount that failed to be withdrawn
    event FlapDividendWithdrawalFailed(address indexed taxToken, address user, uint256 amount);

    /// @notice Emitted when a user's pending balance changes
    /// @param taxToken The tax token contract address
    /// @param user The user whose pending balance changed
    /// @param pendingBalance The new pending balance
    event FlapDividendPendingBalanceChanged(address indexed taxToken, address indexed user, uint256 pendingBalance);

    /// @notice Emitted when a user's reward debt changes
    /// @param taxToken The tax token contract address
    /// @param user The user whose reward debt changed
    /// @param rewardDebt The new reward debt
    event FlapDividendRewardDebtChanged(address indexed taxToken, address indexed user, uint256 rewardDebt);

    /// @notice Initialize the dividend contract
    /// @param dividendToken_ The token used for dividend payments
    /// @param taxToken_ The tax token contract address
    /// @param minimumShareBalance_ The minimum balance required for dividend eligibility
    function initialize(address dividendToken_, address taxToken_, uint256 minimumShareBalance_) external;

    /// @notice Set user's share (only callable by FlapTaxToken)
    /// @param user The user address
    /// @param share The new share amount for the user
    function setShare(address user, uint256 share) external;

    /// @notice Deposit dividends to be distributed
    /// @param amount The amount of dividend tokens to deposit
    /// @return success Whether the deposit was successful
    function deposit(uint256 amount) external returns (bool success);

    /// @notice Batch distribute dividends to specified users
    /// @param users Array of user addresses to distribute dividends to
    /// @return successCount Number of successful distributions
    function distributeDividend(address[] calldata users) external returns (uint256 successCount);

    /// @notice User can call this to withdraw their own dividends (unwraps WETH to ETH if applicable)
    /// @return success Whether the withdrawal was successful
    function withdrawDividends() external returns (bool success);

    /// @notice Withdraw dividends for a specific user
    /// @param user The user address to withdraw for
    /// @return success Whether the withdrawal was successful
    function withdrawDividendsFor(address user) external returns (bool success);

    /// @notice Withdraw dividends for a specific user with option to unwrap WETH
    /// @param user The user address to withdraw for
    /// @param unwrapWETH Whether to unwrap WETH to ETH
    /// @return success Whether the withdrawal was successful
    function withdrawDividendsFor(address user, bool unwrapWETH) external returns (bool success);

    /// @notice Get the withdrawable dividend amount for a user
    /// @param user The user address
    /// @return The amount of dividends the user can claim
    function withdrawableDividends(address user) external view returns (uint256);

    /// @notice Exclude an address from receiving dividends
    /// @param addr The address to exclude
    function excludeAddress(address addr) external;

    /// @notice Get total shares across all users
    /// @return The total amount of shares
    function totalShares() external view returns (uint256);

    /// @notice Get the minimum share balance required for dividend eligibility
    /// @return The minimum share balance
    function minimumShareBalance() external view returns (uint256);

    /// @notice Get total dividends withdrawn by a user
    /// @param user The user address
    /// @return The total amount of dividends withdrawn
    function withdrawnDividends(address user) external view returns (uint256);

    /// @notice Emergency withdraw function to recover tokens in case of emergency
    /// @param token The token address to withdraw (use address(0) for native ETH)
    /// @param amount The amount to withdraw (0 means withdraw all)
    /// @param to The address to send the tokens to
    function emergencyWithdraw(address token, uint256 amount, address to) external;
}

File 8 of 20 : IPortal.sol
// 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

    }

    //
    // 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)

    }

    /// @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

    }

    /// @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.

    /// @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

    }

    /// @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
        uint64 unused; // 64 bits: reserved for future use
    }

    /// @notice Records who locked a CREATE2 salt and for which token version.
    ///         locker + tokenVersion 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
    }

    //
    // 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 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 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 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 if anti-farmer duration is less than minimum allowed
    error AntiFarmerDurationTooShort();

    /// @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 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.
    ///         Currently only TOKEN_TAXED_V3 (6) is accepted; any other value reverts with
    ///         UnsupportedTokenVersion.  The predicted token address must satisfy the
    ///         TAX_TOKEN_SUFFIX vanity requirement (enforced by _predictTokenAddress).
    ///         Fee is forwarded to FEE_RECEIVER.  Emits FlapSaltLocked.
    /// @param salt         CREATE2 salt to reserve.
    /// @param tokenVersion Token version to lock for. Must be TOKEN_TAXED_V3 at this time.
    function lockSalt(bytes32 salt, TokenVersion tokenVersion) external payable;
}

/// @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);

    /// @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);
}

/// @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);
}

/// @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 or TAX_MANAGER_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 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;
}

/// @title Portal Interface
/// @notice This interface combines the core and game interfaces
interface IPortal is
    IPortalCore,
    IAccessControlUpgradeable,
    IRoller,
    IPortalDexRouter,
    IPortalTweak,
    IPortalLens,
    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);
}

// SPDX-License-Identifier: MIT
pragma solidity =0.8.24;

import {Initializable} from "@openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol";

/// @title BuyBackGuardUpgradeable
/// @notice Abstract contract providing buyback status tracking with gas-optimized storage
/// @dev Uses uint256 with constants instead of bool for gas efficiency:
///      - Non-zero to non-zero transitions (1 → 2 → 1) are cheaper than zero to non-zero (0 → 1 → 0)
///      - uint256 is native word size, no need for type conversion
///      Pattern follows OpenZeppelin's ReentrancyGuardUpgradeable
abstract contract BuyBackGuardUpgradeable is Initializable {
    // --- Constants ---
    uint256 private constant _NOT_BUYING_BACK = 1;
    uint256 private constant _BUYING_BACK = 2;

    // --- Storage ---
    uint256 private _buyBackStatus;

    // --- Internal Functions ---

    /// @notice Initialize the buyback guard
    /// @dev Must be called in the initializer of the inheriting contract
    function __BuyBackGuard_init() internal onlyInitializing {
        __BuyBackGuard_init_unchained();
    }

    function __BuyBackGuard_init_unchained() internal onlyInitializing {
        _buyBackStatus = _NOT_BUYING_BACK;
    }

    /// @notice Check if buyback is currently in progress
    /// @return True if buyback is in progress
    function _isBuyingBack() internal view returns (bool) {
        return _buyBackStatus == _BUYING_BACK;
    }

    // --- Modifiers ---

    /// @notice Modifier to mark buyback operations
    /// @dev Sets status to BUYING_BACK during execution, reverts to NOT_BUYING_BACK after
    modifier duringBuyBack() {
        _buyBackStatus = _BUYING_BACK;
        _;
        _buyBackStatus = _NOT_BUYING_BACK;
    }

    /// @dev This empty reserved space is put in place to allow future versions to add new
    ///      variables without shifting down storage in the inheritance chain.
    ///      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
    uint256[49] private __gap;
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

/// @title ISwapRegistry
/// @notice Registry for supported single-hop swap paths used by TaxProcessor for dividend token conversions.
///         The TaxProcessor uses this registry to look up pool/DEX parameters when swapping quote tokens
///         into a custom dividend token during dispatch().
interface ISwapRegistry {
    /// @notice Pool type enum to distinguish between AMM versions
    enum PoolType {
        V2, // Uniswap V2-style constant product pool
        V3 // Uniswap V3-style concentrated liquidity pool

    }

    /// @notice Swap configuration for a token pair
    struct SwapInfo {
        /// @notice The liquidity pool address for this token pair
        address pool;
        /// @notice The DEX identifier (maps to MultiDexRouter dexId)
        uint8 dexId;
        /// @notice The fee tier for the pool (relevant for V3 pools; ignored for V2)
        uint24 feeTier;
        /// @notice Whether this is a V2 or V3 pool
        PoolType poolType;
        /// @notice Whether this swap path is currently active
        bool supported;
    }

    /// @notice Check if a swap from `fromToken` to `toToken` is registered and active
    /// @param fromToken The source token address
    /// @param toToken The destination token address
    /// @return True if the swap path is registered and active
    function isSwapSupported(address fromToken, address toToken) external view returns (bool);

    /// @notice Get the full swap configuration for a token pair
    /// @param fromToken The source token address
    /// @param toToken The destination token address
    /// @return info The swap configuration (pool, dex, fee tier, pool type, supported flag)
    function getSwapInfo(address fromToken, address toToken) external view returns (SwapInfo memory info);

    /// @notice Returns the MultiDexRouter address used for executing swaps
    /// @dev The TaxProcessor calls this to obtain the router address for dividend token conversion.
    ///      This is a storage variable (not immutable) so it can be updated via upgrade or setter.
    function multiDexRouter() external view returns (address);

    /// @notice Register or update a swap path for a token pair
    /// @dev Only callable by the owner.
    /// @param fromToken The source token address
    /// @param toToken The destination token address
    /// @param pool The liquidity pool address
    /// @param dexId The DEX identifier (must match MultiDexRouter's DEX IDs)
    /// @param feeTier The fee tier for the pool (ignored for V2 pools)
    /// @param poolType Whether this is a V2 or V3 pool
    function setSwapPath(
        address fromToken,
        address toToken,
        address pool,
        uint8 dexId,
        uint24 feeTier,
        PoolType poolType
    ) external;

    /// @notice Remove (disable) a swap path for a token pair
    /// @dev Does not delete the entry; sets supported = false to preserve historical data.
    /// @param fromToken The source token address
    /// @param toToken The destination token address
    function removeSwapPath(address fromToken, address toToken) external;

    /// @notice Update the MultiDexRouter address
    /// @param newRouter The new MultiDexRouter address
    function setMultiDexRouter(address newRouter) external;

    /// @notice Emitted when a swap path is registered or updated
    event SwapPathSet(
        address indexed fromToken, address indexed toToken, address pool, uint8 dexId, uint24 feeTier, PoolType poolType
    );

    /// @notice Emitted when a swap path is disabled
    event SwapPathRemoved(address indexed fromToken, address indexed toToken);

    /// @notice Emitted when the MultiDexRouter address is updated
    event MultiDexRouterUpdated(address oldRouter, address newRouter);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @title Multi-DEX Router Interface
/// @notice Interface for a router that can swap tokens across multiple DEX protocols using a unified interface
/// @dev This router provides a wrapper for swapping on different DEX platforms (Uniswap, PancakeSwap, etc.)
/// using the same interface. It does NOT support smart router special addresses like ADDRESS_THIS (address(2))
/// and does NOT support multicall functionality for simplified implementation and reduced complexity.
interface IMultiDexRouter {
    /// @notice Enum for V3 LP fee profiles
    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 Struct containing all DEX-related parameters for a specific DEX
    /// @dev This struct provides complete configuration for interacting with a DEX protocol

    struct DEXInfo {
        bytes32 v2InitCodeHash; // Init code hash for V2 factory
        bytes32 v3InitCodeHash; // Init code hash for V3 factory
        address v2Factory; // V2 factory contract address
        address v3Factory; // V3 factory contract address
        address v3Deployer; // V3 pool deployer contract address
        address v4Vault; // V4 vault contract address (if applicable)
        uint24[] v3SupportedFees; // Array of supported fee tiers for V3 pools
        address smartRouter; // Smart router address for swapping
        address v3Quoter; // V3 quoter contract for price quotes
        address v2SwapRouter; // V2 swap router contract address
        address nonfungiblePositionManager; // Non-fungible position manager for V3 liquidity positions
    }

    /// @notice Parameters for exact input single swap on V3
    /// @dev Similar to ISwapRouter.ExactInputSingleParams
    struct ExactInputSingleParams {
        address tokenIn; // Input token address
        address tokenOut; // Output token address
        uint24 fee; // Fee tier for the V3 pool
        address recipient; // Address to receive output tokens
        uint256 amountIn; // Amount of input tokens to swap
        uint256 amountOutMinimum; // Minimum amount of output tokens expected
        uint160 sqrtPriceLimitX96; // Price limit (0 = no limit)
    }

    /// @notice Parameters for quoting exact input single swap on V3
    /// @dev Similar to IV3Quoter.QuoteExactInputSingleParams
    struct QuoteExactInputSingleParams {
        address tokenIn; // Input token address
        address tokenOut; // Output token address
        uint256 amountIn; // Amount of input tokens
        uint24 fee; // Fee tier for the V3 pool
        uint160 sqrtPriceLimitX96; // Price limit (0 = no limit)
    }

    /// @notice Get DEX configuration information
    /// @param dexId The identifier of the DEX to query
    /// @return dexInfo Struct containing all DEX-related parameters
    function getDEXInfo(uint8 dexId) external view returns (DEXInfo memory dexInfo);

    /// @notice Swaps exact amount of tokens for tokens using V2 pools
    /// @dev Similar to ISwapRouter.swapExactTokensForTokens but with dexId parameter
    /// Does NOT support ADDRESS_THIS special addresses
    /// But we support CONTRACT_BALANCE (i.e, when amountIn is 0, we use the contract's balance), in
    /// such case, you have transferred the tokens to the router contract before calling this method.
    /// @param dexId The identifier of the DEX to use for swapping
    /// @param amountIn The amount of input tokens to swap
    /// @param amountOutMin The minimum amount of output tokens expected
    /// @param path The ordered list of token addresses to swap through
    /// @param to The recipient address for output tokens
    /// @return amountOut The actual amount of output tokens received
    function swapExactTokensForTokens(
        uint8 dexId,
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to
    ) external payable returns (uint256 amountOut);

    /// @notice Swaps exact amount of input token for output token using V3 pools
    /// @dev Similar to ISwapRouter.exactInputSingle but with dexId as separate parameter
    /// Does NOT support ADDRESS_THIS special addresses
    /// But we support CONTRACT_BALANCE (i.e, when amountIn is 0, we use the contract's balance), in
    /// such case, you have transferred the tokens to the router contract before calling this method.
    /// @param dexId The identifier of the DEX to use for swapping
    /// @param params The parameters necessary for the swap
    /// @return amountOut The actual amount of output tokens received
    function exactInputSingle(uint8 dexId, ExactInputSingleParams calldata params)
        external
        payable
        returns (uint256 amountOut);

    /// @notice Quote the amount out for exact input single swap on V3 pools
    /// @dev Similar to IV3Quoter.quoteExactInputSingle but with dexId as separate parameter
    /// @param dexId The identifier of the DEX to use for the quote
    /// @param params The parameters for the quote
    /// @return amountOut The amount of output tokens that would be received
    /// @return sqrtPriceX96After The sqrt price of the pool after the swap
    /// @return initializedTicksCrossed The number of initialized ticks crossed
    /// @return gasEstimate The estimated gas consumption for the swap
    function quoteExactInputSingle(uint8 dexId, QuoteExactInputSingleParams memory params)
        external
        returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate);

    /// @notice Get amounts out for exact input swap on V2 pools
    /// @dev Similar to IV2Quoter.getAmountsOut but with dexId parameter
    /// This method assumes swapping on V2 pools of the specified DEX
    /// @param dexId The identifier of the DEX to use for the quote
    /// @param amountIn The amount of input tokens
    /// @param path The ordered list of token addresses to swap through
    /// @return amounts The amounts of tokens at each step of the swap path
    function getAmountsOut(uint8 dexId, uint256 amountIn, address[] calldata path)
        external
        view
        returns (uint256[] memory amounts);

    /// @notice Compute the V2 pool address for a given token pair
    /// @param dexId The identifier of the DEX to compute the pool address for
    /// @param tokenA The first token in the pair
    /// @param tokenB The second token in the pair
    /// @return pool The computed pool address for the token pair
    function computeV2PoolAddress(uint8 dexId, address tokenA, address tokenB) external view returns (address pool);

    /// @notice Compute the V3 pool address for a given token pair and fee tier
    /// @param dexId The identifier of the DEX to compute the pool address for
    /// @param tokenA The first token in the pair
    /// @param tokenB The second token in the pair
    /// @param fee The fee tier for the V3 pool
    /// @return pool The computed pool address for the token pair and fee tier
    function computeV3PoolAddress(uint8 dexId, address tokenA, address tokenB, uint24 fee)
        external
        view
        returns (address pool);

    /// @notice Get the V2 factory address for a specific DEX
    /// @param dexId The identifier of the DEX to get the factory address for
    /// @return factory The V2 factory address
    function getV2FactoryAddress(uint8 dexId) external view returns (address factory);

    /// @notice Get the V3 factory address for a specific DEX
    /// @param dexId The identifier of the DEX to get the factory address for
    /// @return factory The V3 factory address
    function getV3FactoryAddress(uint8 dexId) external view returns (address factory);

    /// @notice Get the non-fungible position manager address for a specific DEX
    /// @param dexId The identifier of the DEX to get the position manager address for
    /// @return positionManager The non-fungible position manager address
    function getNonfungiblePositionManager(uint8 dexId) external view returns (address positionManager);

    /// @notice Get the V2 pool fee tier for a specific DEX
    /// @param dexId The identifier of the DEX to get the fee tier for
    /// @return fee The V2 pool fee tier (2500 for PancakeSwap, 3000 for others)
    function getV2PoolFeeTier(uint8 dexId) external view returns (uint24 fee);

    /// @notice Get major pools for a token pair across multiple DEXes
    /// @param preferredDexId The preferred DEX identifier (0, 1, or 2)
    /// @param preferredV3FeeTier The preferred V3 fee tier profile for the preferred DEX
    /// @param baseToken The base token address
    /// @param quoteToken The quote token address
    /// @return pools Array of major pool addresses in priority order
    function getMajorPools(
        uint8 preferredDexId,
        V3LPFeeProfile preferredV3FeeTier,
        address baseToken,
        address quoteToken
    ) external view returns (address[] memory pools);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

/// @notice Fee configuration struct (returned by feeConfig() for backward compatibility)
/// @dev Packed into a single 256-bit storage slot for gas efficiency.
/// Field breakdown:
///   - marketBps: 16 bits
///   - deflationBps: 16 bits
///   - lpBps: 16 bits
///   - dividendBps: 16 bits
///   - feeRate: 16 bits
///   - isWeth: 8 bits (boolean)
/// Total: 88 bits (fits in one storage slot)
struct PackedFeeConfig {
    uint16 marketBps;
    uint16 deflationBps;
    uint16 lpBps;
    uint16 dividendBps;
    uint16 feeRate;
    bool isWeth;
}

/// @notice Initialization parameters for TaxProcessor
/// @dev Fields added for V3 support default to zero/address(0) for backward-compatible V2 usage.
///      When creating a V2 tax token, pass zeros for all new fields below the original V2 fields.
struct TaxProcessorInitParams {
    // --- V2 fields (unchanged) ---
    address quoteToken;
    address router;
    address feeReceiver;
    address marketAddress;
    address dividendAddress;
    address taxToken;
    uint16 feeRate;
    uint16 marketBps;
    uint16 deflationBps;
    uint16 lpBps;
    uint16 dividendBps;
    // --- V3 fields (new; pass zeros for V2 tokens) ---
    /// @notice The token used for dividend distribution.
    ///         address(0) => use quoteToken (same as V2 behavior).
    ///         taxToken address => dividend in the tax token itself.
    ///         any other address => specific ERC-20 dividend token (requires SwapRegistry support).
    address dividendToken;
    /// @notice Commission receiver address (zero = commission disabled)
    address commissionReceiver;
    /// @notice Commission in bps taken from the remainder after protocol fee.
    ///         NOTE: This is NOT user-supplied — it is calculated by PortalTokenLauncher
    ///         via _commissionForTax() and passed here at initialization time.
    uint16 commissionBps;
    /// @notice The converter address authorized to execute quote->dividendToken swaps in dispatch().
    ///         Zero = not needed (used only when dividendToken != quoteToken and dividendToken != taxToken).
    address converter;
    /// @notice Reference expected output amount for liquidation threshold direction calculation.
    ///         Zero = threshold direction always returns 0 (no change), disabling the feature.
    uint256 liqExpectedOutputAmount;
}

/// @notice Fee configuration struct (V2 — backward compatible extension of PackedFeeConfig)
/// @dev Returned by feeConfigV2(). The existing feeConfig() view is unchanged.
struct PackedFeeConfigV2 {
    uint16 marketBps;
    uint16 deflationBps;
    uint16 lpBps;
    uint16 dividendBps;
    uint16 feeRate;
    bool isWeth;
    /// @notice Commission in bps taken from the remainder after protocol fee (NEW in V3)
    uint16 commissionBps;
    /// @notice The resolved dividend token address
    address dividendToken;
}

/// @notice Interface for an external tax processor that can receive tax token parts
/// and be informed about token balance changes for tracking/dividend purposes
interface ITaxProcessor {
    // --- Initialization ---

    /// @notice Initialize the tax processor with configuration parameters
    function initialize(TaxProcessorInitParams memory params) external;

    // --- Core Tax Processing ---

    /// @notice Process tax tokens by computing fees, splitting remainder, and handling distribution.
    /// @param taxAmount The total amount of tax tokens to process
    /// @return liqThresholdDirection A directional indicator for the liquidation threshold:
    ///         > 0 => increase threshold (swap output exceeded expected)
    ///         < 0 => decrease threshold (swap output was worse than expected)
    ///         == 0 => no change (output matched expected, or liqExpectedOutputAmount is zero)
    /// @dev The return value is backward-compatible: V2 tax tokens call this via `try ... {}`
    ///      and Solidity silently ignores extra return data when no return type is declared.
    function processTaxTokens(uint256 taxAmount) external returns (int8 liqThresholdDirection);

    /// @notice Process bonding curve tax by accepting quote tokens and distributing them
    function processBondingCurveTax(uint256 quoteAmount) external;

    /// @notice Dispatch accumulated balances to receivers and dividend contract.
    /// @dev When dividendToken requires a DEX swap (Case 3), only the designated converter may
    ///      trigger the swap. Non-converter callers still dispatch fee/market/commission but skip
    ///      the dividend-token swap. The dividend balance is held until the next converter call.
    function dispatch() external;

    // --- View: Addresses ---

    /// @notice Get the quote token address (WETH if isWeth, otherwise stored quoteToken)
    function getQuoteToken() external view returns (address);

    /// @notice Get WETH address
    function weth() external view returns (address);

    /// @notice Get flapBlackHole address
    function flapBlackHole() external view returns (address);

    /// @notice Get tax token address
    function taxToken() external view returns (address);

    /// @notice Get router address
    function router() external view returns (address);

    /// @notice Get fee receiver address
    function feeReceiver() external view returns (address);

    /// @notice Get market receiver address
    function marketAddress() external view returns (address);

    /// @notice Get dividend contract address
    function dividendAddress() external view returns (address);

    /// @notice Get commission receiver address (address(0) if commission disabled)
    function commissionReceiver() external view returns (address);

    /// @notice Get the converter address for MEV-protected dividend-token swaps (address(0) if not needed)
    function converter() external view returns (address);

    /// @notice Get the dividend token address (address(0) means quoteToken is used)
    function dividendToken() external view returns (address);

    /// @notice Get the SwapRegistry address (immutable, set in constructor)
    function swapRegistry() external view returns (address);

    // --- View: Balances ---

    /// @notice Get accumulated fee quote balance
    function feeQuoteBalance() external view returns (uint256);

    /// @notice Get accumulated LP quote balance
    function lpQuoteBalance() external view returns (uint256);

    /// @notice Get accumulated market quote balance
    function marketQuoteBalance() external view returns (uint256);

    /// @notice Get accumulated pending dividend quote token balance (awaiting conversion to dividend token)
    function pendingDividendQuoteTokenBalance() external view returns (uint256);

    /// @notice Get accumulated dividend quote balance
    /// @dev Deprecated: backward-compatible alias for pendingDividendQuoteTokenBalance().
    function dividendQuoteBalance() external view returns (uint256);

    /// @notice Get accumulated dividend token balance waiting to be deposited to dividend contract
    function dividendTokenBalance() external view returns (uint256);

    /// @notice Get accumulated commission quote balance
    function commissionQuoteBalance() external view returns (uint256);

    // --- View: Config ---

    /// @notice Get packed fee configuration (unchanged from V2 for backward compatibility)
    function feeConfig() external view returns (PackedFeeConfig memory);

    /// @notice Get fee configuration including commission bps (V3 extension).
    /// @dev Use this when you need the commission bps. feeConfig() is unchanged for backward compat.
    function feeConfigV2() external view returns (PackedFeeConfigV2 memory);

    /// @notice Get the commission in basis points (taken from remainder after protocol fee)
    function commissionBps() external view returns (uint16);

    /// @notice Get the reference expected output amount for liquidation threshold direction
    function liqExpectedOutputAmount() external view returns (uint256);

    /// @notice Returns true when this TaxProcessor requires a MEV-protected RPC for the converter.
    /// @dev True when dividendToken != address(0) && dividendToken != quoteToken &&
    ///      dividendToken != taxToken (Case 3 - dividend requires a DEX swap).
    ///      When true, the converter MUST call dispatch() through a MEV-protected RPC.
    function requiresMEVProtection() external view returns (bool);

    // --- View: Totals ---

    /// @notice Get total dividend tokens sent to dividend contract
    function totalDividendTokenSent() external view returns (uint256);

    /// @notice Get total quote tokens sent to dividend contract
    /// @dev Deprecated: backward-compatible alias for totalDividendTokenSent().
    ///      The returned value represents total dividend tokens sent (not just quote tokens).
    ///      Its semantic does not match its name.
    function totalQuoteSentToDividend() external view returns (uint256);

    /// @notice Get total quote tokens added to liquidity
    function totalQuoteAddedToLiquidity() external view returns (uint256);

    /// @notice Get total tax tokens added to liquidity
    function totalTokenAddedToLiquidity() external view returns (uint256);

    /// @notice Get total quote tokens sent to marketing wallet
    function totalQuoteSentToMarketing() external view returns (uint256);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract ContextUpgradeable is Initializable {
    function __Context_init() internal onlyInitializing {
    }

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized != type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControlUpgradeable {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

File 18 of 20 : IUniswapV3MintCallback.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Callback for IUniswapV3PoolActions#mint
/// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface
interface IUniswapV3MintCallback {
    /// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#mint.
    /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity.
    /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
    /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity
    /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity
    /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#mint call
    function uniswapV3MintCallback(
        uint256 amount0Owed,
        uint256 amount1Owed,
        bytes calldata data
    ) external;
}

File 19 of 20 : IPancakeV3MintCallback.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Callback for IPancakeV3PoolActions#mint
/// @notice Any contract that calls IPancakeV3PoolActions#mint must implement this interface
interface IPancakeV3MintCallback {
    /// @notice Called to `msg.sender` after minting liquidity to a position from IPancakeV3Pool#mint.
    /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity.
    /// The caller of this method must be checked to be a PancakeV3Pool deployed by the canonical PancakeV3Factory.
    /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity
    /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity
    /// @param data Any data passed through by the caller via the IPancakeV3PoolActions#mint call
    function pancakeV3MintCallback(
        uint256 amount0Owed,
        uint256 amount1Owed,
        bytes calldata data
    ) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library AddressUpgradeable {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

Settings
{
  "remappings": [
    "ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
    "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    "forge-std/=lib/forge-std/src/",
    "@openzeppelin/=lib/openzeppelin-contracts/contracts/",
    "@openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
    "@ozv5/=lib/ozv5/contracts/",
    "solady/=lib/solady/src/",
    "uni-v3-core/=lib/v3-core/contracts/",
    "uni-v4-core/=lib/v4-core/src/",
    "uni-v4-periphery/=lib/v4-periphery/contracts/",
    "uni-v2-core/=lib/v2-core/contracts/",
    "uni-v2-periphery/=lib/v2-periphery/contracts/",
    "pancake-v3-core/=lib/pancake-v3-contracts/projects/v3-core/contracts/",
    "izi-periphery/=lib/iZiSwap-periphery/contracts/",
    "infinity-core/=lib/infinity-core/src/",
    "@ensdomains/=lib/v4-core/node_modules/@ensdomains/",
    "@uniswap/v4-core/=lib/v4-periphery/lib/v4-core/",
    "forge-gas-snapshot/=lib/v4-periphery/lib/forge-gas-snapshot/src/",
    "halmos-cheatcodes/=lib/ozv5/lib/halmos-cheatcodes/src/",
    "hardhat/=lib/v4-core/node_modules/hardhat/",
    "iZiSwap-periphery/=lib/iZiSwap-periphery/contracts/",
    "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/",
    "openzeppelin/=lib/openzeppelin-contracts-upgradeable/contracts/",
    "ozv5/=lib/ozv5/",
    "pancake-create3-factory/=lib/infinity-core/lib/pancake-create3-factory/",
    "pancake-v3-contracts/=lib/pancake-v3-contracts/",
    "solmate/=lib/infinity-core/lib/solmate/",
    "v2-core/=lib/v2-core/contracts/",
    "v2-periphery/=lib/v2-periphery/contracts/",
    "v3-core/=lib/v3-core/",
    "v4-core/=lib/v4-core/src/",
    "v4-periphery/=lib/v4-periphery/contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 99999
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": false
}

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"weth_","type":"address"},{"internalType":"address","name":"flapBlackHole_","type":"address"},{"internalType":"address","name":"portal_","type":"address"},{"internalType":"address","name":"swapRegistry_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"quoteAmount","type":"uint256"}],"name":"FlapTaxProcessorBondingCurveTax","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"quoteAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokensBurned","type":"uint256"}],"name":"FlapTaxProcessorBurnExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"quoteAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gasLeft","type":"uint256"},{"indexed":false,"internalType":"string","name":"reason","type":"string"}],"name":"FlapTaxProcessorBuyBackSkipped","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint16","name":"bps","type":"uint16"}],"name":"FlapTaxProcessorCommissionConfigUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FlapTaxProcessorCommissionPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldConverter","type":"address"},{"indexed":false,"internalType":"address","name":"newConverter","type":"address"}],"name":"FlapTaxProcessorConverterUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"marketAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dividendAmount","type":"uint256"}],"name":"FlapTaxProcessorDispatchExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"dividendToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"quoteIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dividendOut","type":"uint256"}],"name":"FlapTaxProcessorDividendConverted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"string","name":"reason","type":"string"}],"name":"FlapTaxProcessorDividendDepositSkipped","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldLimit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"FlapTaxProcessorMaxBuyBackGasLimitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newAmount","type":"uint256"}],"name":"FlapTaxProcessorMinBuyBackQuoteUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"refundAmount","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isWeth","type":"bool"}],"name":"FlapTaxProcessorPortalRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"taxAmount","type":"uint256"}],"name":"FlapTaxProcessorProcessTaxTokens","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taxToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FlapTaxProcessorTokensBurned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"DEAD_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DISPATCH_IMPL","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"commissionBps","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"commissionQuoteBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"commissionReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"converter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dispatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dividendAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dividendQuoteBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dividendToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dividendTokenBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeConfig","outputs":[{"components":[{"internalType":"uint16","name":"marketBps","type":"uint16"},{"internalType":"uint16","name":"deflationBps","type":"uint16"},{"internalType":"uint16","name":"lpBps","type":"uint16"},{"internalType":"uint16","name":"dividendBps","type":"uint16"},{"internalType":"uint16","name":"feeRate","type":"uint16"},{"internalType":"bool","name":"isWeth","type":"bool"}],"internalType":"struct PackedFeeConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeConfigV2","outputs":[{"components":[{"internalType":"uint16","name":"marketBps","type":"uint16"},{"internalType":"uint16","name":"deflationBps","type":"uint16"},{"internalType":"uint16","name":"lpBps","type":"uint16"},{"internalType":"uint16","name":"dividendBps","type":"uint16"},{"internalType":"uint16","name":"feeRate","type":"uint16"},{"internalType":"bool","name":"isWeth","type":"bool"},{"internalType":"uint16","name":"commissionBps","type":"uint16"},{"internalType":"address","name":"dividendToken","type":"address"}],"internalType":"struct PackedFeeConfigV2","name":"result","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeQuoteBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"flapBlackHole","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getQuoteToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"quoteToken","type":"address"},{"internalType":"address","name":"router","type":"address"},{"internalType":"address","name":"feeReceiver","type":"address"},{"internalType":"address","name":"marketAddress","type":"address"},{"internalType":"address","name":"dividendAddress","type":"address"},{"internalType":"address","name":"taxToken","type":"address"},{"internalType":"uint16","name":"feeRate","type":"uint16"},{"internalType":"uint16","name":"marketBps","type":"uint16"},{"internalType":"uint16","name":"deflationBps","type":"uint16"},{"internalType":"uint16","name":"lpBps","type":"uint16"},{"internalType":"uint16","name":"dividendBps","type":"uint16"},{"internalType":"address","name":"dividendToken","type":"address"},{"internalType":"address","name":"commissionReceiver","type":"address"},{"internalType":"uint16","name":"commissionBps","type":"uint16"},{"internalType":"address","name":"converter","type":"address"},{"internalType":"uint256","name":"liqExpectedOutputAmount","type":"uint256"}],"internalType":"struct TaxProcessorInitParams","name":"params","type":"tuple"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"liqExpectedOutputAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lpQuoteBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"marketAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"marketQuoteBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxBuyBackGasLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minBuyBackQuote","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingDividendQuoteTokenBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"portal","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"preBondBurnFunds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quoteAmount","type":"uint256"}],"name":"processBondingCurveTax","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"taxAmount","type":"uint256"}],"name":"processTaxTokens","outputs":[{"internalType":"int8","name":"liqThresholdDirection","type":"int8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"quoteToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requiresMEVProtection","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"router","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint16","name":"bps","type":"uint16"}],"name":"setCommissionConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"converter_","type":"address"}],"name":"setConverter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"setDividendToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"setLiqExpectedOutputAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"setMaxBuyBackGasLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newAmount","type":"uint256"}],"name":"setMinBuyBackQuote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"feeReceiver_","type":"address"},{"internalType":"address","name":"marketAddress_","type":"address"},{"internalType":"address","name":"dividendAddress_","type":"address"}],"name":"setReceivers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"feeRate_","type":"uint16"},{"internalType":"uint16","name":"marketBps_","type":"uint16"},{"internalType":"uint16","name":"deflationBps_","type":"uint16"},{"internalType":"uint16","name":"lpBps_","type":"uint16"},{"internalType":"uint16","name":"dividendBps_","type":"uint16"}],"name":"setTaxConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swapRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"taxToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalDividendTokenSent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalQuoteAddedToLiquidity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalQuoteSentToDividend","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalQuoteSentToMarketing","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalTokenAddedToLiquidity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"weth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"withdrawAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

Block Transaction Gas Used Reward
view all blocks produced
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
View All Validatorset

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.