SPX6900Strategy

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/SPX6900Strategy.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ERC20} from "solady/tokens/ERC20.sol";
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";

interface IUniswapV2Router02 {
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable;

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;

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

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

interface IUniversalRouter {
    function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable;
}

library Commands {
    uint256 internal constant V4_SWAP = 0x10;
}

contract SPX6900Strategy is ERC20, ReentrancyGuard, Ownable {
    using PoolIdLibrary for PoolKey;
    using StateLibrary for IPoolManager;

    /* ═══════════════════════════════════════════════════ */
    /*                      CONSTANTS                      */
    /* ═══════════════════════════════════════════════════ */

    IPositionManager private constant POSM = IPositionManager(0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e);
    IAllowanceTransfer private constant PERMIT2 = IAllowanceTransfer(0x000000000022D473030F116dDEE9F6B43aC78BA3);
    IUniversalRouter private constant UNIVERSAL_ROUTER = IUniversalRouter(0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af);
    IUniswapV2Router02 private constant UNISWAP_ROUTER = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);

    address public constant SPX6900 = 0xE0f63A424a4439cBE457D80E4f4b51aD25b2c56C;
    address public constant AEON_COLLECTION = 0xc374a204334d4Edd4C6a62f0867C752d65E9579c;
    address public constant SEAPORT = 0x0000000000000068F116a894984e2DB1123eB395;
    address public constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;

    uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18; // 1 Billion tokens

    string private tokenName;
    string private tokenSymbol;
    address public immutable teamWallet;

    /* ═══════════════════════════════════════════════════ */
    /*                   STATE VARIABLES                   */
    /* ═══════════════════════════════════════════════════ */

    // Uniswap V4 Pool variables
    bool public tradingEnabled;
    bool public poolInitialized;
    bool private loadingLiquidity;
    PoolKey public poolKey;
    PoolId public poolId;
    int24 public tickLower;
    int24 public tickUpper;
    uint256 public positionTokenId;

    // Cumulative tracking (all-time totals)
    uint256 public totalEthForSPX; // Total ETH spent on SPX6900 purchases
    uint256 public totalSPXPurchased; // Total SPX6900 tokens acquired
    uint256 public totalEthForNFTs; // Total ETH sent to NFT wallet (deprecated for Aeon system)
    uint256 public aeonNFTLockedEth; // ETH currently locked in unsold Aeon NFTs

    // SPX6900 position tracking for profit taking
    uint256 public spxHoldingBalance; // Current SPX6900 balance held
    uint256 public spxCostBasis; // Total ETH cost basis for current holdings
    uint256 public profitThreshold = 20; // Profit threshold in percentage (default 20%)

    // Position-based SPX buy system
    uint256 public pendingEthForSPX; // ETH accumulated waiting to reach buy threshold
    uint256 public pendingEthForBurn; // ETH from NFT sales waiting to be burned
    uint256 public spxBuyThreshold = 1 ether; // Minimum ETH to accumulate before buying SPX (default 1 ETH)
    uint256 public spxBuyCount; // Number of times SPX has been purchased

    struct SPXPosition {
        uint256 ethAmount; // ETH spent on this position
        uint256 spxAmount; // SPX tokens received
        uint256 timestamp; // When the position was opened
        uint256 blockNumber; // Block number of purchase
        bool closed; // Whether the position has been closed/sold
        uint256 closedAt; // Timestamp when position was closed
        uint256 soldForEth; // ETH received when sold
    }

    SPXPosition[] public spxPositions; // Array of all SPX positions
    uint256 public openPositionCount; // Number of open (unsold) positions

    // Aeon NFT tracking
    struct AeonNFTListing {
        uint256 tokenId; // Aeon NFT token ID
        uint256 purchasePrice; // ETH spent to buy the NFT
        uint256 listPrice; // ETH listed for (purchasePrice * 1.2)
        uint256 listedAt; // Timestamp when listed
        bool sold; // Whether the NFT has been sold
        uint256 soldAt; // Timestamp when sold
    }

    AeonNFTListing[] public aeonListings; // Array of all Aeon NFT listings
    uint256 public aeonMarkupPercent = 20; // Markup percentage for listings (default 20%)

    // Processing flag
    bool private isProcessing;

    // Auto-collect threshold
    uint256 public autoCollectThreshold = 0.01 ether; // Collect fees when accumulated ETH fees exceed this

    /* ═══════════════════════════════════════════════════ */
    /*                    CUSTOM EVENTS                    */
    /* ═══════════════════════════════════════════════════ */

    event TradingEnabled();
    event TaxCollected(uint256 totalEth, uint256 nftAmount, uint256 spxAmount);
    event SPXPurchased(uint256 ethAmount, uint256 spxReceived, uint256 totalSPXPurchased);
    event SPXSold(uint256 spxAmount, uint256 ethReceived, uint256 profit);
    event AutoCollectThresholdUpdated(uint256 newThreshold);
    event ProfitThresholdUpdated(uint256 newThreshold);
    event SPXBuyThresholdUpdated(uint256 newThreshold);
    event PendingEthAccumulated(uint256 amount, uint256 totalPending);
    event SPXPositionOpened(uint256 indexed positionId, uint256 ethAmount, uint256 spxAmount);
    event SPXPositionClosed(uint256 indexed positionId, uint256 spxAmount, uint256 ethReceived, uint256 profit);
    event NFTTradeExecuted(address indexed target, uint256 value, bytes data, bool success);
    event AeonNFTListed(uint256 indexed listingId, uint256 indexed tokenId, uint256 purchasePrice, uint256 listPrice);
    event AeonNFTSold(uint256 indexed listingId, uint256 indexed tokenId, uint256 salePrice, uint256 tokensBurned);
    event AeonMarkupPercentUpdated(uint256 newPercent);

    /* ═══════════════════════════════════════════════════ */
    /*                    CUSTOM ERRORS                    */
    /* ═══════════════════════════════════════════════════ */

    error TradingNotEnabled();
    error TradingAlreadyEnabled();
    error TransferFailed();

    constructor(address _teamWallet, string memory _tokenName, string memory _tokenSymbol) {
        teamWallet = _teamWallet;
        tokenName = _tokenName;
        tokenSymbol = _tokenSymbol;
        _initializeOwner(msg.sender);

        // Mint entire supply to contract
        _mint(address(this), MAX_SUPPLY);

        // Approve Seaport to transfer Aeon NFTs once (saves gas on every purchase)
        IERC721(AEON_COLLECTION).setApprovalForAll(SEAPORT, true);
    }

