PunkStrategyStrategy

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/punkbuy/StrategyPunk.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Ownable} from "solady/auth/Ownable.sol";
import {ERC20} from "solady/tokens/ERC20.sol";
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.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 {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {PositionInfo, PositionInfoLibrary} from "@uniswap/v4-periphery/src/libraries/PositionInfoLibrary.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint128} from "@uniswap/v4-core/src/libraries/FixedPoint128.sol";
import {IUniswapV4Router04} from "v4-router/interfaces/IUniswapV4Router04.sol";
import "./Interfaces.sol";
/*
    ____              __      _____ __             __                   ___ 
   / __ \__  ______  / /__   / ___// /__________ _/ /____  ____ ___  __|_  |
  / /_/ / / / / __ \/ //_/   \__ \/ __/ ___/ __ `/ __/ _ \/ __ `/ / / / __/ 
 / ____/ /_/ / / / / ,<     ___/ / /_/ /  / /_/ / /_/  __/ /_/ / /_/ /____/ 
/_/    \__,_/_/ /_/_/|_|   /____/\__/_/   \__,_/\__/\___/\__, /\__, /       
                                                        /____//____/                                                                                                                                      
*/
contract PunkStrategyStrategy is ERC20, Ownable, ReentrancyGuard {
    using PoolIdLibrary for PoolKey;
    using PositionInfoLibrary for PositionInfo;

    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    /*                      CONSTANTS                      */
    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    IPositionManager private immutable posm;
    IAllowanceTransfer private immutable permit2;
    IUniswapV4Router04 private immutable router;
    address private immutable poolManager;

    uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;

    address public constant PNKSTR_ADDRESS = 0xc50673EDb3A7b94E8CAD8a7d4E0cD68864E33eDF;
    address public constant PNKSTR_HOOK_ADDRESS = 0xfAaad5B731F52cDc9746F2414c823eca9B06E844;
    address public constant DEADADDRESS = 0x000000000000000000000000000000000000dEaD;
    int24 private constant PRIMARY_TOKEN_TICK_SPACING = 60;
    uint24 private constant PRIMARY_TOKEN_POOL_FEE = 0;


    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    /*                      VARIABLES                      */
    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    address public hookAddress;
    bool public routerRestrict = true;
    mapping(address => bool) public listOfRouters;
    mapping(address => bool) internal theList;
    bool public midSwap;
    mapping(bytes32 => uint256) public pnkstrLiquidityTokenIds;
    mapping(uint256 => PoolKey) private registeredPnkstrPoolKeys;
    mapping(uint256 => bool) private registeredPnkstrPoolKeyExists;

    // Hook & accounting
    bool public loadingLiquidity;
    uint256 public currentFees; // accum ETH forwarded by hook into contract (for fee accounting)
    uint256 public totalEthSpentOnPurchases;
    uint256 public totalEthSpentOnLiquidity;
    uint256 public totalPnkPurchased;
    uint256 public totalTokenBurned;

    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    /*                       EVENTS                        */
    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    event HookFeesReceived(uint256 amount);
    event PNKSTRPurchased(uint256 ethSpent, uint256 tokenReceived);
    event LiquidityLoaded(address hook);
    event PnkstrPoolKeyRegistered(uint256 indexed listId, bytes32 indexed poolId);
    event PnkstrPositionInitialized(uint256 indexed listId, bytes32 indexed poolId, uint256 tokenId);
    event FeesConvertedAndBurned(uint256 ethInput, uint256 pnkInput, uint256 primaryTokenBurned);

    event PoolInitialized(address posm, address poolm, address token, address hook);
    event PoolInitFailed(bytes errorData);

    event SwapExecuted(uint256 tokenIn, uint256 tokenOut);


    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    /*                       ERRORS                        */
    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    error OnlyHook();
    error InvalidMultiplier();
    error InsufficientAmount();
    error PurchaseNotFound();
    error NotValidRouter();
    error PositionAlreadyInitialized();
    error PositionNotInitialized();
    error LiquidityTooLow();
    error AmountTooLarge();
    error InvalidPoolKey();
    error InsufficientContractEth();
    error PoolKeyNotRegistered();
    error PoolKeyAlreadyRegistered();
    error PnkstrBalanceTooLow();

    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    /*                     CONSTRUCTOR                     */
    /* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
    constructor(
        address _posm,
        address _permit2,
        address _poolManager,
        address _universalRouter,
        address payable _router
    ) {
        router = IUniswapV4Router04(_router);
        posm = IPositionManager(_posm);
        permit2 = IAllowanceTransfer(_permit2);
        poolManager = _poolManager;

        listOfRouters[address(this)] = true;
        listOfRouters[_posm] = true;
        listOfRouters[_permit2] = true;
        listOfRouters[_router] = true;
        listOfRouters[_universalRouter] = true;
        listOfRouters[DEADADDRESS] = true;

        routerRestrict = true;

        _initializeOwner(msg.sender);

        _mint(address(this), MAX_SUPPLY);
    }

     /* ========================= ERC20 basics ========================= */
    function name() public pure override returns (string memory) { return "PunkStrategyStrategy"; }
    function symbol() public pure override returns (string memory) { return "PSS"; }

    /* ========================= Hook & Owner ========================= */
    function addFees() external payable {
        if (msg.sender != hookAddress && msg.sender != owner()) revert OnlyHook();
        currentFees += msg.value;
        emit HookFeesReceived(msg.value);
    }

    function setMidSwap(bool value) external {
        if (msg.sender != hookAddress) revert OnlyHook();
        midSwap = value;
    }

    function transferEther(
        address _to,
        uint256 _amount
    ) external payable onlyOwner {
        SafeTransferLib.forceSafeTransferETH(_to, _amount);
    }

    function setRouterRestriction(bool _restrict) external onlyOwner {
        routerRestrict = _restrict;
    }

    function addRouter(address _router) external onlyOwner {
        listOfRouters[_router] = true;
    }

    function setlist(address user, bool isEvil) external onlyOwner {
        theList[user] = isEvil;
    }

    function addPoolKey(uint256 listId, uint24 lpFee, int24 tickSpacing, address hooks) external {
        if (registeredPnkstrPoolKeyExists[listId]) revert PoolKeyAlreadyRegistered();
        PoolKey memory poolKey = PoolKey({
            currency0: Currency.wrap(address(0)),
            currency1: Currency.wrap(PNKSTR_ADDRESS),
            fee: lpFee,
            tickSpacing: tickSpacing,
            hooks: IHooks(hooks)
        });

        registeredPnkstrPoolKeys[listId] = poolKey;
        registeredPnkstrPoolKeyExists[listId] = true;

        bytes32 poolId = PoolId.unwrap(poolKey.toId());
        emit PnkstrPoolKeyRegistered(listId, poolId);
    }

    function getPoolInfo(uint256 listId)
        external
        view
        returns (PoolKey memory poolKey, uint256 tokenId)
    {
        bytes32 poolId;
        (poolKey, poolId) = _getRegisteredPoolKey(listId);
        tokenId = pnkstrLiquidityTokenIds[poolId];
    }

    /* ========================= PNKSTR Purchase ========================= */

    function _pnkstrPurchase(uint256 ethAmount) internal returns (uint256, uint256) {
        require(address(this).balance >= ethAmount, "Not enough ETH in contract");
        
        PoolKey memory key = PoolKey(
            Currency.wrap(address(0)),
            Currency.wrap(PNKSTR_ADDRESS),
            10000,
            200,
            IHooks(0x0000000000000000000000000000000000000000)
        );

        BalanceDelta delta = router.swapExactTokensForTokens{value: ethAmount}(
            ethAmount,
            0,
            true,
            key,
            "",
            address(this),
            block.timestamp
        );

        uint256 ethSpent = _abs(delta.amount0());
        uint256 pnkReceived = _abs(delta.amount1());

        totalEthSpentOnPurchases += ethSpent;

        emit PNKSTRPurchased(ethSpent, pnkReceived);

        return (ethSpent, pnkReceived);
    }

    /* ========================= Pool init & liquidity functions ========================= */
    function loadLiquidity(address _hook) external payable onlyOwner {
        require(msg.value > 0, "need some native to fund init");
        hookAddress = _hook;
        _loadLiquidity(_hook);
        emit LiquidityLoaded(_hook);
    }

    function _loadLiquidity(address _hook) internal {
        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 = 0;
        int24 tickSpacing = 60;

        uint256 token0Amount = 1; // 1 wei
        uint256 token1Amount = 1_000_000_000 * 10**18; // 1B TOKEN

        // 12.5e18 ETH = 1_000_000_000e18 TOKEN 
        uint160 startingPrice = 708638228457182841184406864642904;

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

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

        // Hardcoded from LiquidityAmounts.getLiquidityForAmounts
        uint128 liquidity = 111828391515548962972817;

        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;
        permit2.approve(address(this), address(posm), type(uint160).max, type(uint48).max);

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

        loadingLiquidity = false;
    }

    /// @notice Creates parameters for minting liquidity in Uniswap V4
    function _mintLiquidityParams(
        PoolKey memory poolKey,
        int24 _tickLower,
        int24 _tickUpper,
        uint256 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(poolKey, _tickLower, _tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData);
        params[1] = abi.encode(poolKey.currency0, poolKey.currency1);
        return (actions, params);
    }

    /* ========================= PNKSTR liquidity management ========================= */

    /// @notice Mints a new Uniswap V4 position for a registered PNKSTR pool using contract-held liquidity.
    /// @param listId Identifier of the registered pool key configuration.
    /// @param nativeAmount Amount of native ETH (token0) to deploy from the contract balance.
    /// @param minLiquidity Minimum liquidity the mint should produce (0 to skip the check).
    function mintLiquidity(
        uint256 listId,
        uint256 nativeAmount,
        uint256 minLiquidity
    )
        external
        nonReentrant
        returns (uint256)
    {
        (PoolKey memory poolKey, bytes32 poolId) = _getRegisteredPoolKey(listId);
        if (pnkstrLiquidityTokenIds[poolId] != 0) revert PositionAlreadyInitialized();
        uint256 ethPurchase = nativeAmount / 2;
        uint256 pnkAmount;

        (ethPurchase, pnkAmount) = _pnkstrPurchase(ethPurchase);

        uint ethLeft = nativeAmount - ethPurchase;

        if (ethLeft == 0 || pnkAmount == 0) revert InsufficientAmount();
        if (IERC20(PNKSTR_ADDRESS).balanceOf(address(this)) < pnkAmount) revert InsufficientAmount();

        uint256 contractBalanceBefore = address(this).balance;
        if (contractBalanceBefore < ethLeft) revert InsufficientContractEth();

        (uint160 sqrtPriceX96,) = _getCurrentPoolState(poolKey);
        (int24 tickLower, int24 tickUpper) = _globalTicks(poolKey.tickSpacing);

        uint128 liquidity = _computeLiquidity(sqrtPriceX96, tickLower, tickUpper, ethLeft, pnkAmount);
        if (liquidity == 0 || (minLiquidity > 0 && liquidity < minLiquidity)) revert LiquidityTooLow();

        if (pnkAmount > type(uint160).max) revert AmountTooLarge();

        _ensurePnkstrApprovals(pnkAmount);

        uint128 amount0Max = _toUint128(ethLeft);
        uint128 amount1Max = _toUint128(pnkAmount);

        bytes memory hookData = new bytes(0);

        (bytes memory actions, bytes[] memory params) = _mintLiquidityParams(
            poolKey,
            tickLower,
            tickUpper,
            uint256(liquidity),
            uint256(amount0Max),
            uint256(amount1Max),
            address(this),
            hookData
        );

        uint256 tokenId = posm.nextTokenId();
        posm.modifyLiquidities{value: ethLeft}(abi.encode(actions, params), block.timestamp + 60);

        pnkstrLiquidityTokenIds[poolId] = tokenId;
        totalEthSpentOnLiquidity += ethLeft;
        totalPnkPurchased += pnkAmount;

        emit PnkstrPositionInitialized(listId, poolId, tokenId);

        return tokenId;
    }

    /// @notice Adds liquidity to the existing PNKSTR position tracked by this contract using available balances.
    /// @param listId Identifier of the registered pool key configuration.
    function addLiquidity(uint256 listId) external nonReentrant returns (uint128) {
        (PoolKey memory poolKey, bytes32 poolId) = _getRegisteredPoolKey(listId);
        uint256 tokenId = pnkstrLiquidityTokenIds[poolId];
        uint256 availableEth = address(this).balance;
        uint256 ethPurchase = availableEth / 2;
        uint256 pnkAmount;

        (ethPurchase, pnkAmount) = _pnkstrPurchase(ethPurchase);

        uint ethLeft = availableEth - ethPurchase;

        if (tokenId == 0) revert PositionNotInitialized();
        if (ethLeft == 0 || pnkAmount == 0) revert InsufficientAmount();
        if (IERC20(PNKSTR_ADDRESS).balanceOf(address(this)) < pnkAmount) revert InsufficientAmount();
        if (address(this).balance < ethLeft) revert InsufficientContractEth();

        PositionInfo info;
        (poolKey, info) = posm.getPoolAndPositionInfo(tokenId);

        (uint160 sqrtPriceX96,) = _getCurrentPoolState(poolKey);
        int24 tickLower = info.tickLower();
        int24 tickUpper = info.tickUpper();
        uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
        uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(tickUpper);

        uint128 liquidityDelta = LiquidityAmounts.getLiquidityForAmounts(
            sqrtPriceX96,
            sqrtLower,
            sqrtUpper,
            ethLeft,
            pnkAmount
        );
        if (liquidityDelta == 0) revert LiquidityTooLow();

        _ensurePnkstrApprovals(pnkAmount);

        uint256 ethBefore = address(this).balance;
        uint256 pnkBefore = IERC20(PNKSTR_ADDRESS).balanceOf(address(this));

        uint128 amount0Max = _toUint128(ethLeft);
        uint128 amount1Max = _toUint128(pnkAmount);

        bytes memory hookData = new bytes(0);
        bytes memory actions = abi.encodePacked(uint8(Actions.INCREASE_LIQUIDITY), uint8(Actions.SETTLE_PAIR));

        bytes[] memory params = new bytes[](2);
        params[0] = abi.encode(tokenId, uint256(liquidityDelta), amount0Max, amount1Max, hookData);
        params[1] = abi.encode(poolKey.currency0, poolKey.currency1);

        posm.modifyLiquidities{value: ethLeft}(abi.encode(actions, params), block.timestamp + 60);

        uint256 ethAfter = address(this).balance;
        uint256 pnkAfter = IERC20(PNKSTR_ADDRESS).balanceOf(address(this));

        uint256 feeEth = ethAfter + ethLeft > ethBefore ? ethAfter + ethLeft - ethBefore : 0;
        uint256 feePnk = pnkAfter + pnkAmount > pnkBefore ? pnkAfter + pnkAmount - pnkBefore : 0;

        if (feeEth > 0 || feePnk > 0) {
            uint256 burnedPrimaryToken = _convertFeesAndBurn(poolKey, feeEth, feePnk);
            totalTokenBurned += burnedPrimaryToken;
            emit FeesConvertedAndBurned(feeEth, feePnk, burnedPrimaryToken);
        }

        totalEthSpentOnLiquidity += ethLeft;
        totalPnkPurchased += pnkAmount;

        return liquidityDelta;
    }

    function addLiquidityStatus(uint256 listId)
        public
        view
        returns (uint256 requiredEth, uint256 requiredPnk)
    {
        bytes32 poolId;
        PoolKey memory poolKey;
        (poolKey, poolId) = _getRegisteredPoolKey(listId);

        uint256 availableEth = address(this).balance;
        requiredEth = availableEth / 2;

        (uint160 sqrtPriceX96,) = _getCurrentPoolState(poolKey);

        uint256 priceX192 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);
        requiredPnk = FullMath.mulDiv(requiredEth, priceX192, uint256(1) << 192);

        return (requiredEth, requiredPnk);
    }

    function collectLPFees(uint256 listId)
        external
        nonReentrant
        returns (uint256 amountEth, uint256 amountPnk)
    {
        (PoolKey memory poolKey, bytes32 poolId) = _getRegisteredPoolKey(listId);
        uint256 tokenId = pnkstrLiquidityTokenIds[poolId];
        if (tokenId == 0) revert PositionNotInitialized();

        (PoolKey memory storedPoolKey,) = posm.getPoolAndPositionInfo(tokenId);
        if (!_poolIdsMatch(poolKey, storedPoolKey)) revert InvalidPoolKey();

        uint256 ethBefore = address(this).balance;
        uint256 pnkBefore = IERC20(PNKSTR_ADDRESS).balanceOf(address(this));

        bytes memory actions = abi.encodePacked(
            uint8(Actions.DECREASE_LIQUIDITY),
            uint8(Actions.TAKE_PAIR)
        );

        bytes[] memory params = new bytes[](2);
        params[0] = abi.encode(tokenId, uint256(0), uint128(0), uint128(0), bytes(""));
        params[1] = abi.encode(storedPoolKey.currency0, storedPoolKey.currency1, address(this));

        posm.modifyLiquidities(abi.encode(actions, params), block.timestamp + 60);

        amountEth = address(this).balance - ethBefore;
        amountPnk = IERC20(PNKSTR_ADDRESS).balanceOf(address(this)) - pnkBefore;

        uint256 burnedPrimaryToken = _convertFeesAndBurn(storedPoolKey, amountEth, amountPnk);
        totalTokenBurned += burnedPrimaryToken;
        emit FeesConvertedAndBurned(amountEth, amountPnk, burnedPrimaryToken);
    }

    function getPositionDetails(uint256 listId)
        external
        view
        returns (
            uint256 tokenId,
            int24 tickLower,
            int24 tickUpper,
            uint128 liquidity,
            uint160 sqrtPriceX96,
            uint256 positionEth,
            uint256 positionPnk
        )
    {
        PoolKey memory poolKey;
        bytes32 poolIdKey;
        (poolKey, poolIdKey) = _getRegisteredPoolKey(listId);
        (sqrtPriceX96,) = _getCurrentPoolState(poolKey);
        tokenId = pnkstrLiquidityTokenIds[poolIdKey];
        if (tokenId == 0) {
            return (0, 0, 0, 0, sqrtPriceX96, 0, 0);
        }

        PositionInfo info;
        (poolKey, info) = posm.getPoolAndPositionInfo(tokenId);

        tickLower = info.tickLower();
        tickUpper = info.tickUpper();
        liquidity = posm.getPositionLiquidity(tokenId);

        uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
        uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(tickUpper);
        (positionEth, positionPnk) =
            LiquidityAmounts.getAmountsForLiquidity(sqrtPriceX96, sqrtLower, sqrtUpper, liquidity);
    }

    function getPendingFees(uint256 listId) external view returns (uint256 pendingEth, uint256 pendingPnk) {
        if (!registeredPnkstrPoolKeyExists[listId]) revert PoolKeyNotRegistered();

        (PoolKey memory poolKey, bytes32 poolId) = _getRegisteredPoolKey(listId);
        uint256 tokenId = pnkstrLiquidityTokenIds[poolId];
        if (tokenId == 0) revert PositionNotInitialized();

        (PoolKey memory activePoolKey, PositionInfo info) = posm.getPoolAndPositionInfo(tokenId);
        if (!_poolIdsMatch(poolKey, activePoolKey)) poolKey = activePoolKey;

        uint128 liquidity = posm.getPositionLiquidity(tokenId);
        if (liquidity == 0) return (0, 0);

        int24 tickLower = info.tickLower();
        int24 tickUpper = info.tickUpper();

        IPoolManager manager = IPoolManager(poolManager);
        (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
            StateLibrary.getFeeGrowthInside(manager, poolKey.toId(), tickLower, tickUpper);

        bytes32 positionKey = _calculatePositionKey(address(posm), tickLower, tickUpper, bytes32(tokenId));
        (uint128 storedLiquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) =
            StateLibrary.getPositionInfo(manager, poolKey.toId(), positionKey);

        if (storedLiquidity == 0 && feeGrowthInside0LastX128 == 0 && feeGrowthInside1LastX128 == 0) {
            positionKey = _calculatePositionKey(address(this), tickLower, tickUpper, bytes32(0));
            (storedLiquidity, feeGrowthInside0LastX128, feeGrowthInside1LastX128) =
                StateLibrary.getPositionInfo(manager, poolKey.toId(), positionKey);
        }

        if (storedLiquidity == 0) storedLiquidity = liquidity;

        if (feeGrowthInside0X128 > feeGrowthInside0LastX128) {
            pendingEth = FullMath.mulDiv(
                feeGrowthInside0X128 - feeGrowthInside0LastX128, storedLiquidity, FixedPoint128.Q128
            );
        }
        if (feeGrowthInside1X128 > feeGrowthInside1LastX128) {
            pendingPnk = FullMath.mulDiv(
                feeGrowthInside1X128 - feeGrowthInside1LastX128, storedLiquidity, FixedPoint128.Q128
            );
        }
    }

    /* ========================= PNKSTR liquidity helpers ========================= */

    function _getRegisteredPoolKey(uint256 listId) internal view returns (PoolKey memory poolKey, bytes32 poolId) {
        if (!registeredPnkstrPoolKeyExists[listId]) revert PoolKeyNotRegistered();
        poolKey = registeredPnkstrPoolKeys[listId];
        poolId = PoolId.unwrap(poolKey.toId());
    }

    function _poolIdsMatch(PoolKey memory a, PoolKey memory b) internal pure returns (bool) {
        return PoolId.unwrap(a.toId()) == PoolId.unwrap(b.toId());
    }

    function _getCurrentPoolState(PoolKey memory poolKey)
        internal
        view
        returns (uint160 sqrtPriceX96, int24 currentTick)
    {
        PoolId poolId = poolKey.toId();
        (sqrtPriceX96, currentTick,,) = StateLibrary.getSlot0(IPoolManager(poolManager), poolId);
    }

    function _calculatePositionKey(address owner, int24 tickLower, int24 tickUpper, bytes32 salt)
        internal
        pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt));
    }

    function _globalTicks(int24 tickSpacing) internal pure returns (int24 tickLower, int24 tickUpper) {
        tickLower = TickMath.minUsableTick(tickSpacing);
        tickUpper = TickMath.maxUsableTick(tickSpacing);
    }

    function _computeLiquidity(
        uint160 sqrtPriceX96,
        int24 tickLower,
        int24 tickUpper,
        uint256 amount0,
        uint256 amount1
    ) internal pure returns (uint128) {
        uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
        uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(tickUpper);
        return LiquidityAmounts.getLiquidityForAmounts(
            sqrtPriceX96,
            sqrtLower,
            sqrtUpper,
            amount0,
            amount1
        );
    }

    function _convertFeesAndBurn(PoolKey memory poolKey, uint256 ethAmount, uint256 pnkAmount)
        internal
        returns (uint256 burnedPrimaryToken)
    {
        uint256 ethForBurn = ethAmount;
        if (pnkAmount > 0) {
            uint256 ethFromPnk = _swapPnkForEth(poolKey, pnkAmount);
            ethForBurn += ethFromPnk;
        }

        if (ethForBurn > 0) {
            burnedPrimaryToken = _buyAndBurnPrimaryToken(ethForBurn);
        }
    }

    function _swapPnkForEth(PoolKey memory poolKey, uint256 amountPnk) internal returns (uint256 ethOut) {
        if (amountPnk == 0) return 0;

        _ensureRouterApproval(PNKSTR_ADDRESS, amountPnk);

        BalanceDelta delta = router.swapExactTokensForTokens(
            amountPnk,
            0,
            false,
            poolKey,
            "",
            address(this),
            block.timestamp
        );

        ethOut = _abs(delta.amount0());
    }

    function _buyAndBurnPrimaryToken(uint256 ethAmount) internal returns (uint256 primaryTokenBurned) {
        if (ethAmount == 0) return 0;

        PoolKey memory key = PoolKey(
            Currency.wrap(address(0)),
            Currency.wrap(address(this)),
            PRIMARY_TOKEN_POOL_FEE,
            PRIMARY_TOKEN_TICK_SPACING,
            IHooks(hookAddress)
        );

        BalanceDelta delta = router.swapExactTokensForTokens{value: ethAmount}(
            ethAmount,
            0,
            true,
            key,
            "",
            DEADADDRESS,
            block.timestamp
        );

        primaryTokenBurned = _abs(delta.amount1());
    }

    function _ensureRouterApproval(address token, uint256 amount) internal {
        if (IERC20(token).allowance(address(this), address(router)) < amount) {
            IERC20(token).approve(address(router), type(uint256).max);
        }
    }

    function _ensurePnkstrApprovals(uint256 tokenAmount) internal {
        IERC20 token = IERC20(PNKSTR_ADDRESS);
        if (token.allowance(address(this), address(permit2)) < tokenAmount) {
            token.approve(address(permit2), type(uint256).max);
        }
        (uint160 permitted,,) = permit2.allowance(address(this), PNKSTR_ADDRESS, address(posm));
        if (permitted < uint160(tokenAmount)) {
            permit2.approve(PNKSTR_ADDRESS, address(posm), type(uint160).max, type(uint48).max);
        }
    }

    function _toUint128(uint256 value) internal pure returns (uint128) {
        if (value > type(uint128).max) revert AmountTooLarge();
        return uint128(value);
    }

    /// @notice Provides aggregated ETH accounting for monitoring net LP fees.
    function getEthAccounting()
        external
        view
        returns (
            uint256 contractBalance,
            uint256 feesReceived,
            uint256 ethSpentOnPurchases_,
            uint256 ethSpentOnLiquidity_,
            uint256 pnkPurchased_,
            int256 netEarnedFees
        )
    {
        contractBalance = address(this).balance;
        feesReceived = currentFees;
        ethSpentOnPurchases_ = totalEthSpentOnPurchases;
        ethSpentOnLiquidity_ = totalEthSpentOnLiquidity;
        pnkPurchased_ = totalPnkPurchased;

        uint256 totalSpent = ethSpentOnPurchases_ + ethSpentOnLiquidity_;
        uint256 gross = contractBalance + totalSpent;
        if (gross >= feesReceived) {
            netEarnedFees = int256(gross - feesReceived);
        } else {
            netEarnedFees = -int256(feesReceived - gross);
        }
    }

    /* ========================= Utilities ========================= */
    function _abs(int128 x) internal pure returns (uint256) {
        return x < 0 ? uint256(int256(-x)) : uint256(int256(x));
    }
    function tranferERC20(address token, address to, uint256 amount) external onlyOwner {
        IERC20(token).transfer(to, amount);
    }

    function tranferERC721(address token, address to, uint256 id) external onlyOwner {
        IERC721(token).safeTransferFrom(address(this), to, id);
    }

    receive() external payable {}
}

