ETH Price: $2,035.00 (+3.62%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Redeem Winnings245580302026-02-28 21:11:352 days ago1772313095IN
0xf19f8D7E...d9686db59
0 ETH0.000011920.10075763
Remove Liquidity245504752026-02-27 19:51:353 days ago1772221895IN
0xf19f8D7E...d9686db59
0 ETH0.000005950.0414613
Buy245501772026-02-27 18:51:593 days ago1772218319IN
0xf19f8D7E...d9686db59
0 ETH0.000012060.08147299

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Method Block
From
To
0x3d602d80245499622026-02-27 18:08:593 days ago1772215739  Contract Creation0 ETH
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Minimal Proxy Contract for 0x7d45d4835001347b31b722fb830fc1d9336f09f4

Contract Name:
PredictionAMM

Compiler Version
v0.8.23+commit.f704f362

Optimization Enabled:
Yes with 99999 runs

Other Settings:
paris EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 21 : PredictionAMM.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {OutcomeToken} from "./OutcomeToken.sol";
import {IPredictionAMM} from "./interfaces/IPredictionAMM.sol";
import {PollStatus, IPredictionPoll} from "../oracle/interfaces/IPredictionOracle.sol";
import {IMarketFactory} from "./interfaces/IMarketFactory.sol";
import {AMMath} from "./libraries/AMMath.sol";

/**
 * @title PredictionAMM
 * @notice Automated Market Maker for prediction market outcome tokens using CPMM (x*y=k)
 * @dev Implements Fixed Product Market Maker for binary outcomes (YES/NO)
 * @dev Uses EIP-1167 Clone Pattern (minimal proxy with DELEGATECALL, non-upgradeable)
 *
 * @dev This contract incorporates design patterns and code inspired by:
 * - Uniswap V2: Constant Product Market Maker (x*y=k), reserve management, fee structure
 *   https://github.com/Uniswap/v2-core
 * - Gnosis Conditional Tokens: Outcome token minting/burning, liquidity provision mechanics
 *   https://github.com/gnosis/conditional-tokens-contracts
 */
contract PredictionAMM is IPredictionAMM, ERC20, ReentrancyGuard {
    using SafeERC20 for IERC20;

    // ============================================
    // STATE VARIABLES (Optimized storage packing)
    // ============================================

    /// @notice Market creator
    address public creator;

    /// @notice Poll contract address
    address public pollAddress;

    /// @notice Collateral token (USDC, USDT, etc.)
    address public collateralToken;

    /// @notice YES outcome token
    address public yesToken;

    /// @notice NO outcome token
    address public noToken;

    /// @notice Factory contract address (can withdraw protocol fees)
    address public factory;

    /// @notice Packed reserves and timestamp
    /// @dev Layout: [0-111] reserveYes, [112-223] reserveNo, [224-255] marketCloseTimestamp
    uint256 private _reserveData;

    /// @notice Protocol fees collected (in collateral, from sell operations only)
    /// @dev Split 50/50 between platform treasury and market creator on withdrawal
    uint112 public protocolFeesCollected;

    /// @notice Trading fee rate (Uniswap V3 style: 500 = 0.05%, 3000 = 0.3%, 10000 = 1%)
    uint24 public tradingFee;

    /// @notice Protocol fee rate (max 50000 = 5%)
    /// @dev Uses same denominator as tradingFee (BPS_DENOMINATOR = 1_000_000)
    uint24 public protocolFeeRate;

    /// @notice Maximum allowed hourly price imbalance (scaled by BPS_DENOMINATOR, 0 = disabled)
    uint24 public maxPriceImbalancePerHour;

    /// @notice Precision multiplier to normalize LP tokens to 18 decimals
    /// @dev lpPrecision = 10^(18 - collateralDecimals), e.g., 10^12 for USDC (6 decimals)
    /// @dev Max value is 10^18 (for 0 decimals), fits in uint64
    uint64 public lpPrecision;

    /// @notice Initialization flag (for clone pattern)
    /// @dev Packed in slot 7: protocolFeesCollected(14) + tradingFee(3) + protocolFeeRate(3) + maxPriceImbalancePerHour(3) + lpPrecision(8) + _initialized(1) = 32 bytes
    bool private _initialized;

    /// @notice Pre-computed max price swing in ONE scale
    /// @dev Calculated as: maxPriceImbalancePerHour * ONE / BPS_DENOMINATOR
    uint256 private _maxPriceSwingScaled;

    // ============================================
    // PRICE WINDOW CONSTANTS
    // ============================================

    /// @notice Duration of price tracking window in seconds (1 hour = 3600)
    uint32 private constant WINDOW_DURATION = 3600;

    // ============================================
    // PRICE WINDOW STORAGE
    // ============================================

    /// @notice Packed price window data in single storage slot (224 bits used)
    /// @dev Layout: hour(32) | anchorPrice(64) | minPrice(64) | maxPrice(64)
    ///      - hour: Current window identifier (block.timestamp / WINDOW_DURATION)
    ///      - anchorPrice: First price recorded in this window (for reference)
    ///      - minPrice: Minimum price observed in this window
    ///      - maxPrice: Maximum price observed in this window
    ///      Swing limit: (maxPrice - minPrice) <= maxPriceSwingScaled
    uint256 private _priceWindow;

    // Bit masks and offsets for unpacking
    uint256 private constant HOUR_MASK = 0xffffffff;
    uint256 private constant PRICE64_MASK = 0xffffffffffffffff;
    uint256 private constant ANCHOR_OFFSET = 32;
    uint256 private constant MIN_OFFSET = 96;
    uint256 private constant MAX_OFFSET = 160;

    // ============================================
    // MODIFIERS
    // ============================================

    /// @notice Restrict to one-time initialization (for clone pattern)
    modifier initializer() {
        if (_initialized) revert AlreadyInitialized();
        _initialized = true;
        _;
    }

    /// @dev Modifier to check if the current block timestamp is before or equal to the deadline.
    modifier checkDeadline(uint256 deadline) {
        if (_blockTimestamp() > deadline)
            revert TxTooOld(_blockTimestamp(), deadline);
        _;
    }

    // ============================================
    // CONSTANTS
    // ============================================

    /// @notice ONE = 10^18 for precision
    uint256 public constant ONE = 10 ** 18;

    /// @notice Fee denominator for all fee calculations
    /// @dev Used for both tradingFee and protocolFeeRate for consistency
    uint256 public constant BPS_DENOMINATOR = 1_000_000;

    /// @notice Minimum liquidity to prevent division by zero
    uint256 public constant MINIMUM_LIQUIDITY = 1000;

    /// @notice Dead address for permanently locking minimum liquidity (Uniswap V2 style)
    /// @dev Standard burn address used across DeFi (Uniswap V3, SushiSwap, etc.)
    address public constant DEAD_ADDRESS =
        0x000000000000000000000000000000000000dEaD;

    // ============================================
    // STORAGE PACKING MASKS
    // ============================================

    /// @notice Mask for reserveYes (bits 0-111)
    uint256 private constant RESERVE_YES_MASK = (uint256(1) << 112) - 1;

    /// @notice Mask for reserveNo (bits 112-223)
    uint256 private constant RESERVE_NO_MASK = ((uint256(1) << 112) - 1) << 112;

    /// @notice Mask for marketCloseTimestamp (bits 224-255)
    uint256 private constant TIMESTAMP_MASK = ((uint256(1) << 32) - 1) << 224;

    // ============================================
    // CONSTRUCTOR & INITIALIZER
    // ============================================

    constructor() ERC20("", "") {
        // Lock implementation contract
        _initialized = true;
    }

    /**
     * @notice Initialize AMM clone (for EIP-1167 pattern)
     * @param _creator Market creator address
     * @param _pollAddress Poll contract address
     * @param _collateral Collateral token address
     * @param _yesToken YES outcome token address
     * @param _noToken NO outcome token address
     * @param _marketDeadlineTimestamp Market close timestamp
     * @param _tradingFee Trading fee tier
     * @param _protocolFeeRate Protocol fee rate
     * @param _maxPriceImbalancePerHour Maximum allowed hourly price imbalance (0 = disabled)
     */
    function initialize(
        address _creator,
        address _pollAddress,
        address _collateral,
        address _yesToken,
        address _noToken,
        uint32 _marketDeadlineTimestamp,
        uint24 _tradingFee,
        uint24 _protocolFeeRate,
        uint24 _maxPriceImbalancePerHour
    ) external initializer {
        creator = _creator;
        pollAddress = _pollAddress;
        collateralToken = _collateral;
        yesToken = _yesToken;
        noToken = _noToken;
        factory = msg.sender;
        tradingFee = _tradingFee;
        protocolFeeRate = _protocolFeeRate;
        maxPriceImbalancePerHour = _maxPriceImbalancePerHour;
        // Compute scaled value: limit * ONE / BPS_DENOMINATOR
        // Multiplication first for precision
        _maxPriceSwingScaled = Math.mulDiv(
            uint256(_maxPriceImbalancePerHour),
            ONE,
            BPS_DENOMINATOR
        );

        // Calculate LP precision multiplier to normalize to 18 decimals
        // e.g., USDC (6 decimals) -> lpPrecision = 10^12
        uint8 collateralDecimals = IERC20Metadata(_collateral).decimals();
        lpPrecision = uint64(10 ** (18 - collateralDecimals));

        // Initialize packed reserves (0, 0, timestamp)
        _updateReservesData(0, 0, _marketDeadlineTimestamp);
    }

    /**
     * @notice Override name for ERC20 LP token
     */
    function name() public pure override returns (string memory) {
        return "Prediction AMM LP";
    }

    /**
     * @notice Override symbol for ERC20 LP token
     */
    function symbol() public pure override returns (string memory) {
        return "pAMM-LP";
    }

    // ============================================
    // LIQUIDITY MANAGEMENT
    // ============================================

    /**
     * @notice Add liquidity to the pool
     * @dev Mints equal amounts of YES and NO tokens, adds proportionally, returns excess
     * @param collateralAmt Amount of collateral to add
     * @param distributionHint [yesWeight, noWeight] for initial price (only first LP, [0,0] to skip)
     * @param minYesToAdd Minimum YES tokens that must be added to the pool (slippage protection)
     * @param minNoToAdd Minimum NO tokens that must be added to the pool (slippage protection)
     * @param deadline Deadline for the transaction
     * @return mintAmount Amount of LP tokens minted
     */
    function addLiquidity(
        uint256 collateralAmt,
        uint256[2] calldata distributionHint,
        uint256 minYesToAdd,
        uint256 minNoToAdd,
        uint256 deadline
    )
        external
        nonReentrant
        checkDeadline(deadline)
        returns (uint256 mintAmount)
    {
        if (collateralAmt == 0) revert ZeroAmount();
        Reserves memory reserves;
        {
            uint32 timestamp;
            // Unpack reserves and timestamp
            (reserves.rYes, reserves.rNo, timestamp) = _getReservesData();
            // Check market is live (using unpacked timestamp)
            _checkMarketLive(timestamp);
        }

        // Transfer collateral from user
        IERC20(collateralToken).safeTransferFrom(
            msg.sender,
            address(this),
            collateralAmt
        );
        address lpReceiver = msg.sender;

        // Calculate LP tokens and amounts to add
        uint256 poolShareSupply = totalSupply();
        OutcomeAmounts memory amounts;

        if (poolShareSupply == 0) {
            // First liquidity provider
            lpReceiver = creator;
            // Normalize to 18 decimals for LP tokens
            mintAmount = collateralAmt * lpPrecision;
            // Lock minimum liquidity permanently (Uniswap V2 style)
            if (mintAmount <= MINIMUM_LIQUIDITY) revert InsufficientLiquidity();
            mintAmount -= MINIMUM_LIQUIDITY;

            // Permanently lock MINIMUM_LIQUIDITY by minting to DEAD_ADDRESS
            // Using standard burn address (0x...dEaD) - no private key exists
            // This prevents division by zero and follows Uniswap V2 pattern
            _mint(DEAD_ADDRESS, MINIMUM_LIQUIDITY);

            // Use distribution hint to set initial price (Gnosis FPMM style)
            if (distributionHint[0] > 0 || distributionHint[1] > 0) {
                uint256 maxHint = Math.max(
                    distributionHint[0],
                    distributionHint[1]
                );

                // Calculate proportional amounts based on hint
                // Example: hint=[70,30] means YES gets 70%, NO gets 30% of liquidity
                amounts.yesToAdd = Math.mulDiv(
                    collateralAmt,
                    distributionHint[0],
                    maxHint
                );
                amounts.noToAdd = Math.mulDiv(
                    collateralAmt,
                    distributionHint[1],
                    maxHint
                );

                if (amounts.yesToAdd == 0 || amounts.noToAdd == 0) {
                    revert InvalidDistributionHint();
                }

                // Excess to return
                amounts.yesToReturn = collateralAmt - amounts.yesToAdd;
                amounts.noToReturn = collateralAmt - amounts.noToAdd;
            } else {
                // No hint: balanced 50/50 start
                amounts.yesToAdd = collateralAmt;
                amounts.noToAdd = collateralAmt;
            }

            _checkAndUpdatePrice(
                _priceYesFromReserves(
                    Reserves({
                        rYes: SafeCast.toUint112(amounts.yesToAdd),
                        rNo: SafeCast.toUint112(amounts.noToAdd)
                    })
                )
            );
        } else {
            // Subsequent LPs: cannot use hint, must follow current proportions
            require(
                distributionHint[0] == 0 && distributionHint[1] == 0,
                "Cannot use hint after initial funding"
            );

            // Calculate proportional amounts
            uint256 poolWeight = Math.max(reserves.rYes, reserves.rNo);
            // Normalize to 18 decimals for LP tokens
            // poolShareSupply is in 18 decimals, poolWeight is in collateral decimals
            mintAmount = Math.mulDiv(
                collateralAmt * lpPrecision,
                poolShareSupply,
                poolWeight * lpPrecision
            );

            // Calculate what to add to maintain proportion
            amounts.yesToAdd = Math.mulDiv(
                collateralAmt,
                reserves.rYes,
                poolWeight
            );
            amounts.noToAdd = Math.mulDiv(
                collateralAmt,
                reserves.rNo,
                poolWeight
            );

            // Calculate excess to return
            amounts.yesToReturn = collateralAmt - amounts.yesToAdd;
            amounts.noToReturn = collateralAmt - amounts.noToAdd;
        }
        if (mintAmount == 0) revert InsufficientLiquidity();

        // Slippage protection on amounts added to the pool (Uniswap V2-style min amounts)
        if (amounts.yesToAdd < minYesToAdd || amounts.noToAdd < minNoToAdd) {
            revert SlippageExceeded();
        }

        // Mint equal YES and NO tokens
        _mintPairs(collateralAmt, address(this));

        // Mint LP tokens to user (ERC20 style)
        _mint(lpReceiver, mintAmount);

        // Update reserves in memory
        reserves.rYes += SafeCast.toUint112(amounts.yesToAdd);
        reserves.rNo += SafeCast.toUint112(amounts.noToAdd);

        // Pack back to storage
        _updateReserves(reserves.rYes, reserves.rNo);

        // Return excess tokens
        _transferPairs(amounts.yesToReturn, amounts.noToReturn, lpReceiver);

        emit LiquidityAdded(lpReceiver, collateralAmt, mintAmount, amounts);
    }

    /**
     * @notice Remove liquidity from the pool
     * @dev Returns proportional outcome tokens (YES + NO) with fees included in reserves
     * @dev LP can then call redeemWinnings() or burnCompleteSets() to convert to collateral
     * @param sharesToBurn Amount of LP tokens to burn
     * @param minCollateralOut Minimum collateral to receive (slippage protection, 0 to skip)
     * @param deadline Deadline for the transaction
     */
    function removeLiquidity(
        uint256 sharesToBurn,
        uint256 minCollateralOut,
        uint256 deadline
    ) external nonReentrant checkDeadline(deadline) {
        if (sharesToBurn == 0) revert ZeroAmount();

        // Read reserves once from storage
        (uint112 rYes, uint112 rNo, uint32 closeTimestamp) = _getReservesData();
        bool isMarketLive = _blockTimestamp() < closeTimestamp;

        // Calculate what LP will receive
        (
            uint256 yesToReturn,
            uint256 noToReturn,
            uint256 collateralToReturn
        ) = _calcRemoveLiquidity(isMarketLive, sharesToBurn, rYes, rNo);

        // Slippage check for collateral
        if (isMarketLive && collateralToReturn < minCollateralOut)
            revert SlippageExceeded();

        // Update reserves (subtract full amounts before collateral extraction)
        uint112 newRYes = rYes -
            SafeCast.toUint112(yesToReturn + collateralToReturn);
        uint112 newRNo = rNo -
            SafeCast.toUint112(noToReturn + collateralToReturn);
        _updateReserves(newRYes, newRNo);

        // Burn LP tokens
        _burn(msg.sender, sharesToBurn);

        // Transfer assets to LP
        if (collateralToReturn > 0) {
            _burnPairs(collateralToReturn, address(this));
            IERC20(collateralToken).safeTransfer(
                msg.sender,
                collateralToReturn
            );
        }
        _transferPairs(yesToReturn, noToReturn, msg.sender);

        emit LiquidityRemoved(
            msg.sender,
            sharesToBurn,
            yesToReturn,
            noToReturn,
            collateralToReturn
        );
    }

    /**
     * @notice Withdraw protocol fees
     * @dev Automatically splits 50/50 between platform treasury and market creator
     * @return totalAmount Total amount of protocol fees withdrawn
     */
    function withdrawProtocolFees()
        external
        nonReentrant
        returns (uint256 totalAmount)
    {
        totalAmount = protocolFeesCollected;
        if (totalAmount == 0) {
            return 0;
        }

        protocolFeesCollected = 0;
        address platformTreasury = IMarketFactory(factory).platformTreasury();
        uint256 platformShare = totalAmount / 2;
        uint256 creatorShare = totalAmount - platformShare;

        IERC20(collateralToken).safeTransfer(platformTreasury, platformShare);
        IERC20(collateralToken).safeTransfer(creator, creatorShare);

        emit ProtocolFeesWithdrawn(msg.sender, platformShare, creatorShare);
    }

    // ============================================
    // TRADING FUNCTIONS
    // ============================================

    /**
     * @notice Buy outcome tokens with collateral
     * @param isYes True if buying YES, false if buying NO
     * @param collateralAmtIn Amount of collateral to spend
     * @param minTokenOut Minimum outcome tokens to receive (slippage protection)
     * @param deadline Transaction deadline timestamp
     * @return amountOut Amount of outcome tokens received
     */
    function buy(
        bool isYes,
        uint256 collateralAmtIn,
        uint256 minTokenOut,
        uint256 deadline
    )
        external
        nonReentrant
        checkDeadline(deadline)
        returns (uint256 amountOut)
    {
        if (collateralAmtIn == 0) revert ZeroAmount();
        Reserves memory reserves = _getReservesAndCheckLive();
        return _buyTokens(isYes, collateralAmtIn, minTokenOut, reserves);
    }

    /**
     * @notice Sell outcome tokens for collateral
     * @param isYes True if selling YES, false if selling NO
     * @param tokenAmountIn Amount of outcome tokens to sell
     * @param minCollateralOut Minimum collateral to receive (slippage protection)
     * @param deadline Transaction deadline timestamp
     * @return collateralOut Amount of collateral received
     */
    function sell(
        bool isYes,
        uint112 tokenAmountIn,
        uint256 minCollateralOut,
        uint256 deadline
    )
        external
        nonReentrant
        checkDeadline(deadline)
        returns (uint256 collateralOut)
    {
        if (tokenAmountIn == 0) revert ZeroAmount();
        Reserves memory reserves = _getReservesAndCheckLive();
        return _sellTokens(isYes, tokenAmountIn, minCollateralOut, reserves);
    }

    // ============================================
    // DIRECT SWAP FUNCTIONS (YES ↔ NO)
    // ============================================

    /**
     * @notice Swap exact amount of input tokens for output tokens (YES↔NO without USDC)
     * @dev Uses constant product formula with fee, no minting/burning
     * @param yesToNo True to swap YES→NO, false to swap NO→YES
     * @param amountIn Exact amount of tokens to swap
     * @param minAmountOut Minimum tokens to receive (slippage protection)
     * @param deadline Transaction deadline timestamp
     * @return amountOut Amount of tokens received
     */
    function swapExactIn(
        bool yesToNo,
        uint256 amountIn,
        uint256 minAmountOut,
        uint256 deadline
    )
        external
        nonReentrant
        checkDeadline(deadline)
        returns (uint256 amountOut)
    {
        if (amountIn == 0) revert ZeroAmount();
        Reserves memory reserves = _getReservesAndCheckLive();
        (, amountOut) = _swapTokensDirect(
            yesToNo,
            amountIn,
            minAmountOut,
            true,
            reserves
        );
    }

    /**
     * @notice Swap input tokens for exact amount of output tokens (YES↔NO without USDC)
     * @dev Uses constant product formula with fee, no minting/burning
     * @param yesToNo True to swap YES→NO, false to swap NO→YES
     * @param amountOut Exact amount of tokens to receive
     * @param maxAmountIn Maximum tokens willing to spend (slippage protection)
     * @param deadline Transaction deadline timestamp
     * @return amountIn Amount of tokens spent
     */
    function swapExactOut(
        bool yesToNo,
        uint256 amountOut,
        uint256 maxAmountIn,
        uint256 deadline
    ) external nonReentrant checkDeadline(deadline) returns (uint256 amountIn) {
        if (amountOut == 0) revert ZeroAmount();
        Reserves memory reserves = _getReservesAndCheckLive();
        (amountIn, ) = _swapTokensDirect(
            yesToNo,
            amountOut,
            maxAmountIn,
            false,
            reserves
        );
    }

    // ============================================
    // RESOLUTION & REDEEM
    // ============================================

    /**
     * @notice Redeem outcome tokens for collateral after resolution
     * @dev Handles YES/NO/Unknown outcomes differently
     * @return collateralAmount Amount of collateral redeemed
     */
    function redeemWinnings()
        external
        nonReentrant
        returns (uint256 collateralAmount)
    {
        (bool isFinalized, PollStatus outcome) = IPredictionPoll(pollAddress)
            .getFinalizedStatus();

        if (!isFinalized) revert MarketNotResolved();

        uint256 yesBalance = OutcomeToken(yesToken).balanceOf(msg.sender);
        uint256 noBalance = OutcomeToken(noToken).balanceOf(msg.sender);

        // Calculate payout based on outcome (Gnosis FPMM style with payout numerators)
        if (outcome == PollStatus.Yes) {
            // YES won: payout numerators = [1, 0], denominator = 1
            // YES → 1:1, NO → 0
            collateralAmount = yesBalance;
        } else if (outcome == PollStatus.No) {
            // NO won: payout numerators = [0, 1], denominator = 1
            // NO → 1:1, YES → 0
            collateralAmount = noBalance;
        } else {
            // Unknown: payout numerators = [1, 1], denominator = 2
            // Each token worth 0.5 collateral (50/50 split)
            // payout = (yesBalance × 1) / 2 + (noBalance × 1) / 2
            collateralAmount = (yesBalance + noBalance) / 2;
        }

        if (collateralAmount == 0) revert NothingToRedeem();

        // Burn user's tokens
        if (yesBalance > 0) OutcomeToken(yesToken).burn(msg.sender, yesBalance);
        if (noBalance > 0) OutcomeToken(noToken).burn(msg.sender, noBalance);

        if (outcome != PollStatus.Unknown && outcome != PollStatus.Pending) {
            // Calculate and collect protocol fee (using helper)
            uint256 protocolFee = _calculateAndCollectProtocolFee(
                collateralAmount
            );
            collateralAmount -= protocolFee;
        }

        // Transfer collateral to user (after fee)
        IERC20(collateralToken).safeTransfer(msg.sender, collateralAmount);

        emit WinningsRedeemed(
            msg.sender,
            yesBalance,
            noBalance,
            collateralAmount
        );
    }

    // force reserves to match balances
    function sync() external nonReentrant {
        uint112 balanceYes = SafeCast.toUint112(
            IERC20(yesToken).balanceOf(address(this))
        );
        uint112 balanceNo = SafeCast.toUint112(
            IERC20(noToken).balanceOf(address(this))
        );
        _updateReserves(balanceYes, balanceNo);
        emit Sync(balanceYes, balanceNo);
    }

    // ============================================
    // VIEW FUNCTIONS
    // ============================================

    /**
     * @notice Get current YES token price in collateral
     * @return price Price of 1 YES token in collateral (scaled by 1e18)
     */
    function getYesPrice() external view returns (uint256 price) {
        (uint112 reserveYes, uint112 reserveNo) = _getReserves();
        // Price = opposite reserve / total reserves (using mulDiv for overflow protection)
        price = Math.mulDiv(reserveNo, ONE, reserveYes + reserveNo);
    }

    /**
     * @notice Get current NO token price in collateral
     * @return price Price of 1 NO token in collateral (scaled by 1e18)
     */
    function getNoPrice() external view returns (uint256 price) {
        (uint112 reserveYes, uint112 reserveNo) = _getReserves();
        // Using mulDiv for overflow protection
        price = Math.mulDiv(reserveYes, ONE, reserveYes + reserveNo);
    }

    /**
     * @notice Get market information
     * @return Market information
     */
    function getMarketInfo() public view returns (MarketInfo memory) {
        return
            MarketInfo({
                creator: creator,
                pollAddress: pollAddress,
                yesToken: yesToken,
                noToken: noToken,
                collateralToken: collateralToken,
                tradingFee: tradingFee,
                protocolFeeRate: protocolFeeRate,
                maxPriceImbalancePerHour: maxPriceImbalancePerHour,
                closeTimestamp: marketCloseTimestamp()
            });
    }

    /**
     * @notice Get current price window statistics
     * @dev Returns all tracked values for the current hour window.
     *      If no trades have occurred in the current hour, returns data from last active hour.
     *
     * @return hour Current window hour identifier
     * @return anchorPrice Baseline price (first trade of the hour)
     * @return minPrice Lowest price observed this hour
     * @return maxPrice Highest price observed this hour
     * @return currentSwing Current price swing (maxPrice - minPrice)
     * @return isCurrentHour True if data is from current hour, false if stale
     */
    function getPriceWindowStats()
        external
        view
        returns (
            uint32 hour,
            uint64 anchorPrice,
            uint64 minPrice,
            uint64 maxPrice,
            uint64 currentSwing,
            bool isCurrentHour
        )
    {
        uint256 data = _priceWindow;

        hour = uint32(data & HOUR_MASK);
        anchorPrice = uint64((data >> ANCHOR_OFFSET) & PRICE64_MASK);
        minPrice = uint64((data >> MIN_OFFSET) & PRICE64_MASK);
        maxPrice = uint64((data >> MAX_OFFSET) & PRICE64_MASK);
        currentSwing = maxPrice - minPrice;
        isCurrentHour = (hour == uint32(_blockTimestamp() / WINDOW_DURATION));
    }

    /**
     * @notice Get all reserves and LP info in one call
     * @dev Trading fees are included in reserves, not in separate pool
     * @return _reserveYes YES token reserve (includes accumulated trading fees)
     * @return _reserveNo NO token reserve (includes accumulated trading fees)
     * @return _totalLP Total LP tokens issued
     * @return _protocolFees Protocol fees collected (from sell operations, split 50/50 on withdrawal)
     */
    function getReserves()
        external
        view
        returns (
            uint112 _reserveYes,
            uint112 _reserveNo,
            uint256 _totalLP,
            uint256 _protocolFees,
            uint256 _collateralTvl
        )
    {
        (_reserveYes, _reserveNo) = _getReserves();
        _totalLP = totalSupply();
        _protocolFees = protocolFeesCollected;
        _collateralTvl =
            IERC20(collateralToken).balanceOf(address(this)) -
            protocolFeesCollected;
    }

    /**
     * @notice Calculate expected LP tokens and token distribution when adding liquidity
     * @dev Simulates addLiquidity for subsequent LPs (pool must be initialized)
     * @param collateralAmt Amount of collateral to add
     * @return mintAmount Amount of LP tokens to receive
     * @return amounts Token amounts (yesToAdd, noToAdd, yesToReturn, noToReturn)
     */
    function calcAddLiquidity(
        uint256 collateralAmt
    )
        external
        view
        returns (uint256 mintAmount, OutcomeAmounts memory amounts)
    {
        if (collateralAmt == 0) return (0, amounts);

        (uint112 rYes, uint112 rNo, ) = _getReservesData();
        uint256 poolShareSupply = totalSupply();
        if (poolShareSupply == 0) return (0, amounts);

        uint256 poolWeight = Math.max(rYes, rNo);
        mintAmount = Math.mulDiv(
            collateralAmt * lpPrecision,
            poolShareSupply,
            poolWeight * lpPrecision
        );

        amounts.yesToAdd = Math.mulDiv(collateralAmt, rYes, poolWeight);
        amounts.noToAdd = Math.mulDiv(collateralAmt, rNo, poolWeight);
        amounts.yesToReturn = collateralAmt - amounts.yesToAdd;
        amounts.noToReturn = collateralAmt - amounts.noToAdd;
    }

    /**
     * @notice Calculate expected returns when removing liquidity
     * @dev Returns the proportional share of reserves. If market is live,
     *      matched pairs (min of YES/NO) are converted to collateral.
     * @param sharesToBurn Amount of LP tokens to burn
     * @return yesToReturn YES tokens to receive (after collateral extraction if live)
     * @return noToReturn NO tokens to receive (after collateral extraction if live)
     * @return collateralToReturn Collateral to receive (only if market is live)
     */
    function calcRemoveLiquidity(
        uint256 sharesToBurn
    )
        external
        view
        returns (
            uint256 yesToReturn,
            uint256 noToReturn,
            uint256 collateralToReturn
        )
    {
        (uint112 rYes, uint112 rNo, uint32 closeTimestamp) = _getReservesData();
        // If market is live, matched pairs are burned for collateral
        bool isMarketLive = _blockTimestamp() < closeTimestamp;
        return _calcRemoveLiquidity(isMarketLive, sharesToBurn, rYes, rNo);
    }

    /**
     * @notice Get current market state snapshot
     * @return isLive True if market has not reached close timestamp
     * @return collateralTvl Collateral balance held by the AMM excluding protocol fees
     * @return yesChance Current YES chance (scaled by BPS_DENOMINATOR)
     * @return collateral Collateral token address
     */
    function marketState()
        external
        view
        returns (
            bool isLive,
            uint256 collateralTvl,
            uint24 yesChance,
            address collateral
        )
    {
        uint112 reserveYes;
        uint112 reserveNo;
        uint32 closeTs;
        (reserveYes, reserveNo, closeTs) = _getReservesData();

        isLive = _blockTimestamp() < closeTs;
        collateralTvl =
            IERC20(collateralToken).balanceOf(address(this)) -
            protocolFeesCollected;
        collateral = collateralToken;

        uint256 totalReserves = uint256(reserveYes) + uint256(reserveNo);
        if (totalReserves > 0) {
            yesChance = uint24(
                Math.max(
                    Math.mulDiv(reserveNo, BPS_DENOMINATOR, totalReserves),
                    1
                )
            );
        }
    }

    /**
     * @notice Get market close timestamp (unpacks from storage)
     * @return Market close timestamp
     */
    function marketCloseTimestamp() public view returns (uint32) {
        return uint32((_reserveData & TIMESTAMP_MASK) >> 224);
    }

    // ============================================
    // SWAP CALCULATIONS (DIRECT YES ↔ NO)
    // ============================================

    /**
     * @notice Calculate output amount for direct swap (quoter function) with hourly imbalance limit
     * @dev If requested amount exceeds imbalance limit, returns (0, 0, maxAllowedAmountIn)
     * @param yesToNo True for YES→NO, false for NO→YES
     * @param amountIn Amount of tokens to swap
     * @return amountOut Amount of tokens received (after fee), or 0 if over limit
     * @return feeAmount Fee amount in input tokens (for display)
     * @return maxAllowedAmountIn Maximum input amount allowed by hourly imbalance limit
     */
    function calcSwapExactIn(
        bool yesToNo,
        uint256 amountIn
    )
        public
        view
        returns (
            uint256 amountOut,
            uint256 feeAmount,
            uint256 maxAllowedAmountIn
        )
    {
        return _calcSwapExactInInternal(yesToNo, amountIn, true);
    }

    /**
     * @notice Calculate input amount required for exact output swap (quoter function) with hourly imbalance limit
     * @dev If required amount exceeds imbalance limit, returns (0, 0, maxAllowedAmountIn)
     * @param yesToNo True for YES→NO, false for NO→YES
     * @param amountOut Desired amount of output tokens
     * @return amountIn Amount of input tokens required (after fee), or 0 if over limit
     * @return feeAmount Fee amount in input tokens (for display)
     * @return maxAllowedAmountIn Maximum input amount allowed by hourly imbalance limit
     */
    function calcSwapExactOut(
        bool yesToNo,
        uint256 amountOut
    )
        public
        view
        returns (
            uint256 amountIn,
            uint256 feeAmount,
            uint256 maxAllowedAmountIn
        )
    {
        return _calcSwapExactOutInternal(yesToNo, amountOut, true);
    }

    // ============================================
    // QUOTER FUNCTIONS (SIMULATION)
    // ============================================

    /**
     * @notice Calculate output for buying YES with collateral
     * @dev Wrapper for _calcBuy with hourly imbalance limit
     * @param collateralIn Amount of collateral to spend
     * @return yesOut Estimated YES tokens to receive (0 if over imbalance limit)
     * @return maxAllowedCollateralIn Maximum collateral input allowed by hourly imbalance limit
     */
    function calcBuyYes(
        uint256 collateralIn
    ) public view returns (uint256 yesOut, uint256 maxAllowedCollateralIn) {
        (yesOut, maxAllowedCollateralIn) = _calcBuy(true, collateralIn);
    }

    /**
     * @notice Calculate output for buying NO with collateral
     * @dev Wrapper for _calcBuy with hourly imbalance limit
     * @param collateralIn Amount of collateral to spend
     * @return noOut Estimated NO tokens to receive (0 if over imbalance limit)
     * @return maxAllowedCollateralIn Maximum collateral input allowed by hourly imbalance limit
     */
    function calcBuyNo(
        uint256 collateralIn
    ) public view returns (uint256 noOut, uint256 maxAllowedCollateralIn) {
        (noOut, maxAllowedCollateralIn) = _calcBuy(false, collateralIn);
    }

    /**
     * @notice Calculate collateral output for selling YES tokens
     * @dev Wrapper for calcSell for backward compatibility
     * @param yesIn Amount of YES tokens to sell
     * @return collateralOut Estimated collateral to receive (after protocol fee, 0 if over imbalance limit)
     * @return protocolFee Protocol fee amount
     * @return maxAllowedYesIn Maximum YES amount allowed by hourly imbalance limit
     */
    function calcSellYes(
        uint112 yesIn
    )
        public
        view
        returns (
            uint256 collateralOut,
            uint256 protocolFee,
            uint112 maxAllowedYesIn
        )
    {
        (collateralOut, protocolFee, maxAllowedYesIn) = _calcSell(true, yesIn);
    }

    /**
     * @notice Calculate collateral output for selling NO tokens
     * @dev Wrapper for calcSell for backward compatibility
     * @param noIn Amount of NO tokens to sell
     * @return collateralOut Estimated collateral to receive (after protocol fee, 0 if over imbalance limit)
     * @return protocolFee Protocol fee amount
     * @return maxAllowedNoIn Maximum NO amount allowed by hourly imbalance limit
     */
    function calcSellNo(
        uint112 noIn
    )
        public
        view
        returns (
            uint256 collateralOut,
            uint256 protocolFee,
            uint112 maxAllowedNoIn
        )
    {
        (collateralOut, protocolFee, maxAllowedNoIn) = _calcSell(false, noIn);
    }

    // ============================================
    // INTERNAL FUNCTIONS - OPTIMAL SWAP CALCULATIONS
    // ============================================

    /**
     * @dev Internal calculation for remove liquidity (no storage reads)
     * @param isMarketLive True if market is live
     * @param sharesToBurn Amount of LP tokens to burn
     * @param rYes Current YES reserve
     * @param rNo Current NO reserve
     * @return yesToReturn YES tokens (after collateral extraction if live)
     * @return noToReturn NO tokens (after collateral extraction if live)
     * @return collateralToReturn Collateral (only if market is live)
     */
    function _calcRemoveLiquidity(
        bool isMarketLive,
        uint256 sharesToBurn,
        uint112 rYes,
        uint112 rNo
    )
        internal
        view
        returns (
            uint256 yesToReturn,
            uint256 noToReturn,
            uint256 collateralToReturn
        )
    {
        if (sharesToBurn == 0) return (0, 0, 0);

        uint256 _totalSupply = totalSupply();
        if (_totalSupply == 0) return (0, 0, 0);

        // Calculate proportional share of outcome tokens
        yesToReturn = Math.mulDiv(rYes, sharesToBurn, _totalSupply);
        noToReturn = Math.mulDiv(rNo, sharesToBurn, _totalSupply);

        if (isMarketLive) {
            collateralToReturn = Math.min(yesToReturn, noToReturn);
            yesToReturn -= collateralToReturn;
            noToReturn -= collateralToReturn;
        }
    }

    /**
     * @notice Calculate output for buying outcome tokens (unified function)
     * @dev Simulates mint + swap ALL opposite token to estimate output
     * @param isYes True for buying YES, false for buying NO
     * @param collateralIn Amount of collateral to spend
     * @return tokenOut Estimated outcome tokens to receive (0 if over imbalance limit)
     * @return maxAllowedCollateralIn Maximum collateral input allowed by hourly imbalance limit
     */
    function _calcBuy(
        bool isYes,
        uint256 collateralIn
    ) internal view returns (uint256 tokenOut, uint256 maxAllowedCollateralIn) {
        // Internal swap direction:
        // If buying YES: swap NO→YES (yesToNo = false)
        // If buying NO: swap YES→NO (yesToNo = true)
        bool yesToNo = !isYes;
        (uint256 reserveIn, uint256 reserveOut) = _getSwapReserves(yesToNo);

        // Compute imbalance-aware maximum collateral input using direct swap model
        maxAllowedCollateralIn = _maxAllowedDirectSwapIn(
            yesToNo,
            reserveIn,
            reserveOut
        );

        if (collateralIn > maxAllowedCollateralIn) {
            // Over limit: zero output, expose max allowed input
            return (0, maxAllowedCollateralIn);
        }

        // Mint creates collateralIn of both tokens
        // Swap ALL opposite token to target token
        uint256 swappedAmount = _getAmountOut(
            collateralIn,
            reserveIn,
            reserveOut
        );

        // Total = minted + swapped
        tokenOut = collateralIn + swappedAmount;
    }

    /**
     * @notice Calculate collateral output for selling outcome tokens (unified function)
     * @dev Simulates internal swap + burn + protocol fee
     * @param isYes True if selling YES tokens, false if selling NO tokens
     * @param amountToSell Amount of outcome tokens to sell
     * @return collateralOut Estimated collateral to receive (after protocol fee, 0 if over imbalance limit)
     * @return protocolFee Protocol fee amount
     * @return maxAllowedIn Maximum amount allowed by hourly imbalance limit
     */
    function _calcSell(
        bool isYes,
        uint112 amountToSell
    )
        internal
        view
        returns (
            uint256 collateralOut,
            uint256 protocolFee,
            uint112 maxAllowedIn
        )
    {
        (uint112 reserveYes, uint112 reserveNo) = _getReserves();

        // Determine swap direction and reserves in that direction
        bool yesToNo = isYes;
        uint256 reserveIn = yesToNo ? reserveYes : reserveNo;
        uint256 reserveOut = yesToNo ? reserveNo : reserveYes;

        // Precompute fee numerator once for both limit and swap math
        uint256 feeNumer = BPS_DENOMINATOR - tradingFee;

        // Derive maxAllowedIn for SELL from direct swap limit.
        // Direct limit applies to the internal swap leg X.
        // For SELL we have:
        //   S_max = X_max + swapOut(X_max)
        // so we use:
        //   maxAllowedIn = X_max + amountOut(X_max), with pessimistic rounding.
        uint256 directLimit = _maxAllowedDirectSwapIn(
            yesToNo,
            reserveIn,
            reserveOut
        );

        if (directLimit == type(uint256).max) {
            // No imbalance limit in this direction
            maxAllowedIn = type(uint112).max;
        } else {
            // Compute output at directLimit using the same CPMM math as real swaps
            uint256 outAtLimit = _getAmountOutWithFeeNumer(
                directLimit,
                reserveIn,
                reserveOut,
                feeNumer
            );

            uint256 sellLimit = directLimit + outAtLimit;

            if (sellLimit > type(uint112).max) {
                sellLimit = type(uint112).max;
            }
            maxAllowedIn = uint112(sellLimit);
        }

        if (amountToSell > maxAllowedIn) {
            // Over limit: zero output, expose max allowed input
            return (0, 0, maxAllowedIn);
        }

        // Calculate optimal swap for this sell amount
        uint256 amountToSwap = AMMath.calculateOptimalSellSwap(
            amountToSell,
            isYes, // selling YES or NO
            reserveYes,
            reserveNo,
            feeNumer
        );
        if (amountToSwap == 0) {
            return (0, 0, maxAllowedIn);
        }

        // Simulate swap (YES→NO or NO→YES)
        uint256 swappedAmount = _getAmountOutWithFeeNumer(
            amountToSwap,
            reserveIn,
            reserveOut,
            feeNumer
        );

        // Calculate pairs to burn
        uint256 tokenRemaining = amountToSell - amountToSwap;
        uint256 pairsToBurn = Math.min(tokenRemaining, swappedAmount);

        if (pairsToBurn == 0) {
            return (0, 0, maxAllowedIn);
        }

        // Protocol fee calculation
        protocolFee = Math.mulDiv(
            pairsToBurn,
            protocolFeeRate,
            BPS_DENOMINATOR,
            Math.Rounding.Ceil
        );

        collateralOut = pairsToBurn - protocolFee;
    }

    /**
     * @notice Calculate amount out for a swap with fee
     * @param amountIn Amount of input tokens
     * @param reserveIn Reserve of input token
     * @param reserveOut Reserve of output token
     * @return amountOut Amount of output tokens (after fee, rounded down)
     */
    function _getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut
    ) internal view returns (uint256 amountOut) {
        uint256 feeNumer = BPS_DENOMINATOR - tradingFee;
        amountOut = _getAmountOutWithFeeNumer(
            amountIn,
            reserveIn,
            reserveOut,
            feeNumer
        );
    }

    /**
     * @notice Calculate amount out for a swap with fee using precomputed feeNumer
     * @param amountIn Amount of input tokens
     * @param reserveIn Reserve of input token
     * @param reserveOut Reserve of output token
     * @param feeNumer Precomputed fee numerator (BPS_DENOMINATOR - tradingFee)
     * @return amountOut Amount of output tokens (after fee, rounded down)
     */
    function _getAmountOutWithFeeNumer(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut,
        uint256 feeNumer
    ) internal pure returns (uint256 amountOut) {
        if (amountIn == 0) return 0;
        if (reserveIn == 0 || reserveOut == 0) revert InsufficientLiquidity();

        uint256 amountInWithFee = amountIn * feeNumer;

        amountOut = Math.mulDiv(
            amountInWithFee,
            reserveOut,
            reserveIn * BPS_DENOMINATOR + amountInWithFee
        );
    }

    /**
     * @notice Calculate amount in required to get exact amount out
     * @dev Inverse of _getAmountOut, rounds up to ensure exact output is achieved
     * @param amountOut Desired amount of output tokens
     * @param reserveIn Reserve of input token
     * @param reserveOut Reserve of output token
     * @return amountIn Amount of input tokens required (after fee, rounded up)
     */
    function _getAmountIn(
        uint256 amountOut,
        uint256 reserveIn,
        uint256 reserveOut
    ) internal view returns (uint256 amountIn) {
        if (amountOut == 0) return 0;
        if (reserveIn == 0 || reserveOut == 0) revert InsufficientLiquidity();

        // Using Math.Rounding.Ceil to ensure we get enough input to produce desired output
        uint256 numerator = reserveIn * amountOut * BPS_DENOMINATOR;
        uint256 denominator = (reserveOut - amountOut) *
            (BPS_DENOMINATOR - tradingFee);

        amountIn = Math.mulDiv(numerator, 1, denominator, Math.Rounding.Ceil);
    }

    /**
     * @notice Get reserves based on swap direction
     * @param yesToNo True for YES→NO swap, false for NO→YES swap
     * @return reserveIn Reserve of input token
     * @return reserveOut Reserve of output token
     */
    function _getSwapReserves(
        bool yesToNo
    ) internal view returns (uint256 reserveIn, uint256 reserveOut) {
        (uint112 reserveYes, uint112 reserveNo) = _getReserves();
        reserveIn = yesToNo ? reserveYes : reserveNo;
        reserveOut = yesToNo ? reserveNo : reserveYes;
    }

    /**
     * @notice Get reserves and validate market is live
     * @dev Unpacks reserves and timestamp, then checks if market is still open
     * @return reserves Current reserves (YES and NO)
     */
    function _getReservesAndCheckLive()
        internal
        view
        returns (Reserves memory reserves)
    {
        uint32 timestamp;
        (reserves.rYes, reserves.rNo, timestamp) = _getReservesData();
        _checkMarketLive(timestamp);
    }

    /**
     * @notice Update reserves after swap operation
     * @dev Updates reserves in memory and writes to storage in one operation
     * @param yesToNo True for YES→NO swap, false for NO→YES swap
     * @param amountIn Amount of input tokens added to reserves
     * @param amountOut Amount of output tokens removed from reserves
     * @param reserves Current reserves (will be modified and stored)
     */
    function _updateReservesAfterSwap(
        bool yesToNo,
        uint256 amountIn,
        uint256 amountOut,
        Reserves memory reserves
    ) internal {
        // Apply swap effect to reserves in memory
        if (yesToNo) {
            reserves.rYes += SafeCast.toUint112(amountIn);
            reserves.rNo -= SafeCast.toUint112(amountOut);
        } else {
            reserves.rNo += SafeCast.toUint112(amountIn);
            reserves.rYes -= SafeCast.toUint112(amountOut);
        }

        _checkAndUpdatePrice(_priceYesFromReserves(reserves));

        _updateReserves(reserves.rYes, reserves.rNo);
    }

    /**
     * @notice Calculate and collect protocol fee
     * @dev Calculates fee, adds to collected fees, and returns fee amount
     * @param amount Amount to calculate fee from
     * @return fee Protocol fee amount (added to protocolFeesCollected)
     */
    function _calculateAndCollectProtocolFee(
        uint256 amount
    ) internal returns (uint256 fee) {
        fee = Math.mulDiv(
            amount,
            protocolFeeRate,
            BPS_DENOMINATOR,
            Math.Rounding.Ceil
        );
        protocolFeesCollected += SafeCast.toUint112(fee);
    }

    // ============================================
    // HOURLY PRICE IMBALANCE HELPERS
    // ============================================

    /**
     * @notice Get YES price from reserves in ONE scale (1e18)
     * @dev Returns value in range [1, ONE]
     */
    function _priceYesFromReserves(
        Reserves memory reserves
    ) internal pure returns (uint64) {
        uint256 total = uint256(reserves.rYes) + uint256(reserves.rNo);
        return
            uint64(Math.max(Math.mulDiv(uint256(reserves.rNo), ONE, total), 1));
    }

    /**
     * @notice Compute max allowed input for direct YES↔NO swap under hourly imbalance limit
     * @dev Returns type(uint256).max if limit is disabled.
     *      Takes into account existing min/max prices in the current hour window.
     *
     *      Constraint: (max(maxPrice, newPrice) - min(minPrice, newPrice)) <= delta
     *      - For YES→NO (price goes down): newPrice >= maxPrice - delta
     *      - For NO→YES (price goes up): newPrice <= minPrice + delta
     */
    function _maxAllowedDirectSwapIn(
        bool yesToNo,
        uint256 reserveIn,
        uint256 reserveOut
    ) internal view returns (uint256 maxAllowedAmountIn) {
        if (maxPriceImbalancePerHour == 0) {
            return type(uint256).max;
        }

        // Recover YES/NO reserves from swap-direction reserves
        Reserves memory reservesSnapshot;
        if (yesToNo) {
            reservesSnapshot.rYes = SafeCast.toUint112(reserveIn);
            reservesSnapshot.rNo = SafeCast.toUint112(reserveOut);
        } else {
            reservesSnapshot.rYes = SafeCast.toUint112(reserveOut);
            reservesSnapshot.rNo = SafeCast.toUint112(reserveIn);
        }

        // Get effective baseline (corridor edge) for limit calculation
        uint64 effectiveBaseline = _getEffectiveBaseline(yesToNo);

        // Use pre-computed delta from initialization
        uint256 delta = _maxPriceSwingScaled;

        uint256 feeNumer = BPS_DENOMINATOR - tradingFee;

        if (yesToNo) {
            maxAllowedAmountIn = AMMath.maxAllowed(
                effectiveBaseline,
                delta,
                uint256(reservesSnapshot.rYes),
                uint256(reservesSnapshot.rNo),
                feeNumer
            );
        } else {
            maxAllowedAmountIn = AMMath.maxAllowed(
                effectiveBaseline,
                delta,
                uint256(reservesSnapshot.rNo),
                uint256(reservesSnapshot.rYes),
                feeNumer
            );
        }

        uint256 capacity = AMMath.maxReserveCapacity(reserveIn);
        if (maxAllowedAmountIn > capacity) {
            maxAllowedAmountIn = capacity;
        }
    }

    /**
     * @notice Get input token address based on swap direction
     * @param yesToNo True for YES→NO swap, false for NO→YES swap
     * @return Input token address
     */
    function _getInputToken(bool yesToNo) internal view returns (address) {
        return yesToNo ? yesToken : noToken;
    }

    /**
     * @notice Get output token address based on swap direction
     * @param yesToNo True for YES→NO swap, false for NO→YES swap
     * @return Output token address
     */
    function _getOutputToken(bool yesToNo) internal view returns (address) {
        return yesToNo ? noToken : yesToken;
    }

    /**
     * @notice Get token address based on outcome type
     * @param isYes True for YES token, false for NO token
     * @return Token address
     */
    function _getToken(bool isYes) internal view returns (address) {
        return isYes ? yesToken : noToken;
    }

    // ============================================
    // INTERNAL FUNCTIONS - BUY/SELL LOGIC
    // ============================================

    /**
     * @notice Internal function to handle buying outcome tokens
     * @dev Mints equal YES+NO, swaps ALL opposite tokens to target, trading fee stays in reserves
     * @param isYes True for buying YES, false for buying NO
     * @param investmentAmount Amount of collateral to invest
     * @param minOut Minimum tokens to receive (slippage protection)
     * @param reserves Current reserves
     * @return outcomeTokensBought Amount of outcome tokens bought
     */
    function _buyTokens(
        bool isYes,
        uint256 investmentAmount,
        uint256 minOut,
        Reserves memory reserves
    ) internal returns (uint256 outcomeTokensBought) {
        // Transfer collateral from user
        IERC20(collateralToken).safeTransferFrom(
            msg.sender,
            address(this),
            investmentAmount
        );

        // Step 1: Mint equal amounts of both tokens (NO upfront fee!)
        _mintPairs(investmentAmount, address(this));

        bool yesToNo = !isYes;

        // Step 2: Swap ALL opposite tokens to target token with trading fee
        // If buying YES: swap all 100 NO → X YES
        // If buying NO: swap all 100 YES → X NO
        uint256 swappedAmount = _getAmountOut(
            investmentAmount, // Swap ALL opposite tokens
            yesToNo ? reserves.rYes : reserves.rNo, // Reserve of opposite token
            yesToNo ? reserves.rNo : reserves.rYes // Reserve of target token
        );

        // Step 3: Update reserves using helper (fee stays in reserves)
        // Buying YES = swap NO→YES internally (add investment to NO reserves, remove swapped from YES)
        // Buying NO = swap YES→NO internally (add investment to YES reserves, remove swapped from NO)
        _updateReservesAfterSwap(
            yesToNo, //swap opposite tokens
            investmentAmount,
            swappedAmount,
            reserves
        );

        // Step 4: Calculate total tokens for user
        // Contract now has: investmentAmount (minted target) + swappedAmount (from reserves)
        outcomeTokensBought = investmentAmount + swappedAmount;

        if (outcomeTokensBought < minOut) revert SlippageExceeded();

        // Step 5: Transfer ALL target tokens to user (contract has exactly this amount)
        OutcomeToken(_getToken(isYes)).transfer(
            msg.sender,
            outcomeTokensBought
        );

        // Note: NO protocol fee on buy (entry is free)
        // Trading fee from swap stays in reserves

        emit BuyTokens(
            msg.sender,
            isYes,
            outcomeTokensBought,
            investmentAmount,
            0 // No separate fee charged to user
        );
    }

    /**
     * @notice Internal function to handle selling outcome tokens
     * @dev Swaps part of sell token to opposite, burns pairs, applies protocol fee on exit
     * @param isYes True if selling YES, false if selling NO
     * @param outcomeTokensToSell Amount of outcome tokens to sell
     * @param minCollateralOut Minimum collateral to receive (slippage protection)
     * @param reserves Current reserves
     * @return returnAmount Amount of collateral returned to user (after protocol fee)
     */
    function _sellTokens(
        bool isYes,
        uint112 outcomeTokensToSell,
        uint256 minCollateralOut,
        Reserves memory reserves
    ) internal returns (uint256 returnAmount) {
        // Cache fee numerator once for both optimal swap calculation and actual swap
        uint256 feeNumer = BPS_DENOMINATOR - tradingFee;

        // Calculate and execute swap (nested scope to reduce stack)
        uint256 pairsToBurn;
        {
            uint256 amountToSwap = AMMath.calculateOptimalSellSwap(
                outcomeTokensToSell,
                isYes,
                reserves.rYes,
                reserves.rNo,
                feeNumer
            );

            if (amountToSwap == 0) {
                revert InsufficientLiquidity();
            }

            uint256 swappedAmount = _getAmountOutWithFeeNumer(
                amountToSwap,
                isYes ? reserves.rYes : reserves.rNo,
                isYes ? reserves.rNo : reserves.rYes,
                feeNumer
            );

            _updateReservesAfterSwap(
                isYes,
                amountToSwap,
                swappedAmount,
                reserves
            );

            // Calculate amount of pairs to burn
            pairsToBurn = Math.min(
                outcomeTokensToSell - amountToSwap,
                swappedAmount
            );

            if (pairsToBurn == 0) {
                revert NoPairToBurn();
            }
            outcomeTokensToSell = SafeCast.toUint112(
                pairsToBurn + amountToSwap
            );
        }

        // Transfer tokens from user
        OutcomeToken(_getToken(isYes)).transferFrom(
            msg.sender,
            address(this),
            outcomeTokensToSell
        );

        // Burn pairs to get collateral
        _burnPairs(pairsToBurn, address(this));

        // Calculate and collect protocol fee
        uint256 protocolFee = _calculateAndCollectProtocolFee(pairsToBurn);
        returnAmount = pairsToBurn - protocolFee;

        if (returnAmount < minCollateralOut) revert SlippageExceeded();

        // Transfer collateral to user
        IERC20(collateralToken).safeTransfer(msg.sender, returnAmount);

        emit SellTokens(
            msg.sender,
            isYes,
            outcomeTokensToSell,
            returnAmount,
            protocolFee
        );
    }

    /**
     * @notice Unified internal function to handle direct swaps
     * @dev Supports both exact input and exact output swap modes
     * @param yesToNo True for YES→NO, false for NO→YES
     * @param amountSpecified Amount specified (input if isExactIn, output if !isExactIn)
     * @param slippageLimit Slippage protection (minOut if isExactIn, maxIn if !isExactIn)
     * @param isExactIn True for exact input mode, false for exact output mode
     * @param reserves Current reserves
     * @return actualAmountIn Actual amount of input tokens spent
     * @return actualAmountOut Actual amount of output tokens received
     */
    function _swapTokensDirect(
        bool yesToNo,
        uint256 amountSpecified,
        uint256 slippageLimit,
        bool isExactIn,
        Reserves memory reserves
    ) internal returns (uint256 actualAmountIn, uint256 actualAmountOut) {
        uint256 feeAmount;

        // Calculate amounts based on mode (using core CPMM math).
        // Hourly imbalance limits are enforced later in _updateReservesAfterSwap
        // using exact pre/post-trade prices. Here we reuse the same math as
        // calcSwapExactIn/calcSwapExactOut but with imbalance checks disabled.
        if (isExactIn) {
            actualAmountIn = amountSpecified;
            (actualAmountOut, feeAmount, ) = _calcSwapExactInInternal(
                yesToNo,
                actualAmountIn,
                false
            );

            // Check slippage (minAmountOut)
            if (actualAmountOut < slippageLimit) revert SlippageExceeded();
        } else {
            actualAmountOut = amountSpecified;
            (actualAmountIn, feeAmount, ) = _calcSwapExactOutInternal(
                yesToNo,
                actualAmountOut,
                false
            );

            // Check slippage (maxAmountIn)
            if (actualAmountIn > slippageLimit) revert SlippageExceeded();
        }

        // Transfer input token from user
        OutcomeToken(_getInputToken(yesToNo)).transferFrom(
            msg.sender,
            address(this),
            actualAmountIn
        );

        // Update reserves after swap (using helper to reduce duplication)
        _updateReservesAfterSwap(
            yesToNo,
            actualAmountIn,
            actualAmountOut,
            reserves
        );

        // Transfer output token to user
        OutcomeToken(_getOutputToken(yesToNo)).transfer(
            msg.sender,
            actualAmountOut
        );

        // Note: Trading fee stays in reserves
        emit SwapTokens(
            msg.sender,
            yesToNo,
            actualAmountIn,
            actualAmountOut,
            feeAmount
        );
    }

    /**
     * @notice Internal helper for calcSwapExactIn with optional imbalance limit
     * @param checkLimit If true, applies hourly imbalance limit, otherwise skips it
     */
    function _calcSwapExactInInternal(
        bool yesToNo,
        uint256 amountIn,
        bool checkLimit
    )
        internal
        view
        returns (
            uint256 amountOut,
            uint256 feeAmount,
            uint256 maxAllowedAmountIn
        )
    {
        // Get current reserves in swap direction
        (uint256 reserveIn, uint256 reserveOut) = _getSwapReserves(yesToNo);

        if (checkLimit) {
            // Compute imbalance-aware maximum input using the same reserves snapshot
            maxAllowedAmountIn = _maxAllowedDirectSwapIn(
                yesToNo,
                reserveIn,
                reserveOut
            );

            if (amountIn > maxAllowedAmountIn) {
                // Over limit: signal by zero output/fee and return max allowed input
                return (0, 0, maxAllowedAmountIn);
            }
        }

        amountOut = _getAmountOut(amountIn, reserveIn, reserveOut);

        // Calculate fee for display (same as legacy behavior)
        feeAmount = Math.mulDiv(
            amountIn,
            tradingFee,
            BPS_DENOMINATOR,
            Math.Rounding.Ceil
        );
    }

    /**
     * @notice Internal helper for calcSwapExactOut with optional imbalance limit
     * @param checkLimit If true, applies hourly imbalance limit, otherwise skips it
     */
    function _calcSwapExactOutInternal(
        bool yesToNo,
        uint256 amountOut,
        bool checkLimit
    )
        internal
        view
        returns (
            uint256 amountIn,
            uint256 feeAmount,
            uint256 maxAllowedAmountIn
        )
    {
        // Get current reserves in swap direction
        (uint256 reserveIn, uint256 reserveOut) = _getSwapReserves(yesToNo);

        // Base CPMM quote using same reserves snapshot
        amountIn = _getAmountIn(amountOut, reserveIn, reserveOut);

        if (checkLimit) {
            // Compute imbalance-aware maximum input using the same reserves snapshot
            maxAllowedAmountIn = _maxAllowedDirectSwapIn(
                yesToNo,
                reserveIn,
                reserveOut
            );
            if (amountIn > maxAllowedAmountIn) {
                // Over limit: zero result/fee and expose max allowed
                return (0, 0, maxAllowedAmountIn);
            }
        }

        // Calculate fee for display (same as legacy behavior)
        // Fee = amountIn - amountInWithoutFee
        // Where: amountInWithoutFee = (reserveIn * amountOut) / (reserveOut - amountOut)
        uint256 amountInWithoutFee = Math.mulDiv(
            reserveIn,
            amountOut,
            reserveOut - amountOut
        );
        feeAmount = amountIn > amountInWithoutFee
            ? amountIn - amountInWithoutFee
            : 0;
    }

    /**
     * @notice Check if market is live (optimized to accept unpacked timestamp)
     * @param timestamp Market close timestamp
     */
    function _checkMarketLive(uint32 timestamp) internal view {
        require(_blockTimestamp() < timestamp, "Market is closed");
    }

    function _blockTimestamp() private view returns (uint256) {
        return block.timestamp;
    }

    // ============================================
    // STORAGE PACKING HELPERS (Internal)
    // ============================================

    /**
     * @notice Unpack reserves and timestamp from storage
     * @return rYes YES token reserve
     * @return rNo NO token reserve
     * @return timestamp Market close timestamp
     */
    function _getReservesData()
        internal
        view
        returns (uint112 rYes, uint112 rNo, uint32 timestamp)
    {
        uint256 data = _reserveData;
        rYes = uint112(data & RESERVE_YES_MASK);
        rNo = uint112((data & RESERVE_NO_MASK) >> 112);
        timestamp = uint32((data & TIMESTAMP_MASK) >> 224);
    }

    /**
     * @notice Unpack reserves and timestamp from storage
     * @return rYes YES token reserve
     * @return rNo NO token reserve
     */
    function _getReserves() internal view returns (uint112 rYes, uint112 rNo) {
        uint256 data = _reserveData;
        rYes = uint112(data & RESERVE_YES_MASK);
        rNo = uint112((data & RESERVE_NO_MASK) >> 112);
    }

    /**
     * @notice Pack reserves and timestamp into storage
     * @param rYes YES token reserve
     * @param rNo NO token reserve
     * @param timestamp Market close timestamp
     */
    function _updateReservesData(
        uint112 rYes,
        uint112 rNo,
        uint32 timestamp
    ) internal {
        _reserveData =
            uint256(rYes) |
            (uint256(rNo) << 112) |
            (uint256(timestamp) << 224);
    }

    /**
     * @notice Update only reserves, keep timestamp unchanged (optimized)
     * @dev More efficient for trading operations where timestamp doesn't change
     * @param rYes New YES token reserve
     * @param rNo New NO token reserve
     */
    function _updateReserves(uint112 rYes, uint112 rNo) internal {
        // Keep existing timestamp, update only reserves
        _reserveData =
            (_reserveData & TIMESTAMP_MASK) |
            uint256(rYes) |
            (uint256(rNo) << 112);
    }

    /**
     * @notice Burn pairs
     * @param amountToBurn Amount of pairs to burn
     * @param from Address to burn pairs from
     */
    function _burnPairs(uint256 amountToBurn, address from) internal {
        OutcomeToken(yesToken).burn(from, amountToBurn);
        OutcomeToken(noToken).burn(from, amountToBurn);
    }

    /**
     * @notice Mint pairs
     * @param amountToMint Amount of pairs to mint
     * @param to Address to mint pairs to
     */
    function _mintPairs(uint256 amountToMint, address to) internal {
        OutcomeToken(yesToken).mint(to, amountToMint);
        OutcomeToken(noToken).mint(to, amountToMint);
    }

    /**
     * @notice Transfer pairs
     * @param amountYes Amount of YES pairs to transfer
     * @param amountNo Amount of NO pairs to transfer
     * @param to Address to transfer pairs to
     */
    function _transferPairs(
        uint256 amountYes,
        uint256 amountNo,
        address to
    ) internal {
        if (amountYes > 0) {
            OutcomeToken(yesToken).transfer(to, amountYes);
        }
        if (amountNo > 0) {
            OutcomeToken(noToken).transfer(to, amountNo);
        }
    }

    // ============================================
    // PRICE WINDOW FUNCTIONS
    // ============================================

    /**
     * @notice Check price swing limit and update hourly price window
     * @dev Tracks min/max price per hour. Reverts if (max-min) exceeds limit.
     *      Trading inside corridor is free; only expansion triggers check.
     * @param newPrice Current YES price in ONE scale (1 to 1e18)
     */
    function _checkAndUpdatePrice(uint64 newPrice) internal {
        uint256 maxAllowedSwing = _maxPriceSwingScaled;
        if (maxAllowedSwing == 0) return; // Limit disabled

        // Load and unpack
        uint256 data = _priceWindow;
        uint32 storedHour = uint32(data & HOUR_MASK);
        uint64 anchorPrice = uint64((data >> ANCHOR_OFFSET) & PRICE64_MASK);
        uint64 minPrice = uint64((data >> MIN_OFFSET) & PRICE64_MASK);
        uint64 maxPrice = uint64((data >> MAX_OFFSET) & PRICE64_MASK);

        uint32 currentHour = uint32(_blockTimestamp() / WINDOW_DURATION);

        // Check if price expands corridor (compute once, use twice)
        bool expandsUp = newPrice > maxPrice;
        bool expandsDown = newPrice < minPrice;

        // Unified volatility check (same-hour AND cross-hour)
        if (maxPrice != 0 && (expandsUp || expandsDown)) {
            uint64 swing = expandsUp
                ? (newPrice - minPrice)
                : (maxPrice - newPrice);

            if (swing > maxAllowedSwing) {
                revert PriceSwingExceeded(swing, uint64(maxAllowedSwing));
            }
        }

        // Update window
        if (storedHour != currentHour) {
            // New hour: reset window
            storedHour = currentHour;
            anchorPrice = newPrice;
            minPrice = newPrice;
            maxPrice = newPrice;
        } else if (expandsUp) {
            // Same hour: expand up
            maxPrice = newPrice;
        } else if (expandsDown) {
            // Same hour: expand down
            minPrice = newPrice;
        }

        // Pack and store
        _priceWindow =
            uint256(storedHour) |
            (uint256(anchorPrice) << ANCHOR_OFFSET) |
            (uint256(minPrice) << MIN_OFFSET) |
            (uint256(maxPrice) << MAX_OFFSET);
    }

    /**
     * @notice Get effective baseline price for limit calculations
     * @dev Returns the corridor edge as baseline for AMMath.maxAllowed calculation.
     *      - For DOWN (YES→NO): returns windowMax (price moves down from max)
     *      - For UP (NO→YES): returns ONE - windowMin (in NO-price terms)
     * @param yesToNo True for YES→NO swap, false for NO→YES swap
     * @return effectiveBaseline The baseline price for AMMath.maxAllowed calculation
     */
    function _getEffectiveBaseline(
        bool yesToNo
    ) internal view returns (uint64 effectiveBaseline) {
        uint256 data = _priceWindow;

        // Same hour - use corridor edge as baseline
        if (yesToNo) {
            // YES→NO: price goes DOWN, baseline is windowMax
            effectiveBaseline = uint64((data >> MAX_OFFSET) & PRICE64_MASK);
        } else {
            // NO→YES: price goes UP, baseline is ONE - windowMin
            uint64 windowMin = uint64((data >> MIN_OFFSET) & PRICE64_MASK);
            effectiveBaseline = uint64(ONE - uint256(windowMin));
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/draft-IERC6093.sol)
pragma solidity >=0.8.4;

/**
 * @dev Standard ERC-20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.
 */
interface IERC20Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC20InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC20InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC20InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC20InvalidSpender(address spender);
}

/**
 * @dev Standard ERC-721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
    error ERC721InvalidOwner(address owner);

    /**
     * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
    error ERC721NonexistentToken(uint256 tokenId);

    /**
     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC721InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC721InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
    error ERC721InsufficientApproval(address operator, uint256 tokenId);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC721InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC721InvalidOperator(address operator);
}

/**
 * @dev Standard ERC-1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.
 */
interface IERC1155Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     * @param tokenId Identifier number of a token.
     */
    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC1155InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC1155InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param owner Address of the current owner of a token.
     */
    error ERC1155MissingApprovalForAll(address operator, address owner);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC1155InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC1155InvalidOperator(address operator);

    /**
     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
     * Used in batch transfers.
     * @param idsLength Length of the array of token identifiers
     * @param valuesLength Length of the array of token amounts
     */
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)