    function name() public view override returns (string memory) {
        return tokenName;
    }

    function symbol() public view override returns (string memory) {
        return tokenSymbol;
    }

    /// @notice Override _beforeTokenTransfer hook to auto-collect fees on swaps
    /// @dev Solady ERC20 uses _beforeTokenTransfer instead of _transfer
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
        // Skip fee collection if:
        // - Loading initial liquidity
        // - Already processing fees
        // - Pool not initialized
        // - Transfer is from/to this contract (during fee collection)
        if (!loadingLiquidity && !isProcessing && poolInitialized && from != address(this) && to != address(this)) {
            // Check if accumulated fees exceed threshold
            (uint256 ethFees,) = getAccumulatedFees();
            if (ethFees >= autoCollectThreshold) {
                // Try to collect fees (non-reentrant, will fail silently if already processing)
                try this.collectFees() {} catch {}
            }
        }

        super._beforeTokenTransfer(from, to, amount);
    }

    /// @notice Enable trading by loading initial liquidity into V4 pool
    /// @dev Can only be called once by owner. Caller must send ETH for the initial liquidity (amount0Max).
    function enableTrading() external payable onlyOwner {
        if (poolInitialized) revert TradingAlreadyEnabled();

        loadingLiquidity = true;

        // Create the pool with ETH (currency0) and TOKEN (currency1)
        Currency currency0 = Currency.wrap(address(0)); // ETH
        Currency currency1 = Currency.wrap(address(this)); // TOKEN

        uint24 lpFee = 100000; // 10% fee
        int24 tickSpacing = 60;

        uint256 token0Amount = 1; // 1 wei
        uint256 token1Amount = MAX_SUPPLY; // 1B TOKEN

        // Starting price
        uint160 startingPrice = 501082896750095888663770159906816;

        tickLower = TickMath.minUsableTick(tickSpacing);
        tickUpper = int24(175020);

        PoolKey memory key = PoolKey(currency0, currency1, lpFee, tickSpacing, IHooks(address(0)));
        bytes memory hookData = new bytes(0);

        // Store pool information
        poolKey = key;
        poolId = key.toId();

        uint128 liquidity = 158372218983990412488087;

        uint256 amount0Max = token0Amount + 1 wei;
        uint256 amount1Max = token1Amount + 1 wei;

        (bytes memory actions, bytes[] memory mintParams) =
            _mintLiquidityParams(key, tickLower, tickUpper, liquidity, amount0Max, amount1Max, address(this), hookData);

        bytes[] memory params = new bytes[](2);

        params[0] = abi.encodeWithSelector(POSM.initializePool.selector, key, startingPrice, hookData);

        params[1] = abi.encodeWithSelector(
            POSM.modifyLiquidities.selector, abi.encode(actions, mintParams), block.timestamp + 60
        );

        uint256 valueToPass = amount0Max;
        require(msg.value >= valueToPass, "Insufficient ETH sent");

        // Approve Permit2 to spend our tokens
        _approve(address(this), address(PERMIT2), type(uint256).max);
        PERMIT2.approve(address(this), address(POSM), type(uint160).max, type(uint48).max);

        // Get the next token ID before minting
        positionTokenId = POSM.nextTokenId();

        POSM.multicall{value: valueToPass}(params);

        poolInitialized = true;
        tradingEnabled = true;
        loadingLiquidity = false;

        emit TradingEnabled();
    }

    /// @notice Creates parameters for minting liquidity in Uniswap V4
    function _mintLiquidityParams(
        PoolKey memory key,
        int24 _tickLower,
        int24 _tickUpper,
        uint128 liquidity,
        uint256 amount0Max,
        uint256 amount1Max,
        address recipient,
        bytes memory hookData
    ) internal pure returns (bytes memory, bytes[] memory) {
        bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));

        bytes[] memory params = new bytes[](2);
        params[0] = abi.encode(key, _tickLower, _tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData);
        params[1] = abi.encode(key.currency0, key.currency1);
        return (actions, params);
    }

    /// @notice Swap ETH for SPX6900 using Uniswap V2 (position-based)
    function _swapEthForSPX(uint256 ethAmount) internal {
        if (ethAmount == 0) return;

        address[] memory path = new address[](2);
        path[0] = UNISWAP_ROUTER.WETH();
        path[1] = SPX6900;

        uint256 spxBalanceBefore = IERC20(SPX6900).balanceOf(address(this));

        UNISWAP_ROUTER.swapExactETHForTokensSupportingFeeOnTransferTokens{value: ethAmount}(
            0, // Accept any amount of SPX6900
            path,
            address(this),
            block.timestamp + 300
        );

        uint256 spxReceived = IERC20(SPX6900).balanceOf(address(this)) - spxBalanceBefore;

        // Track cumulative totals
        totalEthForSPX += ethAmount;
        totalSPXPurchased += spxReceived;

        // Update position tracking
        spxHoldingBalance += spxReceived;
        spxCostBasis += ethAmount;

        // Create new position entry
        spxPositions.push(
            SPXPosition({
                ethAmount: ethAmount,
                spxAmount: spxReceived,
                timestamp: block.timestamp,
                blockNumber: block.number,
                closed: false,
                closedAt: 0,
                soldForEth: 0
            })
        );

        spxBuyCount++;
        openPositionCount++;

        emit SPXPurchased(ethAmount, spxReceived, totalSPXPurchased);
        emit SPXPositionOpened(spxPositions.length - 1, ethAmount, spxReceived);
    }

    /// @notice Get accumulated fees from our LP position
    /// @return fees0 ETH fees accumulated
    /// @return fees1 Token fees accumulated
    function getAccumulatedFees() public view returns (uint256 fees0, uint256 fees1) {
        if (!poolInitialized) return (0, 0);

        // Get pool manager
        IPoolManager poolManager = POSM.poolManager();

        // Get position info - the position is owned by POSM with positionTokenId as salt
        (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) =
            poolManager.getPositionInfo(poolId, address(POSM), tickLower, tickUpper, bytes32(positionTokenId));

        // Get current fee growth inside the position range
        (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
            poolManager.getFeeGrowthInside(poolId, tickLower, tickUpper);

        // Calculate fees owed
        fees0 = ((feeGrowthInside0X128 - feeGrowthInside0LastX128) * liquidity) / (1 << 128);
        fees1 = ((feeGrowthInside1X128 - feeGrowthInside1LastX128) * liquidity) / (1 << 128);

        return (fees0, fees1);
    }

    /// @notice Collect accumulated LP fees from Uniswap V4 pool
    /// @dev Anyone can call this to trigger fee collection and distribution
    function collectFees() external nonReentrant returns (uint256 totalEth) {
        require(poolInitialized, "Pool not initialized");
        require(!isProcessing, "Already processing");

        isProcessing = true;

        // Check accumulated fees
        (uint256 ethFees, uint256 tokenFees) = getAccumulatedFees();

        // If no fees, return early
        if (ethFees == 0 && tokenFees == 0) {
            isProcessing = false;
            return 0;
        }

        // Collect fees by decreasing 0 liquidity (fee collection without removing liquidity)
        bytes memory actions = abi.encodePacked(uint8(Actions.DECREASE_LIQUIDITY), uint8(Actions.TAKE_PAIR));

        bytes[] memory params = new bytes[](2);

        // DECREASE_LIQUIDITY with 0 liquidity to collect fees only
        params[0] = abi.encode(
            positionTokenId,
            0, // liquidityDelta = 0
            0, // amount0Min
            0, // amount1Min
            "" // hookData
        );

        // TAKE_PAIR - transfer collected fees to this contract
        params[1] = abi.encode(
            poolKey.currency0, // ETH
            poolKey.currency1, // Token
            address(this) // recipient
        );

        // Track ETH balance before operations
        uint256 ethBalanceBefore = address(this).balance;

        // Execute fee collection
        POSM.modifyLiquidities(abi.encode(actions, params), block.timestamp + 60);

        // Now swap ALL collected token fees to ETH
        if (tokenFees > 0) {
            uint256 tokenBalance = balanceOf(address(this));
            if (tokenBalance > 0) {
                _swapTokensForEth(tokenBalance);
            }
        }

        // Calculate total ETH collected (including from swapping tokens)
        totalEth = address(this).balance - ethBalanceBefore;

        // Distribute total ETH (50% for NFTs, 50% for SPX6900)
        if (totalEth > 0) {
            uint256 nftAmount = (totalEth * 50) / 100; // 50% for buying NFTs
            uint256 spxAmount = (totalEth * 50) / 100; // 50% for SPX6900

            // 1. Send 50% to airdrop wallet (for buying NFTs)
            if (nftAmount > 0) {
                totalEthForNFTs += nftAmount;
            }

            // 2. Accumulate ETH for SPX6900 until threshold is reached
            if (spxAmount > 0) {
                pendingEthForSPX += spxAmount;
                emit PendingEthAccumulated(spxAmount, pendingEthForSPX);

                // Check if we've reached the buy threshold
                if (pendingEthForSPX >= spxBuyThreshold) {
                    uint256 amountToBuy = pendingEthForSPX;
                    pendingEthForSPX = 0;
                    _swapEthForSPX(amountToBuy);
                }
            }
        }

        isProcessing = false;
        emit TaxCollected(totalEth, totalEth / 2, totalEth / 2);

        return totalEth;
    }

    /// @notice Swap tokens for ETH using V4 pool
    function _swapTokensForEth(uint256 tokenAmount) internal {
        if (tokenAmount == 0) return;

        // Encode the Universal Router command
        bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP));
        bytes[] memory inputs = new bytes[](1);

        // Encode V4Router actions
        bytes memory actions =
            abi.encodePacked(uint8(Actions.SWAP_EXACT_IN_SINGLE), uint8(Actions.SETTLE_ALL), uint8(Actions.TAKE_ALL));

        // Prepare parameters for each action
        bytes[] memory params = new bytes[](3);
        params[0] = abi.encode(
            IV4Router.ExactInputSingleParams({
                poolKey: poolKey,
                zeroForOne: false, // TOKEN (currency1) -> ETH (currency0)
                amountIn: uint128(tokenAmount),
                amountOutMinimum: 1, // Accept any amount
                hookData: ""
            })
        );
        params[1] = abi.encode(poolKey.currency1, tokenAmount);
        params[2] = abi.encode(poolKey.currency0, 1);

        // Combine actions and params into inputs
        inputs[0] = abi.encode(actions, params);

        // Approve Permit2 to spend tokens (if not already approved)
        if (allowance(address(this), address(PERMIT2)) < tokenAmount) {
            _approve(address(this), address(PERMIT2), type(uint256).max);
        }

        // Approve UniversalRouter via Permit2 (for V4 swaps)
        PERMIT2.approve(address(this), address(UNIVERSAL_ROUTER), type(uint160).max, type(uint48).max);

        // Execute the swap
        UNIVERSAL_ROUTER.execute(commands, inputs, block.timestamp + 300);
    }

    /// @notice Swap ETH for tokens using V4 pool (for burning)
    function _swapEthForTokens(uint256 ethAmount) internal {
        if (ethAmount == 0) return;

        // Encode the Universal Router command
        bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP));
        bytes[] memory inputs = new bytes[](1);

        // Encode V4Router actions
        bytes memory actions =
            abi.encodePacked(uint8(Actions.SWAP_EXACT_IN_SINGLE), uint8(Actions.SETTLE_ALL), uint8(Actions.TAKE_ALL));

        // Prepare parameters for each action
        bytes[] memory params = new bytes[](3);
        params[0] = abi.encode(
            IV4Router.ExactInputSingleParams({
                poolKey: poolKey,
                zeroForOne: true, // ETH (currency0) -> TOKEN (currency1)
                amountIn: uint128(ethAmount),
                amountOutMinimum: 1, // Accept any amount of tokens
                hookData: ""
            })
        );
        params[1] = abi.encode(poolKey.currency0, ethAmount);
        params[2] = abi.encode(poolKey.currency1, 1);

        // Combine actions and params into inputs
        inputs[0] = abi.encode(actions, params);

        // Execute the swap with ETH
        UNIVERSAL_ROUTER.execute{value: ethAmount}(commands, inputs, block.timestamp + 300);
    }

    /// @notice Close/sell a specific SPX position - ONLY SELLS PROFITS (owner only)
    /// @dev Calculates profit portion of SPX and sells only that. The remaining SPX (principal)
    ///      stays in the contract as a long-term hold (untracked after position closes).
    /// @param positionId The ID of the position to close
    function sellSPXPosition(uint256 positionId) external onlyOwner nonReentrant {
        require(positionId < spxPositions.length, "Invalid position ID");
        SPXPosition storage position = spxPositions[positionId];
        require(!position.closed, "Position already closed");

        uint256 spxBalance = IERC20(SPX6900).balanceOf(address(this));
        require(spxBalance >= position.spxAmount, "Insufficient SPX balance");

        // Get current value of the entire position
        address[] memory path = new address[](2);
        path[0] = SPX6900;
        path[1] = UNISWAP_ROUTER.WETH();

        uint256[] memory amounts = UNISWAP_ROUTER.getAmountsOut(position.spxAmount, path);
        uint256 currentValue = amounts[1]; // Total ETH value

        // Calculate profit
        require(currentValue > position.ethAmount, "Position not in profit");
        uint256 profitEth = currentValue - position.ethAmount;

        // Calculate what portion of SPX represents the profit
        // profitEth / currentValue = profitPortion
        // profitSpxAmount = position.spxAmount * profitPortion
        uint256 profitSpxAmount = (position.spxAmount * profitEth) / currentValue;

        require(profitSpxAmount > 0, "Profit amount too small");
        require(profitSpxAmount <= position.spxAmount, "Invalid profit calculation");

        // Swap ONLY the profit portion of SPX for ETH
        uint256 ethBalanceBefore = address(this).balance;

        SafeTransferLib.safeApprove(SPX6900, address(UNISWAP_ROUTER), profitSpxAmount);

        UNISWAP_ROUTER.swapExactTokensForETHSupportingFeeOnTransferTokens(
            profitSpxAmount,
            0, // Accept any amount of ETH
            path,
            address(this),
            block.timestamp + 300
        );

        uint256 ethReceived = address(this).balance - ethBalanceBefore;

        // Update position tracking - remove ENTIRE position from holdings
        spxHoldingBalance -= position.spxAmount;
        spxCostBasis -= position.ethAmount;
        openPositionCount--;

        // Mark position as closed
        position.closed = true;
        position.closedAt = block.timestamp;
        position.soldForEth = ethReceived; // Only the profit ETH

        emit SPXSold(profitSpxAmount, ethReceived, ethReceived); // Sold amount, received, profit
        emit SPXPositionClosed(positionId, profitSpxAmount, ethReceived, ethReceived);
    }

    /// @notice Sell all open SPX positions (owner only)
    function sellAllSPXPositions() external onlyOwner nonReentrant {
        require(openPositionCount > 0, "No open positions");

        // Collect all position IDs to close
        uint256[] memory positionsToClose = new uint256[](openPositionCount);
        uint256 count = 0;

        for (uint256 i = 0; i < spxPositions.length; i++) {
            if (!spxPositions[i].closed) {
                positionsToClose[count] = i;
                count++;
            }
        }

        // Close all positions
        for (uint256 i = 0; i < count; i++) {
            this.sellSPXPosition(positionsToClose[i]);
        }
    }

    /// @notice Update the profit threshold for auto-selling
    /// @param newThreshold New threshold percentage (e.g., 20 for 20%)
    function setProfitThreshold(uint256 newThreshold) external onlyOwner {
        require(newThreshold > 0 && newThreshold <= 100, "Invalid threshold");
        profitThreshold = newThreshold;
        emit ProfitThresholdUpdated(newThreshold);
    }

    /// @notice Execute an NFT trade or marketplace interaction
    /// @param target The address to call (NFT marketplace, contract, etc.)
    /// @param value Amount of ETH to send with the call
    /// @param data Calldata to execute
    /// @return success Whether the call succeeded
    /// @return returnData Data returned from the call
    function tradeNFT(address target, uint256 value, bytes calldata data)
        external
        onlyOwner
        nonReentrant
        returns (bool success, bytes memory returnData)
    {
        require(target != address(0), "Invalid target address");
        require(address(this).balance >= value, "Insufficient ETH balance");

        // Execute the call
        (success, returnData) = target.call{value: value}(data);

        emit NFTTradeExecuted(target, value, data, success);

        return (success, returnData);
    }

    /// @notice Execute multiple NFT trades or marketplace interactions in a single transaction
    /// @dev All operations are atomic - if any call fails, the entire transaction reverts
    /// @param targets Array of addresses to call
    /// @param values Array of ETH amounts to send with each call
    /// @param dataArray Array of calldata to execute
    function batchTradeNFT(address[] calldata targets, uint256[] calldata values, bytes[] calldata dataArray)
        external
        onlyOwner
        nonReentrant
    {
        require(targets.length == values.length && values.length == dataArray.length, "Array length mismatch");
        require(targets.length > 0, "Empty arrays");

        uint256 totalValue = 0;
        for (uint256 i = 0; i < values.length; i++) {
            totalValue += values[i];
        }
        require(address(this).balance >= totalValue, "Insufficient ETH balance");

        for (uint256 i = 0; i < targets.length; i++) {
            require(targets[i] != address(0), "Invalid target address");

            // Execute the call - revert if it fails
            (bool success,) = targets[i].call{value: values[i]}(dataArray[i]);
            require(success, "Batch operation failed");

            emit NFTTradeExecuted(targets[i], values[i], dataArray[i], success);
        }
    }

    /// @notice Update the auto-collect threshold
    /// @param newThreshold New threshold in wei
    function setAutoCollectThreshold(uint256 newThreshold) external onlyOwner {
        autoCollectThreshold = newThreshold;
        emit AutoCollectThresholdUpdated(newThreshold);
    }

    /// @notice Update the SPX buy threshold
    /// @param newThreshold New threshold in wei (e.g., 1 ether)
    function setSPXBuyThreshold(uint256 newThreshold) external onlyOwner {
        require(newThreshold > 0, "Threshold must be > 0");
        spxBuyThreshold = newThreshold;
        emit SPXBuyThresholdUpdated(newThreshold);
    }

    /// @notice Manually execute SPX buy with pending ETH (owner only)
    /// @dev Bypasses threshold check and buys with whatever is pending
    function executePendingSPXBuy() external onlyOwner nonReentrant {
        require(pendingEthForSPX > 0, "No pending ETH");
        uint256 amountToBuy = pendingEthForSPX;
        pendingEthForSPX = 0;
        _swapEthForSPX(amountToBuy);
    }

    /// @notice Get total number of SPX positions
    /// @return Total number of positions opened
    function getSPXPositionCount() external view returns (uint256) {
        return spxPositions.length;
    }

    /// @notice Get a specific SPX position by index
    /// @param index Position index
    /// @return ethAmount ETH spent on position
    /// @return spxAmount SPX tokens received
    /// @return timestamp When position was opened
    /// @return blockNumber Block number of purchase
    function getSPXPosition(uint256 index)
        external
        view
        returns (uint256 ethAmount, uint256 spxAmount, uint256 timestamp, uint256 blockNumber)
    {
        require(index < spxPositions.length, "Invalid index");
        SPXPosition memory pos = spxPositions[index];
        return (pos.ethAmount, pos.spxAmount, pos.timestamp, pos.blockNumber);
    }

    /// @notice Get all SPX positions
    /// @return Array of all positions
    function getAllSPXPositions() external view returns (SPXPosition[] memory) {
        return spxPositions;
    }

    /// @notice Get pending ETH status
    /// @return pending Current pending ETH amount
    /// @return threshold SPX buy threshold
    /// @return percentOfThreshold Percentage of threshold reached (100 = 100%)
    function getPendingETHStatus()
        external
        view
        returns (uint256 pending, uint256 threshold, uint256 percentOfThreshold)
    {
        pending = pendingEthForSPX;
        threshold = spxBuyThreshold;
        percentOfThreshold = threshold > 0 ? (pending * 100) / threshold : 0;
    }

    /// @notice Get available ETH for NFT purchases
    /// @dev NFT sale proceeds are reserved for burning and NOT available for buying more NFTs
    /// @return available ETH currently available for buying NFTs (balance - pendingEthForSPX - pendingEthForBurn)
    /// @return totalAllocated ETH currently locked in unsold Aeon NFT inventory
    function getAvailableNFTEth() external view returns (uint256 available, uint256 totalAllocated) {
        // Calculate reserved ETH (for SPX purchases and burning)
        uint256 reservedEth = pendingEthForSPX + pendingEthForBurn;

        // Available = contract balance minus all reserved ETH
        // NFT sale proceeds are reserved for burning ONLY
        available = address(this).balance > reservedEth ? address(this).balance - reservedEth : 0;

        // TotalAllocated = ETH locked in unsold Aeon NFTs (inventory cost basis)
        totalAllocated = aeonNFTLockedEth;
    }

    /// @notice Get all open (unsold) position IDs
    /// @return Array of open position IDs
    function getOpenPositions() external view returns (uint256[] memory) {
        uint256[] memory openPositions = new uint256[](openPositionCount);
        uint256 count = 0;

        for (uint256 i = 0; i < spxPositions.length; i++) {
            if (!spxPositions[i].closed) {
                openPositions[count] = i;
                count++;
            }
        }

        return openPositions;
    }

    /// @notice Get all closed (sold) position IDs
    /// @return Array of closed position IDs
    function getClosedPositions() external view returns (uint256[] memory) {
        uint256 closedCount = spxPositions.length - openPositionCount;
        uint256[] memory closedPositions = new uint256[](closedCount);
        uint256 count = 0;

        for (uint256 i = 0; i < spxPositions.length; i++) {
            if (spxPositions[i].closed) {
                closedPositions[count] = i;
                count++;
            }
        }

        return closedPositions;
    }

    /// @notice Calculate profit/loss for a specific position
    /// @param positionId Position ID
    /// @return currentValue Current value in ETH
    /// @return profitLoss Profit or loss in ETH
    /// @return profitPercent Profit percentage (can be negative)
    /// @return isProfitable Whether position meets profit threshold
    function getPositionProfitLoss(uint256 positionId)
        external
        view
        returns (uint256 currentValue, int256 profitLoss, int256 profitPercent, bool isProfitable)
    {
        require(positionId < spxPositions.length, "Invalid position ID");
        SPXPosition memory position = spxPositions[positionId];

        if (position.closed) {
            // For closed positions, use the actual sold amount
            currentValue = position.soldForEth;
            profitLoss = int256(position.soldForEth) - int256(position.ethAmount);
        } else {
            // For open positions, get current market value
            address[] memory path = new address[](2);
            path[0] = SPX6900;
            path[1] = UNISWAP_ROUTER.WETH();

            try UNISWAP_ROUTER.getAmountsOut(position.spxAmount, path) returns (uint256[] memory amounts) {
                currentValue = amounts[1]; // ETH we'd receive
                profitLoss = int256(currentValue) - int256(position.ethAmount);
            } catch {
                currentValue = 0;
                profitLoss = 0;
            }
        }

        if (position.ethAmount > 0) {
            profitPercent = (profitLoss * 100) / int256(position.ethAmount);
        } else {
            profitPercent = 0;
        }

        isProfitable = profitPercent >= int256(profitThreshold);
    }

    /// @notice Check if a specific position should be sold
    /// @param positionId Position ID
    /// @return Whether position meets profit threshold
    function shouldSellPosition(uint256 positionId) external view returns (bool) {
        (,,, bool isProfitable) = this.getPositionProfitLoss(positionId);
        return isProfitable;
    }

    /* ═══════════════════════════════════════════════════ */
    /*                  AEON NFT FUNCTIONS                 */
    /* ═══════════════════════════════════════════════════ */

    /// @notice Record a new Aeon NFT listing
    /// @param tokenId The Aeon NFT token ID
    /// @param purchasePrice ETH spent to buy the NFT
    /// @param listPrice ETH listed for (should be purchasePrice * 1.2)
    function recordAeonListing(uint256 tokenId, uint256 purchasePrice, uint256 listPrice) external onlyOwner {
        aeonListings.push(
            AeonNFTListing({
                tokenId: tokenId,
                purchasePrice: purchasePrice,
                listPrice: listPrice,
                listedAt: block.timestamp,
                sold: false,
                soldAt: 0
            })
        );

        // Track ETH locked in this NFT
        aeonNFTLockedEth += purchasePrice;

        emit AeonNFTListed(aeonListings.length - 1, tokenId, purchasePrice, listPrice);
    }

    /// @notice Check for Aeon NFT sales and execute buy/burn for sold NFTs
    /// @dev Loops through all unsold listings, checks ownership, and processes sales
    function processAeonSales() external onlyOwner nonReentrant {
        for (uint256 i = 0; i < aeonListings.length; i++) {
            AeonNFTListing storage listing = aeonListings[i];

            // Skip if already marked as sold
            if (listing.sold) continue;

            // Check if we still own this NFT
            address currentOwner = IERC721(AEON_COLLECTION).ownerOf(listing.tokenId);

            // If we don't own it anymore, it was sold
            if (currentOwner != address(this)) {
                // Mark as sold
                listing.sold = true;
                listing.soldAt = block.timestamp;

                // Release locked ETH (NFT is no longer in inventory)
                if (aeonNFTLockedEth >= listing.purchasePrice) {
                    aeonNFTLockedEth -= listing.purchasePrice;
                } else {
                    aeonNFTLockedEth = 0; // Safety: prevent underflow
                }

                // Calculate actual ETH received after OpenSea fee (2.5%)
                // We list at listPrice, but receive listPrice * 97.5%
                uint256 actualProceeds = (listing.listPrice * 975) / 1000;

                // Reserve this ETH for burning only (not available for NFT purchases)
                pendingEthForBurn += actualProceeds;

                // Buy and burn tokens with the actual sale proceeds
                uint256 tokensBurned = _buyAndBurnTokens(actualProceeds);

                emit AeonNFTSold(i, listing.tokenId, actualProceeds, tokensBurned);
            }
        }
    }

    /// @notice Internal function to buy tokens with ETH and burn them
    /// @dev Uses Uniswap V4 pool to swap ETH for tokens
    /// @param ethAmount Amount of ETH to use for buying tokens
    /// @return tokensBurned Amount of tokens burned
    function _buyAndBurnTokens(uint256 ethAmount) internal returns (uint256 tokensBurned) {
        require(ethAmount > 0, "ETH amount must be > 0");

        // Use available balance if slightly less than requested (safety margin)
        uint256 availableBalance = address(this).balance;
        if (availableBalance < ethAmount) {
            // Only proceed if shortage is small (within 1%)
            if (ethAmount - availableBalance > ethAmount / 100) {
                return 0; // Skip if shortage is > 1%
            }
            ethAmount = availableBalance; // Use what we have
        }

        uint256 balanceBefore = balanceOf(address(this));

        // Swap ETH for tokens using Uniswap V4
        _swapEthForTokens(ethAmount);

        uint256 balanceAfter = balanceOf(address(this));
        tokensBurned = balanceAfter - balanceBefore;

        // Burn the tokens by transferring to dead address
        if (tokensBurned > 0) {
            _transfer(address(this), DEAD_ADDRESS, tokensBurned);
        }

        // Deduct the ETH used from pending burn amount
        if (pendingEthForBurn >= ethAmount) {
            pendingEthForBurn -= ethAmount;
        } else {
            pendingEthForBurn = 0; // Safety: prevent underflow
        }

        return tokensBurned;
    }

    /// @notice Get total number of Aeon listings
    function getAeonListingCount() external view returns (uint256) {
        return aeonListings.length;
    }

    /// @notice Get a specific Aeon listing by index
    function getAeonListing(uint256 index)
        external
        view
        returns (uint256 tokenId, uint256 purchasePrice, uint256 listPrice, uint256 listedAt, bool sold, uint256 soldAt)
    {
        require(index < aeonListings.length, "Invalid index");
        AeonNFTListing memory listing = aeonListings[index];
        return
            (listing.tokenId, listing.purchasePrice, listing.listPrice, listing.listedAt, listing.sold, listing.soldAt);
    }

    /// @notice Get all unsold Aeon listings
    function getUnsoldAeonListings() external view returns (uint256[] memory) {
        uint256 unsoldCount = 0;

        // Count unsold listings
        for (uint256 i = 0; i < aeonListings.length; i++) {
            if (!aeonListings[i].sold) {
                unsoldCount++;
            }
        }

        // Build array of unsold listing indices
        uint256[] memory unsoldIndices = new uint256[](unsoldCount);
        uint256 currentIndex = 0;

        for (uint256 i = 0; i < aeonListings.length; i++) {
            if (!aeonListings[i].sold) {
                unsoldIndices[currentIndex] = i;
                currentIndex++;
            }
        }

        return unsoldIndices;
    }

    /// @notice Update the Aeon markup percentage
    /// @param newPercent New markup percentage (e.g., 20 for 20%)
    function setAeonMarkupPercent(uint256 newPercent) external onlyOwner {
        require(newPercent > 0 && newPercent <= 100, "Invalid markup percent");
        aeonMarkupPercent = newPercent;
        emit AeonMarkupPercentUpdated(newPercent);
    }

    /* ═══════════════════════════════════════════════════ */
    /*                  EIP-1271 SIGNATURE                 */
    /* ═══════════════════════════════════════════════════ */

    /// @notice EIP-1271 signature validation for Seaport listings
    /// @dev Validates that the signature was created by the contract owner
    /// @param hash Hash of the data being signed (Seaport order hash)
    /// @param signature Signature bytes created by the owner
    /// @return magicValue EIP-1271 magic value (0x1626ba7e) if valid, 0xffffffff if invalid
    function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue) {
        // Verify that the signature was created by the owner
        bool isValid = SignatureCheckerLib.isValidSignatureNow(owner(), hash, signature);

        // Return EIP-1271 magic value if valid
        return isValid ? bytes4(0x1626ba7e) : bytes4(0xffffffff);
    }

    /// @notice Allows the contract to receive ETH
    receive() external payable {}
}

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
}