library LiquidityAmounts {
    using SafeCast for uint256;

    uint256 internal constant Q96 = 0x1000000000000000000000000;

    function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0)
        internal
        pure
        returns (uint128 liquidity)
    {
        if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
        uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, Q96);
        return FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
    }

    function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1)
        internal
        pure
        returns (uint128 liquidity)
    {
        if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
        return FullMath.mulDiv(amount1, Q96, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
    }

    function getLiquidityForAmounts(
        uint160 sqrtPriceX96,
        uint160 sqrtPriceAX96,
        uint160 sqrtPriceBX96,
        uint256 amount0,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);

        if (sqrtPriceX96 <= sqrtPriceAX96) {
            liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0);
        } else if (sqrtPriceX96 < sqrtPriceBX96) {
            uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0);
            uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1);

            liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
        } else {
            liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1);
        }
    }

    function getAmount0ForLiquidity(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint128 liquidity)
        internal
        pure
        returns (uint256 amount0)
    {
        if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);

        return FullMath.mulDiv(
            uint256(liquidity) << 96, sqrtPriceBX96 - sqrtPriceAX96, sqrtPriceBX96
        ) / sqrtPriceAX96;
    }

    function getAmount1ForLiquidity(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint128 liquidity)
        internal
        pure
        returns (uint256 amount1)
    {
        if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
        return FullMath.mulDiv(liquidity, sqrtPriceBX96 - sqrtPriceAX96, Q96);
    }

    function getAmountsForLiquidity(
        uint160 sqrtPriceX96,
        uint160 sqrtPriceAX96,
        uint160 sqrtPriceBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0, uint256 amount1) {
        if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);

        if (sqrtPriceX96 <= sqrtPriceAX96) {
            amount0 = getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity);
        } else if (sqrtPriceX96 < sqrtPriceBX96) {
            amount0 = getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity);
            amount1 = getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity);
        } else {
            amount1 = getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity);
        }
    }
}"
    },
    "lib/solady/src/auth/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The caller is not authorized to call the function.
    error Unauthorized();

    /// @dev The `newOwner` cannot be the zero address.
    error NewOwnerIsZeroAddress();

    /// @dev The `pendingOwner` does not have a valid handover request.
    error NoHandoverRequest();

    /// @dev Cannot double-initialize.
    error AlreadyInitialized();

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

    /// @dev The ownership is transferred from `oldOwner` to `newOwner`.
    /// This event is intentionally kept the same as OpenZeppelin's Ownable to be
    /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
    /// despite it not being as lightweight as a single argument event.
    event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);

    /// @dev An ownership handover to `pendingOwner` has been requested.
    event OwnershipHandoverRequested(address indexed pendingOwner);

    /// @dev The ownership handover to `pendingOwner` has been canceled.
    event OwnershipHandoverCanceled(address indexed pendingOwner);

    /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
    uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
        0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;

    /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
        0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;

    /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
        0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;

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

    /// @dev The owner slot is given by:
    /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
    /// It is intentionally chosen to be a high value
    /// to avoid collision with lower slots.
    /// The choice of manual storage layout is to enable compatibility
    /// with both regular and upgradeable contracts.
    bytes32 internal constant _OWNER_SLOT =
        0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;

    /// The ownership handover slot of `newOwner` is given by:
    /// ```
    ///     mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
    ///     let handoverSlot := keccak256(0x00, 0x20)
    /// ```
    /// It stores the expiry timestamp of the two-step ownership handover.
    uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     INTERNAL FUNCTIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
    function _guardInitializeOwner() internal pure virtual returns (bool guard) {}

    /// @dev Initializes the owner directly without authorization guard.
    /// This function must be called upon initialization,
    /// regardless of whether the contract is upgradeable or not.
    /// This is to enable generalization to both regular and upgradeable contracts,
    /// and to save gas in case the initial owner is not the caller.
    /// For performance reasons, this function will not check if there
    /// is an existing owner.
    function _initializeOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                if sload(ownerSlot) {
                    mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
                    revert(0x1c, 0x04)
                }
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(_OWNER_SLOT, newOwner)
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        }
    }

    /// @dev Sets the owner directly without authorization guard.
    function _setOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, newOwner)
            }
        }
    }

    /// @dev Throws if the sender is not the owner.
    function _checkOwner() internal view virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // If the caller is not the stored owner, revert.
            if iszero(eq(caller(), sload(_OWNER_SLOT))) {
                mstore(0x00, 0x82b42900) // `Unauthorized()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Returns how long a two-step ownership handover is valid for in seconds.
    /// Override to return a different value if needed.
    /// Made internal to conserve bytecode. Wrap it in a public function if needed.
    function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
        return 48 * 3600;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  PUBLIC UPDATE FUNCTIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Allows the owner to transfer the ownership to `newOwner`.
    function transferOwnership(address newOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(shl(96, newOwner)) {
                mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
                revert(0x1c, 0x04)
            }
        }
        _setOwner(newOwner);
    }

    /// @dev Allows the owner to renounce their ownership.
    function renounceOwnership() public payable virtual onlyOwner {
        _setOwner(address(0));
    }

    /// @dev Request a two-step ownership handover to the caller.
    /// The request will automatically expire in 48 hours (172800 seconds) by default.
    function requestOwnershipHandover() public payable virtual {
        unchecked {
            uint256 expires = block.timestamp + _ownershipHandoverValidFor();
            /// @solidity memory-safe-assembly
            assembly {
                // Compute and set the handover slot to `expires`.
                mstore(0x0c, _HANDOVER_SLOT_SEED)
                mstore(0x00, caller())
                sstore(keccak256(0x0c, 0x20), expires)
                // Emit the {OwnershipHandoverRequested} event.
                log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
            }
        }
    }

    /// @dev Cancels the two-step ownership handover to the caller, if any.
    function cancelOwnershipHandover() public payable virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, caller())
            sstore(keccak256(0x0c, 0x20), 0)
            // Emit the {OwnershipHandoverCanceled} event.
            log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
        }
    }

    /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
    /// Reverts if there is no existing ownership handover requested by `pendingOwner`.
    function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            let handoverSlot := keccak256(0x0c, 0x20)
            // If the handover does not exist, or has expired.
            if gt(timestamp(), sload(handoverSlot)) {
                mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
                revert(0x1c, 0x04)
            }
            // Set the handover slot to 0.
            sstore(handoverSlot, 0)
        }
        _setOwner(pendingOwner);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   PUBLIC READ FUNCTIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the owner of the contract.
    function owner() public view virtual returns (address result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := sload(_OWNER_SLOT)
        }
    }

    /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
    function ownershipHandoverExpiresAt(address pendingOwner)
        public
        view
        virtual
        returns (uint256 result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the handover slot.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            // Load the handover slot.
            result := sload(keccak256(0x0c, 0x20))
        }
    }

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

    /// @dev Marks a function as only callable by the owner.
    modifier onlyOwner() virtual {
        _checkOwner();
        _;
    }
}"
    },
    "lib/solady/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) {
        /// @solidit

Tags:
ERC20, ERC721, ERC165, Multisig, Mintable, Burnable, Non-Fungible, Swap, Liquidity, Upgradeable, Multi-Signature, Factory|addr:0xe773a1d6e98d382274ef46c92b2db6de6797290d|verified:true|block:23590146|tx:0x81456875c7cb136edd733dd14c1531ec0e6edae9b491b751a46aae231e0f92c1|first_check:1760616748

Submitted on: 2025-10-16 14:12:32

Comments

Log in to comment.

No comments yet.