pragma solidity >=0.6.2;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}

File 4 of 21 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)

pragma solidity >=0.4.16;

import {IERC165} from "../utils/introspection/IERC165.sol";

File 5 of 21 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)

pragma solidity >=0.4.16;

import {IERC20} from "../token/ERC20/IERC20.sol";

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC-20
 * applications.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * Both values are immutable: they can only be set once during construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /// @inheritdoc IERC20
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /// @inheritdoc IERC20
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /// @inheritdoc IERC20
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Skips emitting an {Approval} event indicating an allowance update. This is not
     * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     *
     * ```solidity
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner`'s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance < type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity >=0.6.2;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
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 v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}

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

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 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 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @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.encodeCall(token.transfer, (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.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, 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.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @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.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

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

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @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 {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @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 silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}

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

pragma solidity ^0.8.20;

/**
 * @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 Context {
    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;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 12 of 21 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Return the 512-bit addition of two uint256.
     *
     * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
     */
    function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
        assembly ("memory-safe") {
            low := add(a, b)
            high := lt(low, a)
        }
    }

    /**
     * @dev Return the 512-bit multiplication of two uint256.
     *
     * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
     */
    function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
        // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
        // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
        // variables such that product = high * 2²⁵⁶ + low.
        assembly ("memory-safe") {
            let mm := mulmod(a, b, not(0))
            low := mul(a, b)
            high := sub(sub(mm, low), lt(mm, low))
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a + b;
            success = c >= a;
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a - b;
            success = c <= a;
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a * b;
            assembly ("memory-safe") {
                // Only true when the multiplication doesn't overflow
                // (c / a == b) || (a == 0)
                success := or(eq(div(c, a), b), iszero(a))
            }
            // equivalent to: success ? c : 0
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            success = b > 0;
            assembly ("memory-safe") {
                // The `DIV` opcode returns zero when the denominator is 0.
                result := div(a, b)
            }
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            success = b > 0;
            assembly ("memory-safe") {
                // The `MOD` opcode returns zero when the denominator is 0.
                result := mod(a, b)
            }
        }
    }

    /**
     * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
     */
    function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
        (bool success, uint256 result) = tryAdd(a, b);
        return ternary(success, result, type(uint256).max);
    }

    /**
     * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
     */
    function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
        (, uint256 result) = trySub(a, b);
        return result;
    }

    /**
     * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
     */
    function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
        (bool success, uint256 result) = tryMul(a, b);
        return ternary(success, result, type(uint256).max);
    }

    /**
     * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
     *
     * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
     * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
     * one branch when needed, making this function more expensive.
     */
    function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
        unchecked {
            // branchless ternary works because:
            // b ^ (a ^ b) == a
            // b ^ 0 == b
            return b ^ ((a ^ b) * SafeCast.toUint(condition));
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return ternary(a > b, a, b);
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return ternary(a < b, a, b);
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }

        // The following calculation ensures accurate ceiling division without overflow.
        // Since a is non-zero, (a - 1) / b will not overflow.
        // The largest possible result occurs when (a - 1) / b is type(uint256).max,
        // but the largest value we can obtain is type(uint256).max - 1, which happens
        // when a = type(uint256).max and b = 1.
        unchecked {
            return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
        }
    }

    /**
     * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     *
     * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            (uint256 high, uint256 low) = mul512(x, y);

            // Handle non-overflow cases, 256 by 256 division.
            if (high == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return low / denominator;
            }

            // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
            if (denominator <= high) {
                Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [high low].
            uint256 remainder;
            assembly ("memory-safe") {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                high := sub(high, gt(remainder, low))
                low := sub(low, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly ("memory-safe") {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [high low] by twos.
                low := div(low, twos)

                // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from high into low.
            low |= high * twos;

            // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
            // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv ≡ 1 mod 2⁴.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
            // works in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2⁸
            inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
            inverse *= 2 - denominator * inverse; // inverse mod 2³²
            inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
            inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
            inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
            // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
            // is no longer required.
            result = low * inverse;
            return result;
        }
    }

    /**
     * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
    }

    /**
     * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
     */
    function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
        unchecked {
            (uint256 high, uint256 low) = mul512(x, y);
            if (high >= 1 << n) {
                Panic.panic(Panic.UNDER_OVERFLOW);
            }
            return (high << (256 - n)) | (low >> n);
        }
    }

    /**
     * @dev Calculates x * y >> n with full precision, following the selected rounding direction.
     */
    function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
        return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
    }

    /**
     * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
     *
     * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
     * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
     *
     * If the input value is not inversible, 0 is returned.
     *
     * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
     * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
     */
    function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
        unchecked {
            if (n == 0) return 0;

            // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
            // Used to compute integers x and y such that: ax + ny = gcd(a, n).
            // When the gcd is 1, then the inverse of a modulo n exists and it's x.
            // ax + ny = 1
            // ax = 1 + (-y)n
            // ax ≡ 1 (mod n) # x is the inverse of a modulo n

            // If the remainder is 0 the gcd is n right away.
            uint256 remainder = a % n;
            uint256 gcd = n;

            // Therefore the initial coefficients are:
            // ax + ny = gcd(a, n) = n
            // 0a + 1n = n
            int256 x = 0;
            int256 y = 1;

            while (remainder != 0) {
                uint256 quotient = gcd / remainder;

                (gcd, remainder) = (
                    // The old remainder is the next gcd to try.
                    remainder,
                    // Compute the next remainder.
                    // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
                    // where gcd is at most n (capped to type(uint256).max)
                    gcd - remainder * quotient
                );

                (x, y) = (
                    // Increment the coefficient of a.
                    y,
                    // Decrement the coefficient of n.
                    // Can overflow, but the result is casted to uint256 so that the
                    // next value of y is "wrapped around" to a value between 0 and n - 1.
                    x - y * int256(quotient)
                );
            }

            if (gcd != 1) return 0; // No inverse exists.
            return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
        }
    }

    /**
     * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
     *
     * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
     * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
     * `a**(p-2)` is the modular multiplicative inverse of a in Fp.
     *
     * NOTE: this function does NOT check that `p` is a prime greater than `2`.
     */
    function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
        unchecked {
            return Math.modExp(a, p - 2, p);
        }
    }

    /**
     * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
     *
     * Requirements:
     * - modulus can't be zero
     * - underlying staticcall to precompile must succeed
     *
     * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
     * sure the chain you're using it on supports the precompiled contract for modular exponentiation
     * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
     * the underlying function will succeed given the lack of a revert, but the result may be incorrectly
     * interpreted as 0.
     */
    function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
        (bool success, uint256 result) = tryModExp(b, e, m);
        if (!success) {
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }
        return result;
    }

    /**
     * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
     * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
     * to operate modulo 0 or if the underlying precompile reverted.
     *
     * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
     * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
     * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
     * of a revert, but the result may be incorrectly interpreted as 0.
     */
    function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
        if (m == 0) return (false, 0);
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            // | Offset    | Content    | Content (Hex)                                                      |
            // |-----------|------------|--------------------------------------------------------------------|
            // | 0x00:0x1f | size of b  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
            // | 0x20:0x3f | size of e  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
            // | 0x40:0x5f | size of m  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
            // | 0x60:0x7f | value of b | 0x<.............................................................b> |
            // | 0x80:0x9f | value of e | 0x<.............................................................e> |
            // | 0xa0:0xbf | value of m | 0x<.............................................................m> |
            mstore(ptr, 0x20)
            mstore(add(ptr, 0x20), 0x20)
            mstore(add(ptr, 0x40), 0x20)
            mstore(add(ptr, 0x60), b)
            mstore(add(ptr, 0x80), e)
            mstore(add(ptr, 0xa0), m)

            // Given the result < m, it's guaranteed to fit in 32 bytes,
            // so we can use the memory scratch space located at offset 0.
            success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
            result := mload(0x00)
        }
    }

    /**
     * @dev Variant of {modExp} that supports inputs of arbitrary length.
     */
    function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
        (bool success, bytes memory result) = tryModExp(b, e, m);
        if (!success) {
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }
        return result;
    }

    /**
     * @dev Variant of {tryModExp} that supports inputs of arbitrary length.
     */
    function tryModExp(
        bytes memory b,
        bytes memory e,
        bytes memory m
    ) internal view returns (bool success, bytes memory result) {
        if (_zeroBytes(m)) return (false, new bytes(0));

        uint256 mLen = m.length;

        // Encode call args in result and move the free memory pointer
        result = abi.encodePacked(b.length, e.length, mLen, b, e, m);

        assembly ("memory-safe") {
            let dataPtr := add(result, 0x20)
            // Write result on top of args to avoid allocating extra memory.
            success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
            // Overwrite the length.
            // result.length > returndatasize() is guaranteed because returndatasize() == m.length
            mstore(result, mLen)
            // Set the memory pointer after the returned data.
            mstore(0x40, add(dataPtr, mLen))
        }
    }

    /**
     * @dev Returns whether the provided byte array is zero.
     */
    function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
        for (uint256 i = 0; i < byteArray.length; ++i) {
            if (byteArray[i] != 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * This method is based on Newton's method for computing square roots; the algorithm is restricted to only
     * using integer operations.
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        unchecked {
            // Take care of easy edge cases when a == 0 or a == 1
            if (a <= 1) {
                return a;
            }

            // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
            // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
            // the current value as `ε_n = | x_n - sqrt(a) |`.
            //
            // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
            // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
            // bigger than any uint256.
            //
            // By noticing that
            // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
            // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
            // to the msb function.
            uint256 aa = a;
            uint256 xn = 1;

            if (aa >= (1 << 128)) {
                aa >>= 128;
                xn <<= 64;
            }
            if (aa >= (1 << 64)) {
                aa >>= 64;
                xn <<= 32;
            }
            if (aa >= (1 << 32)) {
                aa >>= 32;
                xn <<= 16;
            }
            if (aa >= (1 << 16)) {
                aa >>= 16;
                xn <<= 8;
            }
            if (aa >= (1 << 8)) {
                aa >>= 8;
                xn <<= 4;
            }
            if (aa >= (1 << 4)) {
                aa >>= 4;
                xn <<= 2;
            }
            if (aa >= (1 << 2)) {
                xn <<= 1;
            }

            // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
            //
            // We can refine our estimation by noticing that the middle of that interval minimizes the error.
            // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
            // This is going to be our x_0 (and ε_0)
            xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)

            // From here, Newton's method give us:
            // x_{n+1} = (x_n + a / x_n) / 2
            //
            // One should note that:
            // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
            //              = ((x_n² + a) / (2 * x_n))² - a
            //              = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
            //              = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
            //              = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
            //              = (x_n² - a)² / (2 * x_n)²
            //              = ((x_n² - a) / (2 * x_n))²
            //              ≥ 0
            // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
            //
            // This gives us the proof of quadratic convergence of the sequence:
            // ε_{n+1} = | x_{n+1} - sqrt(a) |
            //         = | (x_n + a / x_n) / 2 - sqrt(a) |
            //         = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
            //         = | (x_n - sqrt(a))² / (2 * x_n) |
            //         = | ε_n² / (2 * x_n) |
            //         = ε_n² / | (2 * x_n) |
            //
            // For the first iteration, we have a special case where x_0 is known:
            // ε_1 = ε_0² / | (2 * x_0) |
            //     ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
            //     ≤ 2**(2*e-4) / (3 * 2**(e-1))
            //     ≤ 2**(e-3) / 3
            //     ≤ 2**(e-3-log2(3))
            //     ≤ 2**(e-4.5)
            //
            // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
            // ε_{n+1} = ε_n² / | (2 * x_n) |
            //         ≤ (2**(e-k))² / (2 * 2**(e-1))
            //         ≤ 2**(2*e-2*k) / 2**e
            //         ≤ 2**(e-2*k)
            xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5)  -- special case, see above
            xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9)    -- general case with k = 4.5
            xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18)   -- general case with k = 9
            xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36)   -- general case with k = 18
            xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72)   -- general case with k = 36
            xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144)  -- general case with k = 72

            // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
            // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
            // sqrt(a) or sqrt(a) + 1.
            return xn - SafeCast.toUint(xn > a / xn);
        }
    }

    /**
     * @dev Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 x) internal pure returns (uint256 r) {
        // If value has upper 128 bits set, log2 result is at least 128
        r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
        // If upper 64 bits of 128-bit half set, add 64 to result
        r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
        // If upper 32 bits of 64-bit half set, add 32 to result
        r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
        // If upper 16 bits of 32-bit half set, add 16 to result
        r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
        // If upper 8 bits of 16-bit half set, add 8 to result
        r |= SafeCast.toUint((x >> r) > 0xff) << 3;
        // If upper 4 bits of 8-bit half set, add 4 to result
        r |= SafeCast.toUint((x >> r) > 0xf) << 2;

        // Shifts value right by the current result and use it as an index into this lookup table:
        //
        // | x (4 bits) |  index  | table[index] = MSB position |
        // |------------|---------|-----------------------------|
        // |    0000    |    0    |        table[0] = 0         |
        // |    0001    |    1    |        table[1] = 0         |
        // |    0010    |    2    |        table[2] = 1         |
        // |    0011    |    3    |        table[3] = 1         |
        // |    0100    |    4    |        table[4] = 2         |
        // |    0101    |    5    |        table[5] = 2         |
        // |    0110    |    6    |        table[6] = 2         |
        // |    0111    |    7    |        table[7] = 2         |
        // |    1000    |    8    |        table[8] = 3         |
        // |    1001    |    9    |        table[9] = 3         |
        // |    1010    |   10    |        table[10] = 3        |
        // |    1011    |   11    |        table[11] = 3        |
        // |    1100    |   12    |        table[12] = 3        |
        // |    1101    |   13    |        table[13] = 3        |
        // |    1110    |   14    |        table[14] = 3        |
        // |    1111    |   15    |        table[15] = 3        |
        //
        // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.
        assembly ("memory-safe") {
            r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))
        }
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 x) internal pure returns (uint256 r) {
        // If value has upper 128 bits set, log2 result is at least 128
        r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
        // If upper 64 bits of 128-bit half set, add 64 to result
        r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
        // If upper 32 bits of 64-bit half set, add 32 to result
        r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
        // If upper 16 bits of 32-bit half set, add 16 to result
        r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
        // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
        return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

File 13 of 21 : SafeCast.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.20;

/**
 * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeCast {
    /**
     * @dev Value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);

    /**
     * @dev An int value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedIntToUint(int256 value);

    /**
     * @dev Value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);

    /**
     * @dev An uint value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedUintToInt(uint256 value);

    /**
     * @dev Returns the downcasted uint248 from uint256, reverting on
     * overflow (when the input is greater than largest uint248).
     *
     * Counterpart to Solidity's `uint248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        if (value > type(uint248).max) {
            revert SafeCastOverflowedUintDowncast(248, value);
        }
        return uint248(value);
    }

    /**
     * @dev Returns the downcasted uint240 from uint256, reverting on
     * overflow (when the input is greater than largest uint240).
     *
     * Counterpart to Solidity's `uint240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        if (value > type(uint240).max) {
            revert SafeCastOverflowedUintDowncast(240, value);
        }
        return uint240(value);
    }

    /**
     * @dev Returns the downcasted uint232 from uint256, reverting on
     * overflow (when the input is greater than largest uint232).
     *
     * Counterpart to Solidity's `uint232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        if (value > type(uint232).max) {
            revert SafeCastOverflowedUintDowncast(232, value);
        }
        return uint232(value);
    }

    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        if (value > type(uint224).max) {
            revert SafeCastOverflowedUintDowncast(224, value);
        }
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint216 from uint256, reverting on
     * overflow (when the input is greater than largest uint216).
     *
     * Counterpart to Solidity's `uint216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        if (value > type(uint216).max) {
            revert SafeCastOverflowedUintDowncast(216, value);
        }
        return uint216(value);
    }

    /**
     * @dev Returns the downcasted uint208 from uint256, reverting on
     * overflow (when the input is greater than largest uint208).
     *
     * Counterpart to Solidity's `uint208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        if (value > type(uint208).max) {
            revert SafeCastOverflowedUintDowncast(208, value);
        }
        return uint208(value);
    }

    /**
     * @dev Returns the downcasted uint200 from uint256, reverting on
     * overflow (when the input is greater than largest uint200).
     *
     * Counterpart to Solidity's `uint200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        if (value > type(uint200).max) {
            revert SafeCastOverflowedUintDowncast(200, value);
        }
        return uint200(value);
    }

    /**
     * @dev Returns the downcasted uint192 from uint256, reverting on
     * overflow (when the input is greater than largest uint192).
     *
     * Counterpart to Solidity's `uint192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        if (value > type(uint192).max) {
            revert SafeCastOverflowedUintDowncast(192, value);
        }
        return uint192(value);
    }

    /**
     * @dev Returns the downcasted uint184 from uint256, reverting on
     * overflow (when the input is greater than largest uint184).
     *
     * Counterpart to Solidity's `uint184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        if (value > type(uint184).max) {
            revert SafeCastOverflowedUintDowncast(184, value);
        }
        return uint184(value);
    }

    /**
     * @dev Returns the downcasted uint176 from uint256, reverting on
     * overflow (when the input is greater than largest uint176).
     *
     * Counterpart to Solidity's `uint176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        if (value > type(uint176).max) {
            revert SafeCastOverflowedUintDowncast(176, value);
        }
        return uint176(value);
    }

    /**
     * @dev Returns the downcasted uint168 from uint256, reverting on
     * overflow (when the input is greater than largest uint168).
     *
     * Counterpart to Solidity's `uint168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        if (value > type(uint168).max) {
            revert SafeCastOverflowedUintDowncast(168, value);
        }
        return uint168(value);
    }

    /**
     * @dev Returns the downcasted uint160 from uint256, reverting on
     * overflow (when the input is greater than largest uint160).
     *
     * Counterpart to Solidity's `uint160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        if (value > type(uint160).max) {
            revert SafeCastOverflowedUintDowncast(160, value);
        }
        return uint160(value);
    }

    /**
     * @dev Returns the downcasted uint152 from uint256, reverting on
     * overflow (when the input is greater than largest uint152).
     *
     * Counterpart to Solidity's `uint152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        if (value > type(uint152).max) {
            revert SafeCastOverflowedUintDowncast(152, value);
        }
        return uint152(value);
    }

    /**
     * @dev Returns the downcasted uint144 from uint256, reverting on
     * overflow (when the input is greater than largest uint144).
     *
     * Counterpart to Solidity's `uint144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        if (value > type(uint144).max) {
            revert SafeCastOverflowedUintDowncast(144, value);
        }
        return uint144(value);
    }

    /**
     * @dev Returns the downcasted uint136 from uint256, reverting on
     * overflow (when the input is greater than largest uint136).
     *
     * Counterpart to Solidity's `uint136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        if (value > type(uint136).max) {
            revert SafeCastOverflowedUintDowncast(136, value);
        }
        return uint136(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        if (value > type(uint128).max) {
            revert SafeCastOverflowedUintDowncast(128, value);
        }
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint120 from uint256, reverting on
     * overflow (when the input is greater than largest uint120).
     *
     * Counterpart to Solidity's `uint120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        if (value > type(uint120).max) {
            revert SafeCastOverflowedUintDowncast(120, value);
        }
        return uint120(value);
    }

    /**
     * @dev Returns the downcasted uint112 from uint256, reverting on
     * overflow (when the input is greater than largest uint112).
     *
     * Counterpart to Solidity's `uint112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        if (value > type(uint112).max) {
            revert SafeCastOverflowedUintDowncast(112, value);
        }
        return uint112(value);
    }

    /**
     * @dev Returns the downcasted uint104 from uint256, reverting on
     * overflow (when the input is greater than largest uint104).
     *
     * Counterpart to Solidity's `uint104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        if (value > type(uint104).max) {
            revert SafeCastOverflowedUintDowncast(104, value);
        }
        return uint104(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        if (value > type(uint96).max) {
            revert SafeCastOverflowedUintDowncast(96, value);
        }
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        if (value > type(uint88).max) {
            revert SafeCastOverflowedUintDowncast(88, value);
        }
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint80 from uint256, reverting on
     * overflow (when the input is greater than largest uint80).
     *
     * Counterpart to Solidity's `uint80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        if (value > type(uint80).max) {
            revert SafeCastOverflowedUintDowncast(80, value);
        }
        return uint80(value);
    }

    /**
     * @dev Returns the downcasted uint72 from uint256, reverting on
     * overflow (when the input is greater than largest uint72).
     *
     * Counterpart to Solidity's `uint72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        if (value > type(uint72).max) {
            revert SafeCastOverflowedUintDowncast(72, value);
        }
        return uint72(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        if (value > type(uint64).max) {
            revert SafeCastOverflowedUintDowncast(64, value);
        }
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint56 from uint256, reverting on
     * overflow (when the input is greater than largest uint56).
     *
     * Counterpart to Solidity's `uint56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        if (value > type(uint56).max) {
            revert SafeCastOverflowedUintDowncast(56, value);
        }
        return uint56(value);
    }

    /**
     * @dev Returns the downcasted uint48 from uint256, reverting on
     * overflow (when the input is greater than largest uint48).
     *
     * Counterpart to Solidity's `uint48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        if (value > type(uint48).max) {
            revert SafeCastOverflowedUintDowncast(48, value);
        }
        return uint48(value);
    }

    /**
     * @dev Returns the downcasted uint40 from uint256, reverting on
     * overflow (when the input is greater than largest uint40).
     *
     * Counterpart to Solidity's `uint40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        if (value > type(uint40).max) {
            revert SafeCastOverflowedUintDowncast(40, value);
        }
        return uint40(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        if (value > type(uint32).max) {
            revert SafeCastOverflowedUintDowncast(32, value);
        }
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint24 from uint256, reverting on
     * overflow (when the input is greater than largest uint24).
     *
     * Counterpart to Solidity's `uint24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        if (value > type(uint24).max) {
            revert SafeCastOverflowedUintDowncast(24, value);
        }
        return uint24(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        if (value > type(uint16).max) {
            revert SafeCastOverflowedUintDowncast(16, value);
        }
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        if (value > type(uint8).max) {
            revert SafeCastOverflowedUintDowncast(8, value);
        }
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        if (value < 0) {
            revert SafeCastOverflowedIntToUint(value);
        }
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(248, value);
        }
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(240, value);
        }
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(232, value);
        }
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(224, value);
        }
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(216, value);
        }
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(208, value);
        }
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(200, value);
        }
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(192, value);
        }
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(184, value);
        }
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(176, value);
        }
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(168, value);
        }
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(160, value);
        }
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(152, value);
        }
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(144, value);
        }
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(136, value);
        }
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(128, value);
        }
    }

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(120, value);
        }
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(112, value);
        }
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(104, value);
        }
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(96, value);
        }
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(88, value);
        }
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(80, value);
        }
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(72, value);
        }
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(64, value);
        }
    }

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(56, value);
        }
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(48, value);
        }
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(40, value);
        }
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(32, value);
        }
    }

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(24, value);
        }
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(16, value);
        }
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(8, value);
        }
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        if (value > uint256(type(int256).max)) {
            revert SafeCastOverflowedUintToInt(value);
        }
        return int256(value);
    }

    /**
     * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
     */
    function toUint(bool b) internal pure returns (uint256 u) {
        assembly ("memory-safe") {
            u := iszero(iszero(b))
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)

pragma solidity ^0.8.20;

/**
 * @dev Helper library for emitting standardized panic codes.
 *
 * ```solidity
 * contract Example {
 *      using Panic for uint256;
 *
 *      // Use any of the declared internal constants
 *      function foo() { Panic.GENERIC.panic(); }
 *
 *      // Alternatively
 *      function foo() { Panic.panic(Panic.GENERIC); }
 * }
 * ```
 *
 * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
 *
 * _Available since v5.1._
 */
// slither-disable-next-line unused-state
library Panic {
    /// @dev generic / unspecified error
    uint256 internal constant GENERIC = 0x00;
    /// @dev used by the assert() builtin
    uint256 internal constant ASSERT = 0x01;
    /// @dev arithmetic underflow or overflow
    uint256 internal constant UNDER_OVERFLOW = 0x11;
    /// @dev division or modulo by zero
    uint256 internal constant DIVISION_BY_ZERO = 0x12;
    /// @dev enum conversion error
    uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
    /// @dev invalid encoding in storage
    uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
    /// @dev empty array pop
    uint256 internal constant EMPTY_ARRAY_POP = 0x31;
    /// @dev array out of bounds access
    uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
    /// @dev resource error (too large allocation or too large array)
    uint256 internal constant RESOURCE_ERROR = 0x41;
    /// @dev calling invalid internal function
    uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;

    /// @dev Reverts with a panic code. Recommended to use with
    /// the internal constants with predefined codes.
    function panic(uint256 code) internal pure {
        assembly ("memory-safe") {
            mstore(0x00, 0x4e487b71)
            mstore(0x20, code)
            revert(0x1c, 0x24)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @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 EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * 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 ReentrancyGuard {
    // 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;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _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
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

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

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

/**
 * @title IMarketFactory
 * @notice Interface for creating and managing prediction markets
 */
interface IMarketFactory {
    /// @notice Lightweight snapshot of a market state
    struct MarketState {
        bool isLive;
        uint256 collateralTvl;
        uint24 yesChance;
        address marketAddress;
        address collateralToken;
    }

    // ============================================
    // EVENTS
    // ============================================

    /// @notice Emitted when protocol fee rate is updated
    event ProtocolFeeRateUpdated(uint256 oldRate, uint256 newRate);

    /// @notice Emitted when new market is created
    event MarketCreated(
        address indexed pollAddress,
        address indexed marketAddress,
        address indexed creator,
        address yesToken,
        address noToken,
        address collateral,
        uint24 feeTier,
        uint24 maxPriceImbalancePerHour
    );

    /// @notice Emitted when new pari-mutuel market is created
    event PariMutuelCreated(
        address indexed pollAddress,
        address indexed marketAddress,
        address indexed creator,
        address collateral,
        uint8 curveFlattener,
        uint24 curveOffset
    );

    /// @notice Emitted when implementation contract is updated
    event ImplementationUpdated(
        string indexed contractType,
        address oldImplementation,
        address newImplementation
    );

    /// @notice Emitted when the close buffer is updated
    event MarketCloseBufferUpdated(uint32 oldBuffer, uint32 newBuffer);

    /// @notice Emitted when collateral is added/removed from whitelist
    event CollateralWhitelisted(address indexed collateral, bool whitelisted);

    // ============================================
    // ERRORS
    // ============================================

    error MarketAlreadyExists();
    error NotPollCreator();
    error InvalidCollateral();
    error CollateralNotWhitelisted();
    error CollateralAlreadyWhitelisted();
    error InsufficientInitialLiquidity();
    error PollNotFound();
    error MarketNotFound();
    error InvalidAddress();
    error InvalidFeeRate();
    error PariMutuelAlreadyExists();
    error InvalidCurveFlattener();
    error InvalidCurveOffset();

    // ============================================
    // MAIN FUNCTIONS
    // ============================================

    /**
     * @notice Create prediction market for a poll
     * @param _pollAddress Poll contract address
     * @param _collateral Collateral token address (USDC, USDT, etc.)
     * @param _initialLiquidity Initial liquidity amount
     * @param _distributionHint [yesWeight, noWeight] for initial price
     * @param _feeTier Trading fee tier (Uniswap V3 style: 500 = 0.05%, 3000 = 0.3%, 10000 = 1%)
     * @param _maxPriceImbalancePerHour Max allowed hourly price imbalance (scaled by BPS_DENOMINATOR, 0 = disabled)
     * @return ammPool Created AMM pool address
     */
    function createMarket(
        address _pollAddress,
        address _collateral,
        uint256 _initialLiquidity,
        uint256[2] calldata _distributionHint,
        uint24 _feeTier,
        uint24 _maxPriceImbalancePerHour
    ) external returns (address ammPool);

    /**
     * @notice Create pari-mutuel market for a poll
     * @param _pollAddress Poll contract address
     * @param _collateral Collateral token address
     * @param _initialLiquidity Initial liquidity amount in collateral tokens
     * @param _distributionHint [yesWeight, noWeight] for initial price
     * @param _curveFlattener Curve flattener [1, 11]
     * @return pariMutuelPool Created pari-mutuel pool address
     */
    function createPariMutuel(
        address _pollAddress,
        address _collateral,
        uint256 _initialLiquidity,
        uint256[2] calldata _distributionHint,
        uint8 _curveFlattener,
        uint24 _curveOffset
    ) external returns (address pariMutuelPool);

    /**
     * @notice Set OutcomeToken implementation for cloning
     * @param _implementation New implementation address
     */
    function setOutcomeTokenImplementation(address _implementation) external;

    /**
     * @notice Set PredictionAMM implementation for cloning
     * @param _implementation New implementation address
     */
    function setAMMImplementation(address _implementation) external;

    /**
     * @notice Set PredictionPariMutuel implementation for cloning
     * @param _implementation New implementation address
     */
    function setPariMutuelImplementation(address _implementation) external;

    /**
     * @notice Add collateral to whitelist
     * @param _collateral Collateral token address
     */
    function addCollateralToWhitelist(address _collateral) external;

    /**
     * @notice Remove collateral from whitelist
     * @param _collateral Collateral token address
     */
    function removeCollateralFromWhitelist(address _collateral) external;

    /**
     * @notice Set protocol fee rate
     * @param _protocolFeeRate Protocol fee rate
     */
    function setProtocolFeeRate(uint24 _protocolFeeRate) external;

    /**
     * @notice Set epochs buffer between market close and oracle resolution
     * @param _bufferEpochs Number of epochs to subtract from oracle deadline
     */
    function setMarketCloseBufferEpochs(uint32 _bufferEpochs) external;

    function platformTreasury() external view returns (address);

    function setPlatformTreasury(address _platformTreasury) external;

    // ============================================
    // VIEW FUNCTIONS
    // ============================================

    /**
     * @notice Check if market exists for poll
     * @param _pollAddress Poll address
     * @return True if market exists
     */
    function marketExists(address _pollAddress) external view returns (bool);

    /**
     * @notice Check if pari-mutuel market exists for poll
     * @param _pollAddress Poll address
     * @return True if pari-mutuel market exists
     */
    function pariMutuelExists(
        address _pollAddress
    ) external view returns (bool);

    /**
     * @notice Get oracle contract address
     * @return Oracle address
     */
    function oracle() external view returns (address);

    /**
     * @notice Check if collateral is whitelisted
     * @param _collateral Collateral address to check
     * @return True if whitelisted
     */
    function isCollateralWhitelisted(
        address _collateral
    ) external view returns (bool);

    /**
     * @notice Get all whitelisted collateral addresses
     * @return Array of whitelisted collaterals
     */
    function getWhitelistedCollaterals()
        external
        view
        returns (address[] memory);

    /**
     * @notice Batch fetch market states for a list of polls
     * @param _pollAddresses Poll addresses to query
     * @return Array of market states (zeroed struct if market missing)
     */
    function getMarketsState(
        address[] calldata _pollAddresses
    ) external view returns (MarketState[] memory);

    /**
     * @notice Get pari-mutuel market address for poll
     * @param _pollAddress Poll address
     * @return Pari-mutuel market
     */
    function getPariMutuelByPoll(
        address _pollAddress
    ) external view returns (address);
}

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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title IOutcomeToken
 * @notice Interface for outcome tokens (YES/NO) used in prediction markets
 */
interface IOutcomeToken is IERC20 {
    /// @notice Emitted when tokens are minted
    event Minted(address indexed to, uint256 amount);

    /// @notice Emitted when tokens are burned
    event Burned(address indexed from, uint256 amount);

    /// @notice Only market contract can call this function
    error OnlyMarket();
    /// @notice Already initialized error
    error AlreadyInitialized();

    /// @notice Get the market contract address
    function market() external view returns (address);

    /// @notice Get the collateral token address
    function collateral() external view returns (address);

    /// @notice Check if this is YES token (true) or NO token (false)
    function isYesToken() external view returns (bool);

    /// @notice Initialize clone (for EIP-1167 pattern)
    function initialize(
        string memory _name,
        string memory _symbol,
        address _collateral,
        bool _isYesToken,
        address _market
    ) external;

    /// @notice Mint tokens to address (only market can call)
    function mint(address to, uint256 amount) external;

    /// @notice Burn tokens from address (only market can call)
    function burn(address from, uint256 amount) external;
}

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

/**
 * @title IPredictionAMM
 * @notice Interface for Automated Market Maker for prediction market outcome tokens
 */
interface IPredictionAMM {
    // ============================================
    // STRUCTS
    // ============================================

    /// @notice Struct to manage liquidity calculations (reduces stack usage)
    struct OutcomeAmounts {
        uint256 yesToAdd;
        uint256 noToAdd;
        uint256 yesToReturn;
        uint256 noToReturn;
    }

    struct Reserves {
        uint112 rYes;
        uint112 rNo;
    }

    /// @notice Complete market information
    struct MarketInfo {
        address creator; // Market creator
        address pollAddress; // Associated poll address
        address yesToken; // YES outcome token
        address noToken; // NO outcome token
        address collateralToken; // Collateral token (USDC, etc.)
        uint24 tradingFee; // Trading fee rate
        uint24 protocolFeeRate; // Protocol fee rate
        uint24 maxPriceImbalancePerHour; // Max allowed hourly price imbalance
        uint32 closeTimestamp; // Market close timestamp in seconds
    }
    // ============================================
    // EVENTS
    // ============================================

    /// @notice Emitted when liquidity is added
    event LiquidityAdded(
        address indexed provider,
        uint256 collateralAmount,
        uint256 lpTokens,
        OutcomeAmounts amounts
    );

    /// @notice Emitted when liquidity is removed
    event LiquidityRemoved(
        address indexed provider,
        uint256 lpTokens,
        uint256 yesAmount,
        uint256 noAmount,
        uint256 collateralToReturn
    );

    event ProtocolFeesWithdrawn(
        address indexed caller,
        uint256 platformShare,
        uint256 creatorShare
    );

    /// @notice Emitted when tokens are traded
    event BuyTokens(
        address indexed trader,
        bool indexed isYes,
        uint256 tokenAmount,
        uint256 collateralAmount,
        uint256 fee
    );

    event SellTokens(
        address indexed trader,
        bool indexed isYes,
        uint256 tokenAmount,
        uint256 collateralAmount,
        uint256 fee
    );

    event SwapTokens(
        address indexed trader,
        bool indexed yesToNo,
        uint256 amountIn,
        uint256 amountOut,
        uint256 fee
    );

    /// @notice Emitted when winnings are redeemed
    event WinningsRedeemed(
        address indexed user,
        uint256 yesAmount,
        uint256 noAmount,
        uint256 collateralAmount
    );

    event Sync(uint112 rYes, uint112 rNo);

    // ============================================
    // ERRORS
    // ============================================

    error InvalidOutcome();
    error InsufficientLiquidity();
    error InvalidDistributionHint();
    error SlippageExceeded();
    error NoLiquidity();
    error NothingToRedeem();
    error ZeroAmount();
    error AlreadyInitialized();
    error MarketNotResolved();
    error NoPairToBurn();
    error TxTooOld(uint256 blockTimestamp, uint256 deadline);
    /// @notice Thrown when price swing exceeds maximum allowed limit
    /// @param swing Actual price swing (max - min)
    /// @param maxAllowed Maximum allowed swing based on MAX_SWING_BPS
    error PriceSwingExceeded(uint64 swing, uint64 maxAllowed);

    // ============================================
    // VIEW FUNCTIONS
    // ============================================

    function creator() external view returns (address);

    function pollAddress() external view returns (address);

    function collateralToken() external view returns (address);

    function yesToken() external view returns (address);

    function noToken() external view returns (address);

    function marketCloseTimestamp() external view returns (uint32);

    function tradingFee() external view returns (uint24);

    function protocolFeeRate() external view returns (uint24);

    function maxPriceImbalancePerHour() external view returns (uint24);

    function protocolFeesCollected() external view returns (uint112);

    /// @notice Precision multiplier to normalize LP tokens to 18 decimals
    /// @dev lpPrecision = 10^(18 - collateralDecimals), e.g., 10^12 for USDC (6 decimals)
    function lpPrecision() external view returns (uint64);

    function factory() external view returns (address);

    function initialize(
        address _creator,
        address _pollAddress,
        address _collateral,
        address _yesToken,
        address _noToken,
        uint32 _marketDeadlineTimestamp,
        uint24 _tradingFee,
        uint24 _protocolFeeRate,
        uint24 _maxPriceImbalancePerHour
    ) external;

    function calcBuyYes(
        uint256 collateralIn
    ) external view returns (uint256 yesOut, uint256 maxAllowedCollateralIn);

    function calcBuyNo(
        uint256 collateralIn
    ) external view returns (uint256 noOut, uint256 maxAllowedCollateralIn);

    function calcSellYes(
        uint112 yesIn
    )
        external
        view
        returns (
            uint256 collateralOut,
            uint256 protocolFee,
            uint112 maxAllowedYesIn
        );

    function calcSellNo(
        uint112 noIn
    )
        external
        view
        returns (
            uint256 collateralOut,
            uint256 protocolFee,
            uint112 maxAllowedNoIn
        );

    function calcSwapExactIn(
        bool yesToNo,
        uint256 amountIn
    )
        external
        view
        returns (
            uint256 amountOut,
            uint256 feeAmount,
            uint256 maxAllowedAmountIn
        );

    function calcSwapExactOut(
        bool yesToNo,
        uint256 amountOut
    )
        external
        view
        returns (
            uint256 amountIn,
            uint256 feeAmount,
            uint256 maxAllowedAmountIn
        );

    function getYesPrice() external view returns (uint256 price);

    function getNoPrice() external view returns (uint256 price);

    /**
     * @notice Get current market state snapshot
     * @return isLive True if market is still open
     * @return collateralTvl Collateral balance excluding protocol fees
     * @return yesChance YES chance scaled by ONE (1e18)
     * @return collateral Collateral token address
     */
    function marketState()
        external
        view
        returns (
            bool isLive,
            uint256 collateralTvl,
            uint24 yesChance,
            address collateral
        );

    function getReserves()
        external
        view
        returns (
            uint112 reserveYes,
            uint112 reserveNo,
            uint256 totalLP,
            uint256 protocolFees,
            uint256 collateralTvl
        );

    /// @notice Calculate expected LP tokens and token distribution when adding liquidity
    /// @dev For subsequent LPs only (pool must be initialized)
    /// @param collateralAmt Amount of collateral to add
    /// @return mintAmount Amount of LP tokens to receive
    /// @return amounts Token amounts (yesToAdd, noToAdd, yesToReturn, noToReturn)
    function calcAddLiquidity(
        uint256 collateralAmt
    ) external view returns (uint256 mintAmount, OutcomeAmounts memory amounts);

    /// @notice Calculate expected returns when removing liquidity
    /// @param sharesToBurn Amount of LP tokens to burn
    /// @return yesToReturn YES tokens to receive (after collateral extraction if market is live)
    /// @return noToReturn NO tokens to receive (after collateral extraction if market is live)
    /// @return collateralToReturn Collateral to receive (only if market is live, from burning matched pairs)
    function calcRemoveLiquidity(
        uint256 sharesToBurn
    )
        external
        view
        returns (
            uint256 yesToReturn,
            uint256 noToReturn,
            uint256 collateralToReturn
        );

    function getMarketInfo() external view returns (MarketInfo memory);

    function getPriceWindowStats()
        external
        view
        returns (
            uint32 hour,
            uint64 anchorPrice,
            uint64 minPrice,
            uint64 maxPrice,
            uint64 currentSwing,
            bool isCurrentHour
        );

    // ============================================
    // TRADING FUNCTIONS
    // ============================================

    function buy(
        bool isYes,
        uint256 collateralIn,
        uint256 minTokenOut,
        uint256 deadline
    ) external returns (uint256 amountOut);

    function sell(
        bool isYes,
        uint112 tokenAmountIn,
        uint256 minCollateralOut,
        uint256 deadline
    ) external returns (uint256 collateralOut);

    function swapExactIn(
        bool yesToNo,
        uint256 amountIn,
        uint256 minAmountOut,
        uint256 deadline
    ) external returns (uint256 amountOut);

    function swapExactOut(
        bool yesToNo,
        uint256 amountOut,
        uint256 maxAmountIn,
        uint256 deadline
    ) external returns (uint256 amountIn);

    function sync() external;

    // ============================================
    // LIQUIDITY FUNCTIONS
    // ============================================

    function addLiquidity(
        uint256 collateralAmount,
        uint256[2] calldata distributionHint,
        uint256 minYesToAdd,
        uint256 minNoToAdd,
        uint256 deadline
    ) external returns (uint256 lpTokens);

    function removeLiquidity(
        uint256 lpTokens,
        uint256 minCollateralOut,
        uint256 deadline
    ) external;

    function withdrawProtocolFees() external returns (uint256 totalAmount);

    // ============================================
    // RESOLUTION FUNCTIONS
    // ============================================

    function redeemWinnings() external returns (uint256 collateralAmount);
}

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

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

/**
 * @title AMMath
 * @notice Mathematical utilities for AMM calculations with overflow-safe operations
 * @dev Uses dynamic bit-shift scaling to handle large numbers while preserving precision
 */
library AMMath {
    /// @notice Basis points denominator (1,000,000 = 100%)
    uint256 internal constant BPS_DENOMINATOR = 1_000_000;
    /// @notice Scale factor for YES price representation (0..1 with 18 decimals)
    uint256 internal constant PRICE_SCALE = 1e18;

    /// @notice Maximum value for uint112 reserves
    uint256 internal constant MAX_UINT112 = type(uint112).max;

    // ============================================
    // SCALED SQRT FUNCTIONS
    // ============================================

    /**
     * @notice Compute sqrt(a² + b) using dynamic scaling
     * @dev Uses minimal shift to preserve maximum precision.
     *      For small values: direct computation without scaling.
     *      For large values: scales down, computes sqrt, scales up.
     * @param a First operand (will be squared)
     * @param b Second operand (added to a²)
     * @return Square root of (a² + b)
     */
    function scaledSqrt(uint256 a, uint256 b) internal pure returns (uint256) {
        // Calculate minimum shift needed to prevent overflow
        // a² needs 2*aBits, b needs bBits
        uint256 aBits = a > 0 ? Math.log2(a) + 1 : 0;
        uint256 bBits = b > 0 ? Math.log2(b) + 1 : 0;

        // Max bits needed for sum
        uint256 maxBits = aBits * 2 > bBits ? aBits * 2 : bBits;

        // Shift needed: if maxBits >= 256, we need to shift
        // Use maxBits >= 256 and subtract 254 to leave 1 extra bit for sum overflow safety
        // Ensure shift is even for clean sqrt (sqrt scaling requires even exponent)
        uint256 shift = maxBits >= 256 ? maxBits - 254 : 0;
        if (shift % 2 == 1) shift += 1;

        if (shift == 0) {
            // No scaling needed - direct computation
            return Math.sqrt(a * a + b);
        }

        // Scale down, compute sqrt, scale up
        uint256 scale = uint256(1) << shift;
        uint256 aSquaredScaled = Math.mulDiv(a, a, scale);
        uint256 bScaled = b / scale;

        return Math.sqrt(aSquaredScaled + bScaled) << (shift / 2);
    }

    /**
     * @notice Compute sqrt(a² + multiplier * b) when multiplier * b would overflow
     * @dev Uses dynamic scaling based on operand sizes
     * @param a First operand (will be squared)
     * @param multiplier Multiplier for b
     * @param b Second operand
     * @return Square root of (a² + multiplier * b)
     */
    function scaledSqrtSafe(
        uint256 a,
        uint256 multiplier,
        uint256 b
    ) internal pure returns (uint256) {
        // Calculate bits needed for each term
        uint256 aBits = a > 0 ? Math.log2(a) + 1 : 0;
        uint256 multBits = multiplier > 0 ? Math.log2(multiplier) + 1 : 0;
        uint256 bBits = b > 0 ? Math.log2(b) + 1 : 0;

        // a² needs 2*aBits, multiplier*b needs multBits + bBits
        uint256 aSquaredBits = aBits * 2;
        uint256 productBits = multBits + bBits;
        uint256 maxBits = aSquaredBits > productBits
            ? aSquaredBits
            : productBits;

        // Shift needed to fit in 256 bits, must be even
        // Use maxBits >= 256 and subtract 254 to leave 1 extra bit for sum overflow safety
        uint256 shift = maxBits >= 256 ? maxBits - 254 : 0;
        if (shift % 2 == 1) shift += 1;

        if (shift == 0) {
            // No scaling needed - direct computation
            return Math.sqrt(a * a + multiplier * b);
        }

        // Scale down, compute sqrt, scale up
        uint256 scale = uint256(1) << shift;
        uint256 aSquaredScaled = Math.mulDiv(a, a, scale);
        uint256 productScaled = Math.mulDiv(multiplier, b, scale);

        return Math.sqrt(aSquaredScaled + productScaled) << (shift / 2);
    }

    /**
     * @notice Compute sqrt(B² + 4*A*aF*inner) for _maxAllowed calculations
     * @dev Specialized function for price limit calculations
     * @param B Coefficient B from quadratic formula
     * @param A Coefficient A from quadratic formula
     * @param aF a * FEE_DENOMINATOR
     * @param inner (S - P) * b - P * a
     * @return Square root of (B² + 4*A*aF*inner)
     */
    function scaledSqrtForQuadratic(
        uint256 B,
        uint256 A,
        uint256 aF,
        uint256 inner
    ) internal pure returns (uint256) {
        // Calculate shift needed: max(2*log2(B), 2+log2(A)+log2(aF)+log2(inner)) - 256
        uint256 shift;
        {
            uint256 bBits = B > 0 ? Math.log2(B) + 1 : 0;
            uint256 productBits = 2 +
                (A > 0 ? Math.log2(A) + 1 : 0) +
                (aF > 0 ? Math.log2(aF) + 1 : 0) +
                (inner > 0 ? Math.log2(inner) + 1 : 0);
            uint256 maxBits = bBits * 2 > productBits ? bBits * 2 : productBits;
            // Use maxBits >= 256 and subtract 254 to leave 1 extra bit for sum overflow safety
            shift = maxBits >= 256 ? maxBits - 254 : 0;
            if (shift % 2 == 1) shift += 1;
        }

        if (shift == 0) {
            // No scaling needed - direct computation
            return Math.sqrt(B * B + 4 * A * aF * inner);
        }

        // Scale down, compute sqrt, scale up
        uint256 scale = uint256(1) << shift;
        uint256 sumScaled = Math.mulDiv(B, B, scale) +
            Math.mulDiv(4 * A, Math.mulDiv(aF, inner, scale), 1);

        return Math.sqrt(sumScaled) << (shift / 2);
    }

    // ============================================
    // UTILITY FUNCTIONS
    // ============================================

    /**
     * @notice Calculate remaining capacity before hitting uint112 max
     * @param reserve Current reserve value
     * @return Remaining capacity (0 if already at max)
     */
    function maxReserveCapacity(
        uint256 reserve
    ) internal pure returns (uint256) {
        if (reserve >= MAX_UINT112) {
            return 0;
        }
        return MAX_UINT112 - reserve;
    }

    /**
     * @notice Calculate optimal swap amount for sell operation WITH fee
     * @dev Uses quadratic formula with dynamic scaling for overflow-safe discriminant computation
     * @dev DO NOT modify without thorough mathematical analysis - this is a critical optimization
     *
     * Mathematical derivation (continuous form):
     *   Goal: maximize pairs = min(S - X, swapOut(X))
     *   Optimal when: S - X = swapOut(X) (remaining tokens = swapped opposite tokens)
     *
     *   With fee: swapOut(X) = (X * f * r_to) / (r_from + X * f)
     *   where f = (BPS_DENOMINATOR - tradingFee) / BPS_DENOMINATOR.
     *
     *   Solving: S - X = (X * f * r_to) / (r_from + X * f)
     *   Cross-multiply: (S - X)(r_from + X*f) = X * f * r_to
     *   Expand: S*r_from + S*X*f - X*r_from - X²*f = X*f*r_to
     *   Rearrange: f*X² + X*(r_from + f*r_to - S*f) - S*r_from = 0
     *
     *   Using quadratic formula on a'X² + b'X + c' = 0:
     *     X = (-b' + sqrt(b'² - 4*a'*c')) / (2*a')
     *
     *   In the integer implementation we introduce:
     *     feeNumer = BPS_DENOMINATOR - tradingFee
     *     feeDenom = BPS_DENOMINATOR
     *   and work with:
     *     a' = feeNumer
     *     b' = pFull = r_from * feeDenom + feeNumer * (r_to - S)
     *     c' = -q = - S * r_from * feeDenom
     *
     *   This gives:
     *     X = (sqrt(pFull² + 4 * feeNumer * q) - pFull) / (2 * feeNumer)
     *
     * Implementation details:
     *   - Uses dynamic bit-shift scaling based on operand sizes (via Math.log2)
     *   - For small values: direct computation without scaling (no precision loss)
     *   - For large values: minimal necessary shift to fit in uint256
     *   - The final division uses half-up rounding for optimal result.
     *
     * @param amountToSell Amount of tokens user wants to sell
     * @param sellIsYes True if selling YES, false if selling NO
     * @param rYes Current YES reserve
     * @param rNo Current NO reserve
     * @param feeNumer Fee numerator (BPS_DENOMINATOR - tradingFee), must be > 0
     * @return amountToSwap Optimal amount to swap into opposite token
     */
    function calculateOptimalSellSwap(
        uint112 amountToSell,
        bool sellIsYes,
        uint112 rYes,
        uint112 rNo,
        uint256 feeNumer
    ) internal pure returns (uint256 amountToSwap) {
        uint256 rSwapFrom = sellIsYes ? rYes : rNo;
        uint256 rSwapTo = sellIsYes ? rNo : rYes;

        // Calculate p = r_from * feeDenom + feeNumer * (r_to - S)
        // Using unchecked where overflow is impossible due to uint112 inputs
        uint256 pFull;
        {
            uint256 term1 = rSwapFrom * BPS_DENOMINATOR;
            uint256 term2 = feeNumer * rSwapTo;
            uint256 term3 = feeNumer * amountToSell;
            if (term3 > term1 + term2) {
                return 0;
            }
            pFull = term1 + term2 - term3;
        }

        // Calculate q = amountToSell * r_from * feeDenom
        // amountToSell and rSwapFrom are uint112, so their product fits in uint224
        // Multiplying by BPS_DENOMINATOR (1e6) gives max ~5.2e39 * 1e6 = 5.2e45, safe
        uint256 q = uint256(amountToSell) * rSwapFrom * BPS_DENOMINATOR;

        // Calculate discriminant = sqrt(pFull² + 4 * feeNumer * q)
        uint256 discriminant;

        // Compute 4 * feeNumer * q safely
        uint256 fourFeeQ;
        {
            uint256 fourFeeNumer = 4 * feeNumer;
            // Check for overflow: fourFeeNumer * q
            if (q != 0 && fourFeeNumer > type(uint256).max / q) {
                // Will overflow - use scaled computation
                discriminant = scaledSqrtSafe(pFull, fourFeeNumer, q);
            } else {
                fourFeeQ = fourFeeNumer * q;

                if (pFull <= type(uint128).max) {
                    // pFull fits in 128 bits, check if we can compute directly
                    uint256 pSquared = pFull * pFull;
                    bool needsScaling = fourFeeQ != 0 &&
                        pSquared > type(uint256).max - fourFeeQ;

                    if (!needsScaling) {
                        // Direct computation - most common case
                        discriminant = Math.sqrt(pSquared + fourFeeQ);
                    } else {
                        // Need scaling due to sum overflow risk
                        discriminant = scaledSqrt(pFull, fourFeeQ);
                    }
                } else {
                    // pFull > 2^128, must use scaled computation
                    discriminant = scaledSqrt(pFull, fourFeeQ);
                }
            }
        }

        if (discriminant <= pFull) {
            return 0;
        }

        // Final calculation with half-up rounding:
        // amountToSwap = round((discriminant - pFull) / (2 * feeNumer))
        uint256 numerator = discriminant - pFull;
        uint256 denominator = 2 * feeNumer;

        amountToSwap = (numerator + denominator / 2) / denominator;

        // Clamp to reserve capacity
        uint256 capacity = maxReserveCapacity(rSwapFrom);
        if (amountToSwap > capacity) {
            amountToSwap = capacity;
        }
    }

    /**
     * @notice Solve for max allowed YES→NO input under price band using quadratic formula
     * @dev Uses same discriminant handling style as _calculateOptimalSellSwap, with pessimistic rounding.
     *
     * Mathematical derivation:
     *   Target price: P = baselinePrice - delta
     *   Quadratic: A*x² + B*x - Q = 0
     *   where:
     *     A = P * feeNumer
     *     B = P * a * (feeNumer + BPS_DENOMINATOR)
     *     Q = a * BPS_DENOMINATOR * [(S - P) * b - P * a]
     *
     *   Solution: x = (-B + sqrt(B² + 4AQ)) / (2A)
     *
     * @param baselinePrice The baseline price in PRICE_SCALE (1e18)
     * @param delta Allowed price deviation in PRICE_SCALE (1e18)
     * @param a Reserve of input token (rYes for YES→NO)
     * @param b Reserve of output token (rNo for YES→NO)
     * @param feeNumer Fee numerator (BPS_DENOMINATOR - tradingFee)
     * @return Maximum allowed input amount, or type(uint256).max if unconstrained
     */
    function maxAllowed(
        uint64 baselinePrice,
        uint256 delta,
        uint256 a,
        uint256 b,
        uint256 feeNumer
    ) internal pure returns (uint256) {
        uint256 bp = uint256(baselinePrice);

        // If delta >= baseline, price can go to 0 → unconstrained
        if (delta >= bp) {
            return type(uint256).max;
        }

        uint256 P = bp - delta;
        uint256 S = PRICE_SCALE;

        // A = P * feeNumer (coefficient of x²)
        // P <= PRICE_SCALE (1e18), feeNumer <= BPS_DENOMINATOR (1e6)
        // Max: 1e18 * 1e6 = 1e24, safe (fits in uint256)
        uint256 A = P * feeNumer;
        if (A == 0) {
            return type(uint256).max;
        }

        // For large reserves, we need overflow-safe arithmetic
        // a, b can be up to uint112.max ≈ 5.2e33
        // P, S are up to 1e18
        // So P * a can be up to 1e18 * 5.2e33 = 5.2e51, which fits in uint256 (max ~1.15e77)

        // B = P * a * (feeNumer + BPS_DENOMINATOR) (coefficient of x)
        uint256 feeSum = feeNumer + BPS_DENOMINATOR; // max ~2e6
        // B = P * a * feeSum, reorder to avoid overflow:
        // P * feeSum <= 1e18 * 2e6 = 2e24, then * a (max 5.2e33) = 1e58, safe
        uint256 B = (P * feeSum) * a;

        // Q = a * BPS_DENOMINATOR * [(S - P) * b - P * a]
        // term1 = (S - P) * b, term2 = P * a
        // Both can be up to 1e18 * 5.2e33 = 5.2e51, safe (fits in uint256)
        uint256 term1 = (S - P) * b;
        uint256 term2 = P * a;

        if (term1 <= term2) {
            // No positive solution - current price already at or beyond limit
            return 0;
        }

        uint256 inner = term1 - term2; // max 5.2e51
        // Q = a * BPS_DENOMINATOR * inner
        // a * BPS_DENOMINATOR <= 5.2e33 * 1e6 = 5.2e39
        // 5.2e39 * 5.2e51 = 2.7e91 > uint256.max!
        // Need to check and use scaled computation
        uint256 aF = a * BPS_DENOMINATOR; // max 5.2e39, safe

        // Calculate discriminant = sqrt(B² + 4AQ)
        uint256 discriminant;

        // Check if aF * inner would overflow
        if (inner != 0 && aF > type(uint256).max / inner) {
            // Q would overflow - use fully scaled approach
            discriminant = scaledSqrtForQuadratic(B, A, aF, inner);
        } else {
            uint256 Q = aF * inner;
            uint256 fourA = 4 * A;

            // Check for overflow: fourA * Q
            if (Q != 0 && fourA > type(uint256).max / Q) {
                // Will overflow - use scaled computation
                discriminant = scaledSqrtSafe(B, fourA, Q);
            } else {
                uint256 fourAQ = fourA * Q;

                if (B <= type(uint128).max) {
                    // Check if direct computation is safe
                    uint256 BSquared = B * B;
                    bool needsScaling = fourAQ != 0 &&
                        BSquared > type(uint256).max - fourAQ;

                    if (!needsScaling) {
                        discriminant = Math.sqrt(BSquared + fourAQ);
                    } else {
                        discriminant = scaledSqrt(B, fourAQ);
                    }
                } else {
                    discriminant = scaledSqrt(B, fourAQ);
                }
            }
        }

        if (discriminant <= B) {
            return 0;
        }

        // x = (discriminant - B) / (2A), floor division (pessimistic)
        uint256 numerator = discriminant - B;
        uint256 x = numerator / (2 * A);

        return x;
    }
}

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IOutcomeToken} from "./interfaces/IOutcomeToken.sol";

/**
 * @title OutcomeToken
 * @notice ERC20 token representing YES or NO outcome in prediction market
 * @dev Minting and burning restricted to market contract only
 * @dev Uses same decimals as collateral token for 1:1 ratio (1 YES + 1 NO = 1 collateral)
 * @dev Uses EIP-1167 Clone Pattern (minimal proxy with DELEGATECALL, non-upgradeable)
 */
contract OutcomeToken is ERC20, IOutcomeToken {
    /// @notice Market contract that can mint/burn tokens
    address public market;

    /// @notice Collateral token (e.g., USDC, USDT)
    address public collateral;

    /// @notice True if this is YES token, false if NO token
    bool public isYesToken;

    /// @notice Token name (storage for clone pattern)
    string private _name;

    /// @notice Token symbol (storage for clone pattern)
    string private _symbol;

    /// @notice Decimals (same as collateral for 1:1 ratio)
    uint8 private _decimals;

    /// @notice Initialization flag (for clone pattern)
    bool private _initialized;

    /// @notice Restrict function to market only
    modifier onlyMarket() {
        if (msg.sender != market) revert OnlyMarket();
        _;
    }

    /// @notice Restrict to one-time initialization (for clone pattern)
    modifier initializer() {
        if (_initialized) revert AlreadyInitialized();
        _initialized = true;
        _;
    }

    constructor() ERC20("", "") {
        // Lock implementation contract
        _initialized = true;
    }

    /**
     * @notice Initialize outcome token (for EIP-1167 clones)
     * @param name_ Token name (e.g., "YES - Will it rain tomorrow?")
     * @param symbol_ Token symbol (e.g., "YES")
     * @param _collateral Collateral token address
     * @param _isYesToken True for YES token, false for NO token
     * @param _market Market contract address
     */
    function initialize(
        string memory name_,
        string memory symbol_,
        address _collateral,
        bool _isYesToken,
        address _market
    ) external initializer {
        _name = name_;
        _symbol = symbol_;
        collateral = _collateral;
        isYesToken = _isYesToken;
        market = _market;

        // Use same decimals as collateral for 1:1 ratio
        _decimals = IERC20Metadata(_collateral).decimals();
    }

    /**
     * @notice Override name to read from storage (for clone pattern)
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @notice Override symbol to read from storage (for clone pattern)
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @notice Override decimals to match collateral
     */
    function decimals() public view virtual override returns (uint8) {
        return _decimals;
    }

    /**
     * @notice Mint tokens to address
     * @dev Only callable by market contract
     * @param to Recipient address
     * @param amount Amount to mint
     */
    function mint(address to, uint256 amount) external onlyMarket {
        _mint(to, amount);
        emit Minted(to, amount);
    }

    /**
     * @notice Burn tokens from address
     * @dev Only callable by market contract
     * @param from Address to burn from
     * @param amount Amount to burn
     */
    function burn(address from, uint256 amount) external onlyMarket {
        _burn(from, amount);
        emit Burned(from, amount);
    }
}

File 21 of 21 : IPredictionOracle.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/**
 * @title Poll Status Enum
 * @notice Shared enumeration for poll status
 */
enum PollStatus {
    Pending, // 0 - Initial state, awaiting answer
    Yes, // 1 - Positive answer
    No, // 2 - Negative answer
    Unknown // 3 - Cannot determine answer at check time
}

/**
 * @title PollInfo
 * @notice Complete poll information for backend processing
 */
struct PollInfo {
    address pollAddress;
    string question;
    string rules;
    string[] sources;
    uint32 deadlineEpoch; // target epoch when poll result should be resolved
    uint32 finalizationEpoch; // Epoch when poll result becomes final and immutable
    uint32 checkEpoch; // Epoch when poll should be checked/resolved by oracle operator
    address creator;
    address arbiter; // Arbiter address who can override poll status
    PollStatus status;
    uint8 category; // Poll type/category for classification
    string resolutionReason; // Explanation/reasoning for the resolution decision
}

/**
 * @title IPredictionPoll
 * @notice Interface for PredictionPoll contract
 */
interface IPredictionPoll {
    /// @notice Poll data structure
    struct PollData {
        string question; // Question text (max 200 chars)
        string rules; // Rules description (max 1000 chars)
        string[] sources; // Source URLs (max 3, 200 chars each)
        address creator; // Poll creator address
        address arbiter; // Arbiter address who can override poll status
        PollStatus status; // Current poll status (packed with arbiter and category)
        uint8 category; // Poll type/category (0-255, for frontend classification)
        uint32 finalizationEpoch; // Epoch when poll result becomes final and immutable
        uint32 deadlineEpoch; // target epoch when poll result should be resolved
        string resolutionReason; // Explanation/reasoning for the resolution decision
    }
    // ============================================
    // EVENTS
    // ============================================

    event AnswerSet(PollStatus status, address indexed setter, string reason);

    /// @notice Emitted when arbitration is started
    /// @param arbiter Address of the arbiter who started arbitration
    /// @param oldFinalizationEpoch Previous finalization epoch
    /// @param newFinalizationEpoch New finalization epoch after extension
    event ArbitrationStarted(
        address indexed arbiter,
        uint32 oldFinalizationEpoch,
        uint32 newFinalizationEpoch
    );

    // ============================================
    // ERRORS
    // ============================================

    /// @notice Only factory can call this function
    error OnlyFactory();

    /// @notice Only operator can call this function
    error OnlyOperator();

    /// @notice Deadline epoch not reached yet
    error DeadlineEpochNotReached();

    /// @notice Status is not Pending - operator cannot override
    error StatusNotPending();

    /// @notice Arbitration has not started yet
    error ArbitrationNotStarted();

    /// @notice Poll has been finalized and cannot be changed
    error PollFinalized();

    /// @notice Only arbiter can call this function
    error OnlyArbiter();

    /// @notice Poll already initialized
    error AlreadyInitialized();

    /// @notice Cannot set status to Pending as resolution
    error InvalidResolutionStatus();

    // ============================================
    // FUNCTIONS
    // ============================================

    function factory() external view returns (address);

    function arbitrationStarted() external view returns (bool);

    function ARBITRATION_SUBMISSION_WINDOW() external view returns (uint32);

    function ARBITRATION_ESCALATION_PERIOD() external view returns (uint32);

    function initialize(
        string memory _question,
        string memory _rules,
        string[] memory _sources,
        uint32 _deadlineEpoch,
        address _creator,
        address _arbiter,
        uint8 _category
    ) external;

    function setAnswer(PollStatus _status, string calldata _reason) external;

    function resolveArbitration(
        PollStatus _status,
        string calldata _reason
    ) external;

    function refreshPoll(bool _isFree) external returns (bool);

    function getStatus() external view returns (PollStatus);

    function getPollData() external view returns (PollData memory);

    function getCreator() external view returns (address);

    function getArbiter() external view returns (address);

    function getPollType() external view returns (uint8);

    function getFinalizationEpoch() external view returns (uint32);

    function getDeadlineEpoch() external view returns (uint32);

    function getFinalizedStatus()
        external
        view
        returns (bool isFinalized, PollStatus status);

    function matchesFilters(
        uint256 _statusFilter,
        uint256 _typeFilter
    ) external view returns (bool);

    function startArbitration() external;
}

/**
 * @title IPredictionOracle
 * @notice Interface for PredictionOracle contract
 */
interface IPredictionOracle {
    // ============================================
    // EVENTS
    // ============================================

    /// @notice Emitted when new poll is created
    event PollCreated(
        address indexed pollAddress,
        address indexed creator,
        uint32 deadlineEpoch,
        string question
    );

    /// @notice Emitted when poll check epoch is refreshed
    event PollRefreshed(
        address indexed pollAddress,
        uint32 oldCheckEpoch,
        uint32 newCheckEpoch,
        bool wasFree
    );

    /// @notice Emitted when operator is added
    event OperatorAdded(address indexed operator);

    /// @notice Emitted when operator is removed
    event OperatorRemoved(address indexed operator);

    /// @notice Emitted when operator gas fee is updated
    event OperatorGasFeeUpdated(uint256 newFee);

    /// @notice Emitted when protocol fee is updated
    event ProtocolFeeUpdated(uint256 newFee);

    /// @notice Emitted when poll implementation is updated
    event PollImplementationUpdated(
        address indexed oldImplementation,
        address indexed newImplementation
    );

    /// @notice Emitted when protocol fees are withdrawn
    event ProtocolFeesWithdrawn(address indexed to, uint256 amount);

    // ============================================
    // ERRORS
    // ============================================

    error InvalidQuestionLength();
    error InvalidRulesLength();
    error TooManySources();
    error InvalidSourceLength();
    error InsufficientPayment();
    error NoOperatorsAvailable();
    error OperatorAlreadyExists();
    error OperatorNotFound();
    error CannotRemoveLastOperator();
    error CannotRefreshYet();
    error InvalidAddress();
    error RefreshPaymentRequired();
    error PollNotFound();
    error WithdrawalFailed();
    error InsufficientProtocolFees();
    error InvalidTargetTimestamp();
    error ForbiddenRefresh();
    error ImplementationNotSet();

    // ============================================
    // POLL MANAGEMENT
    // ============================================

    function createPoll(
        string calldata _question,
        string calldata _rules,
        string[] calldata _sources,
        uint256 _targetTimestamp,
        address _arbiter,
        uint8 _category
    ) external payable returns (address pollAddress);

    function refreshPollFree(address _pollAddress) external;

    function refreshPollPaid(address _pollAddress) external payable;

    // ============================================
    // MANAGEMENT
    // ============================================

    function addOperator(address _operator) external;

    function removeOperator(address _operator) external;

    function getOperators() external view returns (address[] memory);

    function getOperatorCount() external view returns (uint256);

    function isOperator(address _addr) external view returns (bool);

    function setOperatorGasFee(uint256 _fee) external;

    function setProtocolFee(uint256 _fee) external;

    function setPollImplementation(address _implementation) external;

    function pause() external;

    function unpause() external;

    function withdrawProtocolFees(address payable _to) external;

    // ============================================
    // VIEW FUNCTIONS
    // ============================================

    function operatorGasFee() external view returns (uint256);

    function protocolFee() external view returns (uint256);

    function accumulatedProtocolFees() external view returns (uint256);

    function pollImplementation() external view returns (address);

    function MAX_QUESTION_LENGTH() external view returns (uint256);

    function MAX_RULES_LENGTH() external view returns (uint256);

    function MAX_SOURCES() external view returns (uint256);

    function MAX_SOURCE_LENGTH() external view returns (uint256);

    function PENDING_TIMEOUT_EPOCHS() external view returns (uint256);

    function EPOCH_LENGTH() external view returns (uint256);

    function getCurrentEpoch() external view returns (uint32);

    function getPollsByEpochRange(
        uint32 _fromEpoch,
        uint32 _toEpoch,
        uint256 _statusFilter,
        uint256 _typeFilter,
        uint256 _maxResults,
        uint256 _startIndex
    )
        external
        view
        returns (PollInfo[] memory polls, uint32 nextEpoch, uint256 nextIndex);

    function getPollsByEpochs(
        uint32[] calldata _epochs,
        uint256 _statusFilter,
        uint256 _typeFilter,
        uint256 _maxResults
    ) external view returns (PollInfo[] memory polls);

    function getPollsByCreator(
        address _creator,
        uint256 _maxResults,
        uint256 _offset
    ) external view returns (PollInfo[] memory polls, bool hasMore);

    function getCurrentCheckEpoch(
        address _pollAddress
    ) external view returns (uint32);

    function verifyPollAddressExists(
        address _pollAddress
    ) external view returns (bool);

    function pollsByCheckEpoch(
        uint32 _epoch,
        uint256 _index
    ) external view returns (address);

    function pollsByCreator(
        address _creator,
        uint256 _index
    ) external view returns (address);
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 99999
  },
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract ABI

API
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC20InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC20InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC20InvalidSender","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"}],"name":"ERC20InvalidSpender","type":"error"},{"inputs":[],"name":"InsufficientLiquidity","type":"error"},{"inputs":[],"name":"InvalidDistributionHint","type":"error"},{"inputs":[],"name":"InvalidOutcome","type":"error"},{"inputs":[],"name":"MarketNotResolved","type":"error"},{"inputs":[],"name":"NoLiquidity","type":"error"},{"inputs":[],"name":"NoPairToBurn","type":"error"},{"inputs":[],"name":"NothingToRedeem","type":"error"},{"inputs":[{"internalType":"uint64","name":"swing","type":"uint64"},{"internalType":"uint64","name":"maxAllowed","type":"uint64"}],"name":"PriceSwingExceeded","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"uint8","name":"bits","type":"uint8"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"SafeCastOverflowedUintDowncast","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"SlippageExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"blockTimestamp","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"TxTooOld","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":true,"internalType":"bool","name":"isYes","type":"bool"},{"indexed":false,"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"BuyTokens","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpTokens","type":"uint256"},{"components":[{"internalType":"uint256","name":"yesToAdd","type":"uint256"},{"internalType":"uint256","name":"noToAdd","type":"uint256"},{"internalType":"uint256","name":"yesToReturn","type":"uint256"},{"internalType":"uint256","name":"noToReturn","type":"uint256"}],"indexed":false,"internalType":"struct IPredictionAMM.OutcomeAmounts","name":"amounts","type":"tuple"}],"name":"LiquidityAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256","name":"lpTokens","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"yesAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"noAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralToReturn","type":"uint256"}],"name":"LiquidityRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"uint256","name":"platformShare","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"creatorShare","type":"uint256"}],"name":"ProtocolFeesWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":true,"internalType":"bool","name":"isYes","type":"bool"},{"indexed":false,"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"SellTokens","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":true,"internalType":"bool","name":"yesToNo","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"SwapTokens","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint112","name":"rYes","type":"uint112"},{"indexed":false,"internalType":"uint112","name":"rNo","type":"uint112"}],"name":"Sync","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"yesAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"noAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralAmount","type":"uint256"}],"name":"WinningsRedeemed","type":"event"},{"inputs":[],"name":"BPS_DENOMINATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEAD_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINIMUM_LIQUIDITY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ONE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"collateralAmt","type":"uint256"},{"internalType":"uint256[2]","name":"distributionHint","type":"uint256[2]"},{"internalType":"uint256","name":"minYesToAdd","type":"uint256"},{"internalType":"uint256","name":"minNoToAdd","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"mintAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"isYes","type":"bool"},{"internalType":"uint256","name":"collateralAmtIn","type":"uint256"},{"internalType":"uint256","name":"minTokenOut","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"buy","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"collateralAmt","type":"uint256"}],"name":"calcAddLiquidity","outputs":[{"internalType":"uint256","name":"mintAmount","type":"uint256"},{"components":[{"internalType":"uint256","name":"yesToAdd","type":"uint256"},{"internalType":"uint256","name":"noToAdd","type":"uint256"},{"internalType":"uint256","name":"yesToReturn","type":"uint256"},{"internalType":"uint256","name":"noToReturn","type":"uint256"}],"internalType":"struct IPredictionAMM.OutcomeAmounts","name":"amounts","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"collateralIn","type":"uint256"}],"name":"calcBuyNo","outputs":[{"internalType":"uint256","name":"noOut","type":"uint256"},{"internalType":"uint256","name":"maxAllowedCollateralIn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"collateralIn","type":"uint256"}],"name":"calcBuyYes","outputs":[{"internalType":"uint256","name":"yesOut","type":"uint256"},{"internalType":"uint256","name":"maxAllowedCollateralIn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"name":"calcRemoveLiquidity","outputs":[{"internalType":"uint256","name":"yesToReturn","type":"uint256"},{"internalType":"uint256","name":"noToReturn","type":"uint256"},{"internalType":"uint256","name":"collateralToReturn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint112","name":"noIn","type":"uint112"}],"name":"calcSellNo","outputs":[{"internalType":"uint256","name":"collateralOut","type":"uint256"},{"internalType":"uint256","name":"protocolFee","type":"uint256"},{"internalType":"uint112","name":"maxAllowedNoIn","type":"uint112"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint112","name":"yesIn","type":"uint112"}],"name":"calcSellYes","outputs":[{"internalType":"uint256","name":"collateralOut","type":"uint256"},{"internalType":"uint256","name":"protocolFee","type":"uint256"},{"internalType":"uint112","name":"maxAllowedYesIn","type":"uint112"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"yesToNo","type":"bool"},{"internalType":"uint256","name":"amountIn","type":"uint256"}],"name":"calcSwapExactIn","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"maxAllowedAmountIn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"yesToNo","type":"bool"},{"internalType":"uint256","name":"amountOut","type":"uint256"}],"name":"calcSwapExactOut","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"maxAllowedAmountIn","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateralToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"creator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMarketInfo","outputs":[{"components":[{"internalType":"address","name":"creator","type":"address"},{"internalType":"address","name":"pollAddress","type":"address"},{"internalType":"address","name":"yesToken","type":"address"},{"internalType":"address","name":"noToken","type":"address"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint24","name":"tradingFee","type":"uint24"},{"internalType":"uint24","name":"protocolFeeRate","type":"uint24"},{"internalType":"uint24","name":"maxPriceImbalancePerHour","type":"uint24"},{"internalType":"uint32","name":"closeTimestamp","type":"uint32"}],"internalType":"struct IPredictionAMM.MarketInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNoPrice","outputs":[{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPriceWindowStats","outputs":[{"internalType":"uint32","name":"hour","type":"uint32"},{"internalType":"uint64","name":"anchorPrice","type":"uint64"},{"internalType":"uint64","name":"minPrice","type":"uint64"},{"internalType":"uint64","name":"maxPrice","type":"uint64"},{"internalType":"uint64","name":"currentSwing","type":"uint64"},{"internalType":"bool","name":"isCurrentHour","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserveYes","type":"uint112"},{"internalType":"uint112","name":"_reserveNo","type":"uint112"},{"internalType":"uint256","name":"_totalLP","type":"uint256"},{"internalType":"uint256","name":"_protocolFees","type":"uint256"},{"internalType":"uint256","name":"_collateralTvl","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getYesPrice","outputs":[{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_creator","type":"address"},{"internalType":"address","name":"_pollAddress","type":"address"},{"internalType":"address","name":"_collateral","type":"address"},{"internalType":"address","name":"_yesToken","type":"address"},{"internalType":"address","name":"_noToken","type":"address"},{"internalType":"uint32","name":"_marketDeadlineTimestamp","type":"uint32"},{"internalType":"uint24","name":"_tradingFee","type":"uint24"},{"internalType":"uint24","name":"_protocolFeeRate","type":"uint24"},{"internalType":"uint24","name":"_maxPriceImbalancePerHour","type":"uint24"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lpPrecision","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"marketCloseTimestamp","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"marketState","outputs":[{"internalType":"bool","name":"isLive","type":"bool"},{"internalType":"uint256","name":"collateralTvl","type":"uint256"},{"internalType":"uint24","name":"yesChance","type":"uint24"},{"internalType":"address","name":"collateral","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxPriceImbalancePerHour","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"noToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pollAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFeeRate","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFeesCollected","outputs":[{"internalType":"uint112","name":"","type":"uint112"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"redeemWinnings","outputs":[{"internalType":"uint256","name":"collateralAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"sharesToBurn","type":"uint256"},{"internalType":"uint256","name":"minCollateralOut","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"isYes","type":"bool"},{"internalType":"uint112","name":"tokenAmountIn","type":"uint112"},{"internalType":"uint256","name":"minCollateralOut","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"sell","outputs":[{"internalType":"uint256","name":"collateralOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"yesToNo","type":"bool"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactIn","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"yesToNo","type":"bool"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"maxAmountIn","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactOut","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"sync","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tradingFee","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawProtocolFees","outputs":[{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"yesToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ 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.