interface IERC721 {
    function ownerOf(uint256 tokenId) external view returns (address);
    function setApprovalForAll(address operator, bool approved) external;
}
"
    },
    "dependencies/solady-0.1.26/src/tokens/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Simple ERC20 + EIP-2612 implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
///
/// @dev Note:
/// - The ERC20 standard allows minting and transferring to and from the zero address,
///   minting and transferring zero tokens, as well as self-approvals.
///   For performance, this implementation WILL NOT revert for such actions.
///   Please add any checks with overrides if desired.
/// - The `permit` function uses the ecrecover precompile (0x1).
///
/// If you are overriding:
/// - NEVER violate the ERC20 invariant:
///   the total sum of all balances must be equal to `totalSupply()`.
/// - Check that the overridden function is actually used in the function you want to
///   change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC20 {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The total supply has overflowed.
    error TotalSupplyOverflow();

    /// @dev The allowance has overflowed.
    error AllowanceOverflow();

    /// @dev The allowance has underflowed.
    error AllowanceUnderflow();

    /// @dev Insufficient balance.
    error InsufficientBalance();

    /// @dev Insufficient allowance.
    error InsufficientAllowance();

    /// @dev The permit is invalid.
    error InvalidPermit();

    /// @dev The permit has expired.
    error PermitExpired();

    /// @dev The allowance of Permit2 is fixed at infinity.
    error Permit2AllowanceIsFixedAtInfinity();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
    event Transfer(address indexed from, address indexed to, uint256 amount);

    /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
    uint256 private constant _TRANSFER_EVENT_SIGNATURE =
        0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

    /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
    uint256 private constant _APPROVAL_EVENT_SIGNATURE =
        0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STORAGE                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The storage slot for the total supply.
    uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c;

    /// @dev The balance slot of `owner` is given by:
    /// ```
    ///     mstore(0x0c, _BALANCE_SLOT_SEED)
    ///     mstore(0x00, owner)
    ///     let balanceSlot := keccak256(0x0c, 0x20)
    /// ```
    uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2;

    /// @dev The allowance slot of (`owner`, `spender`) is given by:
    /// ```
    ///     mstore(0x20, spender)
    ///     mstore(0x0c, _ALLOWANCE_SLOT_SEED)
    ///     mstore(0x00, owner)
    ///     let allowanceSlot := keccak256(0x0c, 0x34)
    /// ```
    uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20;

    /// @dev The nonce slot of `owner` is given by:
    /// ```
    ///     mstore(0x0c, _NONCES_SLOT_SEED)
    ///     mstore(0x00, owner)
    ///     let nonceSlot := keccak256(0x0c, 0x20)
    /// ```
    uint256 private constant _NONCES_SLOT_SEED = 0x38377508;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`.
    uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901;

    /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
    bytes32 private constant _DOMAIN_TYPEHASH =
        0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;

    /// @dev `keccak256("1")`.
    /// If you need to use a different version, override `_versionHash`.
    bytes32 private constant _DEFAULT_VERSION_HASH =
        0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;

    /// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
    bytes32 private constant _PERMIT_TYPEHASH =
        0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

    /// @dev The canonical Permit2 address.
    /// For signature-based allowance granting for single transaction ERC20 `transferFrom`.
    /// Enabled by default. To disable, override `_givePermit2InfiniteAllowance()`.
    /// [Github](https://github.com/Uniswap/permit2)
    /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
    address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ERC20 METADATA                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the name of the token.
    function name() public view virtual returns (string memory);

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

    /// @dev Returns the decimals places of the token.
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           ERC20                            */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the amount of tokens in existence.
    function totalSupply() public view virtual returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := sload(_TOTAL_SUPPLY_SLOT)
        }
    }

    /// @dev Returns the amount of tokens owned by `owner`.
    function balanceOf(address owner) public view virtual returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x0c, _BALANCE_SLOT_SEED)
            mstore(0x00, owner)
            result := sload(keccak256(0x0c, 0x20))
        }
    }

    /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
    function allowance(address owner, address spender)
        public
        view
        virtual
        returns (uint256 result)
    {
        if (_givePermit2InfiniteAllowance()) {
            if (spender == _PERMIT2) return type(uint256).max;
        }
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x20, spender)
            mstore(0x0c, _ALLOWANCE_SLOT_SEED)
            mstore(0x00, owner)
            result := sload(keccak256(0x0c, 0x34))
        }
    }

    /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
    ///
    /// Emits a {Approval} event.
    function approve(address spender, uint256 amount) public virtual returns (bool) {
        if (_givePermit2InfiniteAllowance()) {
            /// @solidity memory-safe-assembly
            assembly {
                // If `spender == _PERMIT2 && amount != type(uint256).max`.
                if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(amount)))) {
                    mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`.
                    revert(0x1c, 0x04)
                }
            }
        }
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the allowance slot and store the amount.
            mstore(0x20, spender)
            mstore(0x0c, _ALLOWANCE_SLOT_SEED)
            mstore(0x00, caller())
            sstore(keccak256(0x0c, 0x34), amount)
            // Emit the {Approval} event.
            mstore(0x00, amount)
            log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
        }
        return true;
    }

    /// @dev Transfer `amount` tokens from the caller to `to`.
    ///
    /// Requirements:
    /// - `from` must at least have `amount`.
    ///
    /// Emits a {Transfer} event.
    function transfer(address to, uint256 amount) public virtual returns (bool) {
        _beforeTokenTransfer(msg.sender, to, amount);
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the balance slot and load its value.
            mstore(0x0c, _BALANCE_SLOT_SEED)
            mstore(0x00, caller())
            let fromBalanceSlot := keccak256(0x0c, 0x20)
            let fromBalance := sload(fromBalanceSlot)
            // Revert if insufficient balance.
            if gt(amount, fromBalance) {
                mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                revert(0x1c, 0x04)
            }
            // Subtract and store the updated balance.
            sstore(fromBalanceSlot, sub(fromBalance, amount))
            // Compute the balance slot of `to`.
            mstore(0x00, to)
            let toBalanceSlot := keccak256(0x0c, 0x20)
            // Add and store the updated balance of `to`.
            // Will not overflow because the sum of all user balances
            // cannot exceed the maximum uint256 value.
            sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
            // Emit the {Transfer} event.
            mstore(0x20, amount)
            log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c)))
        }
        _afterTokenTransfer(msg.sender, to, amount);
        return true;
    }

    /// @dev Transfers `amount` tokens from `from` to `to`.
    ///
    /// Note: Does not update the allowance if it is the maximum uint256 value.
    ///
    /// Requirements:
    /// - `from` must at least have `amount`.
    /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
    ///
    /// Emits a {Transfer} event.
    function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
        _beforeTokenTransfer(from, to, amount);
        // Code duplication is for zero-cost abstraction if possible.
        if (_givePermit2InfiniteAllowance()) {
            /// @solidity memory-safe-assembly
            assembly {
                let from_ := shl(96, from)
                if iszero(eq(caller(), _PERMIT2)) {
                    // Compute the allowance slot and load its value.
                    mstore(0x20, caller())
                    mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
                    let allowanceSlot := keccak256(0x0c, 0x34)
                    let allowance_ := sload(allowanceSlot)
                    // If the allowance is not the maximum uint256 value.
                    if not(allowance_) {
                        // Revert if the amount to be transferred exceeds the allowance.
                        if gt(amount, allowance_) {
                            mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
                            revert(0x1c, 0x04)
                        }
                        // Subtract and store the updated allowance.
                        sstore(allowanceSlot, sub(allowance_, amount))
                    }
                }
                // Compute the balance slot and load its value.
                mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
                let fromBalanceSlot := keccak256(0x0c, 0x20)
                let fromBalance := sload(fromBalanceSlot)
                // Revert if insufficient balance.
                if gt(amount, fromBalance) {
                    mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                    revert(0x1c, 0x04)
                }
                // Subtract and store the updated balance.
                sstore(fromBalanceSlot, sub(fromBalance, amount))
                // Compute the balance slot of `to`.
                mstore(0x00, to)
                let toBalanceSlot := keccak256(0x0c, 0x20)
                // Add and store the updated balance of `to`.
                // Will not overflow because the sum of all user balances
                // cannot exceed the maximum uint256 value.
                sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
                // Emit the {Transfer} event.
                mstore(0x20, amount)
                log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                let from_ := shl(96, from)
                // Compute the allowance slot and load its value.
                mstore(0x20, caller())
                mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
                let allowanceSlot := keccak256(0x0c, 0x34)
                let allowance_ := sload(allowanceSlot)
                // If the allowance is not the maximum uint256 value.
                if not(allowance_) {
                    // Revert if the amount to be transferred exceeds the allowance.
                    if gt(amount, allowance_) {
                        mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
                        revert(0x1c, 0x04)
                    }
                    // Subtract and store the updated allowance.
                    sstore(allowanceSlot, sub(allowance_, amount))
                }
                // Compute the balance slot and load its value.
                mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
                let fromBalanceSlot := keccak256(0x0c, 0x20)
                let fromBalance := sload(fromBalanceSlot)
                // Revert if insufficient balance.
                if gt(amount, fromBalance) {
                    mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
                    revert(0x1c, 0x04)
                }
                // Subtract and store the updated balance.
                sstore(fromBalanceSlot, sub(fromBalance, amount))
                // Compute the balance slot of `to`.
                mstore(0x00, to)
                let toBalanceSlot := keccak256(0x0c, 0x20)
                // Add and store the updated balance of `to`.
                // Will not overflow because the sum of all user balances
                // cannot exceed the maximum uint256 value.
                sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
                // Emit the {Transfer} event.
                mstore(0x20, amount)
                log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
            }
        }
        _afterTokenTransfer(from, to, amount);
        return true;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          EIP-2612                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev For more performance, override to return the constant value
    /// of `keccak256(bytes(name()))` if `name()` will never change.
    function _constantNameHash() internal view virtual returns (bytes32 result) {}

    /// @dev If you need a different value, override this function.
    function _versionHash() internal view virtual returns (bytes32 result) {
        result = _DEFAULT_VERSION_HASH;
    }

    /// @dev For inheriting contracts to increment the nonce.
    function _incrementNonce(address owner) internal virtual {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x0c, _NONCES_SLOT_SEED)
            mstore(0x00, owner)
            let nonceSlot := keccak256(0x0c, 0x20)
            sstore(nonceSlot, add(1, sload(nonceSlot)))
        }
    }

    /// @dev Returns the current nonce for `owner`.
    /// This value is used to compute the signature for EIP-2612 permit.
    function nonces(address owner) public view virtual returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the nonce slot and load its value.
            mstore(0x0c, _NONCES_SLOT_SEED)
            mstore(0x00, owner)
            result := sload(keccak256(0x0c, 0x20))
        }
    }

    /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`,
    /// authorized by a signed approval by `owner`.
    ///
    /// Emits a {Approval} event.
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        if (_givePermit2InfiniteAllowance()) {
            /// @solidity memory-safe-assembly
            assembly {
                // If `spender == _PERMIT2 && value != type(uint256).max`.
                if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(value)))) {
                    mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`.
                    revert(0x1c, 0x04)
                }
            }
        }
        bytes32 nameHash = _constantNameHash();
        //  We simply calculate it on-the-fly to allow for cases where the `name` may change.
        if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
        bytes32 versionHash = _versionHash();
        /// @solidity memory-safe-assembly
        assembly {
            // Revert if the block timestamp is greater than `deadline`.
            if gt(timestamp(), deadline) {
                mstore(0x00, 0x1a15a3cc) // `PermitExpired()`.
                revert(0x1c, 0x04)
            }
            let m := mload(0x40) // Grab the free memory pointer.
            // Clean the upper 96 bits.
            owner := shr(96, shl(96, owner))
            spender := shr(96, shl(96, spender))
            // Compute the nonce slot and load its value.
            mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX)
            mstore(0x00, owner)
            let nonceSlot := keccak256(0x0c, 0x20)
            let nonceValue := sload(nonceSlot)
            // Prepare the domain separator.
            mstore(m, _DOMAIN_TYPEHASH)
            mstore(add(m, 0x20), nameHash)
            mstore(add(m, 0x40), versionHash)
            mstore(add(m, 0x60), chainid())
            mstore(add(m, 0x80), address())
            mstore(0x2e, keccak256(m, 0xa0))
            // Prepare the struct hash.
            mstore(m, _PERMIT_TYPEHASH)
            mstore(add(m, 0x20), owner)
            mstore(add(m, 0x40), spender)
            mstore(add(m, 0x60), value)
            mstore(add(m, 0x80), nonceValue)
            mstore(add(m, 0xa0), deadline)
            mstore(0x4e, keccak256(m, 0xc0))
            // Prepare the ecrecover calldata.
            mstore(0x00, keccak256(0x2c, 0x42))
            mstore(0x20, and(0xff, v))
            mstore(0x40, r)
            mstore(0x60, s)
            let t := staticcall(gas(), 1, 0x00, 0x80, 0x20, 0x20)
            // If the ecrecover fails, the returndatasize will be 0x00,
            // `owner` will be checked if it equals the hash at 0x00,
            // which evaluates to false (i.e. 0), and we will revert.
            // If the ecrecover succeeds, the returndatasize will be 0x20,
            // `owner` will be compared against the returned address at 0x20.
            if iszero(eq(mload(returndatasize()), owner)) {
                mstore(0x00, 0xddafbaef) // `InvalidPermit()`.
                revert(0x1c, 0x04)
            }
            // Increment and store the updated nonce.
            sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds.
            // Compute the allowance slot and store the value.
            // The `owner` is already at slot 0x20.
            mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender))
            sstore(keccak256(0x2c, 0x34), value)
            // Emit the {Approval} event.
            log3(add(m, 0x60), 0

Tags:
ERC20, ERC721, Multisig, Mintable, Burnable, Non-Fungible, Swap, Liquidity, Upgradeable, Multi-Signature, Factory|addr:0xbae643de5557b6081d644e946eb140a4399b6da2|verified:true|block:23535750|tx:0x0e81bf37b18acc499ddfa3648d58e019e9f04349f69d5c429ccad08780ca32aa|first_check:1759994295

Submitted on: 2025-10-09 09:18:16

Comments

Log in to comment.

No comments yet.