AtomicGMEMEGmgnLaunch

0xcf65cf4628849e6cf61f4954911c89e1abc25911

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
1,944 bytes
ABI entries
46
32 read · 0 write
License
none

Contract information

Address
0xcf65cf4628849e6cf61f4954911c89e1abc25911
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x06c79b4447…b8103d745c

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (32)

BPS()uint256
CANONICAL_GME()address
DEAD()address
GME_MID_WIDTH()int24
GME_NARROW_1_WIDTH()int24
GME_NARROW_2_WIDTH()int24
GME_TAIL_WIDTH()int24
GME_WIDE_WIDTH()int24
LP_FEE()uint24
PERMIT2()address
ROBINHOOD_CHAIN_ID()uint256
TICK_SPACING()int24
V3_MAX_USABLE_TICK()int24
V3_MIN_USABLE_TICK()int24
V3_POSITION_MANAGER()address
V3_SWAP_ROUTER()address
V4_POSITION_MANAGER()address
WETH()address
WETH_POOL_SHARE_BPS()uint256
gmeMidPositionTokenId()uint256
gmeNarrow1PositionTokenId()uint256
gmeNarrow2PositionTokenId()uint256
gmeOpeningTick()int24
gmeTailPositionTokenId()uint256
gmeWidePositionTokenId()uint256
launchRecipient()address
token()address
v3FeeCollector()address
v4FeeCollector()address
wethOpeningTick()int24
wethPool()address
wethPositionTokenId()uint256

Events (1)

GmgnReadyLaunch

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "collector",
        "type": "address"
      },
      {
        "internalType": "int24",
        "name": "wethOpeningAbsoluteTick",
        "type": "int24"
      },
      {
        "internalType": "int24",
        "name": "gmeOpeningAbsoluteTick",
        "type": "int24"
      },
      {
        "internalType": "uint128",
        "name": "initialBuyETH",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "minimumTokensBought",
        "type": "uint128"
      }
    ],
    "stateMutability": "payable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "InitialBuyFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidFeeCollector",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidInitialBuy",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidRecipient",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "int24",
        "name": "tick",
        "type": "int24"
      }
    ],
    "name": "InvalidTick",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidV3Mint",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      }
    ],
    "name": "LiquidityTooLarge",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "PositionNotLocked",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "bits",
        "type": "uint8"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "SafeCastOverflowedUintDowncast",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFailed",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "actual",
        "type": "uint256"
      }
    ],
    "name": "WrongChain",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "v3FeeCollector",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "wethPool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "gmePoolId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethPositionTokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256[5]",
        "name": "gmePositionTokenIds",
        "type": "uint256[5]"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "initialBuyETH",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensBought",
        "type": "uint256"
      }
    ],
    "name": "GmgnReadyLaunch",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "CANONICAL_GME",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DEAD",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "GME_MID_WIDTH",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "GME_NARROW_1_WIDTH",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "GME_NARROW_2_WIDTH",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "GME_TAIL_WIDTH",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "GME_WIDE_WIDTH",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "LP_FEE",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "PERMIT2",
    "outputs": [
      {
        "internalType": "contract IAllowanceTransfer",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ROBINHOOD_CHAIN_ID",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_SPACING",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "V3_MAX_USABLE_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "V3_MIN_USABLE_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "V3_POSITION_MANAGER",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManagerLike",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "V3_SWAP_ROUTER",
    "outputs": [
      {
        "internalType": "contract ISwapRouter02GMEME",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "V4_POSITION_MANAGER",
    "outputs": [
      {
        "internalType": "contract IPositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WETH",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WETH_POOL_SHARE_BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "gmeMidPositionTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "gmeNarrow1PositionTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "gmeNarrow2PositionTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "gmeOpeningTick",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "gmeTailPositionTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "gmeWidePositionTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "token",
    "outputs": [
      {
        "internalType": "contract GameStopMemeStock",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "v3FeeCollector",
    "outputs": [
      {
        "internalType": "contract V3PositionFeesForwarder",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "v4FeeCollector",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "wethOpeningTick",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "wethPool",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "wethPositionTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.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 {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {LiquidityAmounts} from "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";
import {PositionFeesForwarder} from "@uniswap/liquidity-launcher/src/periphery/PositionFeesForwarder.sol";
import {GameStopMemeStock} from "./GameStopMemeStock.sol";
import {V3PositionFeesForwarder} from "./V3PositionFeesForwarder.sol";
import {INonfungiblePositionManagerLike} from "./interfaces/IV3Launch.sol";

interface ISwapRouter02GMEME {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
}

/// @notice Atomic GMEME launch with a standard WETH/GMEME Uniswap v3 market for GMGN routing and
/// a separate, permanently locked GME/GMEME Uniswap v4 stock market.
/// @dev The token is plain fixed-supply ERC-20. Both pools are hookless/taxless and charge a static 1% LP fee.
contract AtomicGMEMEGmgnLaunch {
    using PoolIdLibrary for PoolKey;
    using SafeERC20 for IERC20;

    uint256 public constant ROBINHOOD_CHAIN_ID = 4663;
    uint256 public constant BPS = 10_000;
    uint256 public constant WETH_POOL_SHARE_BPS = 7_000;
    uint24 public constant LP_FEE = 10_000;
    int24 public constant TICK_SPACING = 200;
    int24 public constant V3_MIN_USABLE_TICK = -887_200;
    int24 public constant V3_MAX_USABLE_TICK = 887_200;

    int24 public constant GME_NARROW_1_WIDTH = 2_000;
    int24 public constant GME_NARROW_2_WIDTH = 4_000;
    int24 public constant GME_MID_WIDTH = 8_000;
    int24 public constant GME_WIDE_WIDTH = 18_000;
    int24 public constant GME_TAIL_WIDTH = 92_000;

    address public constant WETH = 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73;
    address public constant CANONICAL_GME = 0x1b0E319c6A659F002271B69dB8A7df2F911c153E;
    INonfungiblePositionManagerLike public constant V3_POSITION_MANAGER =
        INonfungiblePositionManagerLike(0x73991a25C818Bf1f1128dEAaB1492D45638DE0D3);
    ISwapRouter02GMEME public constant V3_SWAP_ROUTER = ISwapRouter02GMEME(0xCaf681a66D020601342297493863E78C959E5cb2);
    IPositionManager public constant V4_POSITION_MANAGER = IPositionManager(0x58daec3116aae6D93017bAAea7749052E8a04fA7);
    IAllowanceTransfer public constant PERMIT2 = IAllowanceTransfer(0x000000000022D473030F116dDEE9F6B43aC78BA3);
    address public constant DEAD = 0x000000000000000000000000000000000000dEaD;

    GameStopMemeStock public immutable token;
    address public immutable launchRecipient;
    address public immutable v4FeeCollector;
    V3PositionFeesForwarder public immutable v3FeeCollector;
    address public immutable wethPool;
    uint256 public immutable wethPositionTokenId;
    uint256 public immutable gmeNarrow1PositionTokenId;
    uint256 public immutable gmeNarrow2PositionTokenId;
    uint256 public immutable gmeMidPositionTokenId;
    uint256 public immutable gmeWidePositionTokenId;
    uint256 public immutable gmeTailPositionTokenId;
    int24 public immutable wethOpeningTick;
    int24 public immutable gmeOpeningTick;

    struct RouterExactInputSingleParams {
        PoolKey poolKey;
        bool zeroForOne;
        uint128 amountIn;
        uint128 amountOutMinimum;
        uint256 minHopPriceX36;
        bytes hookData;
    }

    event GmgnReadyLaunch(
        address indexed token,
        address indexed recipient,
        address indexed v3FeeCollector,
        address wethPool,
        bytes32 gmePoolId,
        uint256 wethPositionTokenId,
        uint256[5] gmePositionTokenIds,
        uint256 initialBuyETH,
        uint256 tokensBought
    );

    error WrongChain(uint256 actual);
    error InvalidRecipient();
    error InvalidFeeCollector();
    error InvalidTick(int24 tick);
    error InvalidInitialBuy();
    error InvalidV3Mint();
    error LiquidityTooLarge(uint128 liquidity);
    error PositionNotLocked(uint256 tokenId);
    error TransferFailed();
    error InitialBuyFailed();

    constructor(
        address recipient,
        address collector,
        int24 wethOpeningAbsoluteTick,
        int24 gmeOpeningAbsoluteTick,
        uint128 initialBuyETH,
        uint128 minimumTokensBought
    ) payable {
        if (block.chainid != ROBINHOOD_CHAIN_ID) revert WrongChain(block.chainid);
        if (
            recipient == address(0) || recipient == address(1) || recipient == address(2) || recipient.code.length != 0
                || recipient != msg.sender
        ) revert InvalidRecipient();
        if (msg.value != initialBuyETH || (initialBuyETH == 0) != (minimumTokensBought == 0)) {
            revert InvalidInitialBuy();
        }
        if (collector == address(0)) revert InvalidFeeCollector();
        _validateOpeningTicks(wethOpeningAbsoluteTick, gmeOpeningAbsoluteTick);
        _validateV4Collector(collector, recipient);

        launchRecipient = recipient;
        v4FeeCollector = collector;
        token = new GameStopMemeStock(address(this));
        v3FeeCollector = new V3PositionFeesForwarder(V3_POSITION_MANAGER, address(this), recipient);

        uint256 wethTokenBudget = token.totalSupply() * WETH_POOL_SHARE_BPS / BPS;
        (wethPool, wethPositionTokenId, wethOpeningTick) =
            _createWethPool(wethOpeningAbsoluteTick, wethTokenBudget, address(v3FeeCollector));

        (PoolKey memory gmeKey, bool tokenIsCurrency0, int24 actualGmeOpeningTick) =
            _initializeGmePool(gmeOpeningAbsoluteTick);
        gmeOpeningTick = actualGmeOpeningTick;

        if (!IERC20(address(token)).approve(address(PERMIT2), type(uint256).max)) revert TransferFailed();
        PERMIT2.approve(address(token), address(V4_POSITION_MANAGER), type(uint160).max, type(uint48).max);

        uint256 gmeTokenBudget = token.balanceOf(address(this));
        uint256 narrow1Amount = gmeTokenBudget * 1_000 / BPS;
        uint256 narrow2Amount = gmeTokenBudget * 2_500 / BPS;
        uint256 midAmount = gmeTokenBudget * 3_000 / BPS;
        uint256 wideAmount = gmeTokenBudget * 2_000 / BPS;
        uint256 tailAmount = gmeTokenBudget - narrow1Amount - narrow2Amount - midAmount - wideAmount;

        gmeNarrow1PositionTokenId = _mintV4TokenOnlyPosition(
            gmeKey, actualGmeOpeningTick, tokenIsCurrency0, GME_NARROW_1_WIDTH, narrow1Amount, collector
        );
        gmeNarrow2PositionTokenId = _mintV4TokenOnlyPosition(
            gmeKey, actualGmeOpeningTick, tokenIsCurrency0, GME_NARROW_2_WIDTH, narrow2Amount, collector
        );
        gmeMidPositionTokenId = _mintV4TokenOnlyPosition(
            gmeKey, actualGmeOpeningTick, tokenIsCurrency0, GME_MID_WIDTH, midAmount, collector
        );
        gmeWidePositionTokenId = _mintV4TokenOnlyPosition(
            gmeKey, actualGmeOpeningTick, tokenIsCurrency0, GME_WIDE_WIDTH, wideAmount, collector
        );
        gmeTailPositionTokenId = _mintV4TokenOnlyPosition(
            gmeKey, actualGmeOpeningTick, tokenIsCurrency0, GME_TAIL_WIDTH, tailAmount, collector
        );

        uint256 roundingRemainder = token.balanceOf(address(this));
        if (roundingRemainder != 0 && !token.transfer(DEAD, roundingRemainder)) revert TransferFailed();

        uint256 bought = 0;
        if (initialBuyETH != 0) {
            bought = V3_SWAP_ROUTER.exactInputSingle{value: initialBuyETH}(
                ISwapRouter02GMEME.ExactInputSingleParams({
                    tokenIn: WETH,
                    tokenOut: address(token),
                    fee: LP_FEE,
                    recipient: recipient,
                    amountIn: initialBuyETH,
                    amountOutMinimum: minimumTokensBought,
                    sqrtPriceLimitX96: 0
                })
            );
            if (bought < minimumTokensBought) revert InitialBuyFailed();
        }

        _verifyLocks(collector);

        uint256[5] memory gmeIds = [
            gmeNarrow1PositionTokenId,
            gmeNarrow2PositionTokenId,
            gmeMidPositionTokenId,
            gmeWidePositionTokenId,
            gmeTailPositionTokenId
        ];
        emit GmgnReadyLaunch(
            address(token),
            recipient,
            address(v3FeeCollector),
            wethPool,
            PoolId.unwrap(gmeKey.toId()),
            wethPositionTokenId,
            gmeIds,
            initialBuyETH,
            bought
        );
    }

    function _createWethPool(int24 openingAbsoluteTick, uint256 tokenBudget, address locker)
        private
        returns (address pool, uint256 positionTokenId, int24 actualOpeningTick)
    {
        bool tokenIsToken0 = address(token) < WETH;
        actualOpeningTick = tokenIsToken0 ? -openingAbsoluteTick : openingAbsoluteTick;
        address token0 = tokenIsToken0 ? address(token) : WETH;
        address token1 = tokenIsToken0 ? WETH : address(token);
        uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(actualOpeningTick);

        pool = V3_POSITION_MANAGER.createAndInitializePoolIfNecessary(token0, token1, LP_FEE, sqrtPriceX96);
        IERC20(address(token)).forceApprove(address(V3_POSITION_MANAGER), tokenBudget);

        (int24 lower, int24 upper) =
            tokenIsToken0 ? (actualOpeningTick, V3_MAX_USABLE_TICK) : (V3_MIN_USABLE_TICK, actualOpeningTick);

        uint128 mintedLiquidity;
        uint256 amount0Used;
        uint256 amount1Used;
        (positionTokenId, mintedLiquidity, amount0Used, amount1Used) = V3_POSITION_MANAGER.mint(
            INonfungiblePositionManagerLike.MintParams({
                token0: token0,
                token1: token1,
                fee: LP_FEE,
                tickLower: lower,
                tickUpper: upper,
                amount0Desired: tokenIsToken0 ? tokenBudget : 0,
                amount1Desired: tokenIsToken0 ? 0 : tokenBudget,
                amount0Min: 0,
                amount1Min: 0,
                recipient: locker,
                deadline: type(uint256).max
            })
        );
        uint256 tokenAmountUsed = tokenIsToken0 ? amount0Used : amount1Used;
        uint256 quoteAmountUsed = tokenIsToken0 ? amount1Used : amount0Used;
        if (mintedLiquidity < 1 || tokenAmountUsed < 1 || quoteAmountUsed > 0) revert InvalidV3Mint();
        IERC20(address(token)).forceApprove(address(V3_POSITION_MANAGER), 0);
    }

    function _initializeGmePool(int24 openingAbsoluteTick)
        private
        returns (PoolKey memory key, bool tokenIsCurrency0, int24 actualOpeningTick)
    {
        tokenIsCurrency0 = address(token) < CANONICAL_GME;
        actualOpeningTick = tokenIsCurrency0 ? -openingAbsoluteTick : openingAbsoluteTick;
        Currency tokenCurrency = Currency.wrap(address(token));
        Currency gmeCurrency = Currency.wrap(CANONICAL_GME);
        key = tokenIsCurrency0
            ? PoolKey(tokenCurrency, gmeCurrency, LP_FEE, TICK_SPACING, IHooks(address(0)))
            : PoolKey(gmeCurrency, tokenCurrency, LP_FEE, TICK_SPACING, IHooks(address(0)));

        if (
            V4_POSITION_MANAGER.initializePool(key, TickMath.getSqrtPriceAtTick(actualOpeningTick)) != actualOpeningTick
        ) {
            revert InvalidTick(actualOpeningTick);
        }
    }

    function _mintV4TokenOnlyPosition(
        PoolKey memory key,
        int24 actualOpeningTick,
        bool tokenIsCurrency0,
        int24 width,
        uint256 tokenAmount,
        address owner
    ) private returns (uint256 tokenId) {
        (int24 lower, int24 upper) = tokenIsCurrency0
            ? (actualOpeningTick, actualOpeningTick + width)
            : (actualOpeningTick - width, actualOpeningTick);
        uint128 liquidity = tokenIsCurrency0
            ? LiquidityAmounts.getLiquidityForAmount0(
                TickMath.getSqrtPriceAtTick(lower), TickMath.getSqrtPriceAtTick(upper), tokenAmount
            )
            : LiquidityAmounts.getLiquidityForAmount1(
                TickMath.getSqrtPriceAtTick(lower), TickMath.getSqrtPriceAtTick(upper), tokenAmount
            );
        uint128 maxPerTick = Pool.tickSpacingToMaxLiquidityPerTick(TICK_SPACING);
        if (liquidity > maxPerTick) revert LiquidityTooLarge(liquidity);

        tokenId = V4_POSITION_MANAGER.nextTokenId();
        uint128 amount = SafeCast.toUint128(tokenAmount);
        bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));
        bytes[] memory params = new bytes[](2);
        params[0] = abi.encode(
            key,
            lower,
            upper,
            liquidity,
            tokenIsCurrency0 ? amount : uint128(0),
            tokenIsCurrency0 ? uint128(0) : amount,
            owner,
            bytes("")
        );
        params[1] = abi.encode(key.currency0, key.currency1);
        V4_POSITION_MANAGER.modifyLiquidities(abi.encode(actions, params), type(uint256).max);
    }

    function _validateOpeningTicks(int24 wethAbsoluteTick, int24 gmeAbsoluteTick) private pure {
        if (wethAbsoluteTick <= 0 || wethAbsoluteTick >= V3_MAX_USABLE_TICK || wethAbsoluteTick % TICK_SPACING != 0) {
            revert InvalidTick(wethAbsoluteTick);
        }
        if (
            gmeAbsoluteTick <= GME_TAIL_WIDTH || gmeAbsoluteTick % TICK_SPACING != 0
                || gmeAbsoluteTick + GME_TAIL_WIDTH >= TickMath.MAX_TICK
        ) revert InvalidTick(gmeAbsoluteTick);
    }

    function _validateV4Collector(address collector, address recipient) private view {
        PositionFeesForwarder forwarder = PositionFeesForwarder(payable(collector));
        if (
            collector.code.length == 0 || address(forwarder.positionManager()) != address(V4_POSITION_MANAGER)
                || forwarder.operator() != DEAD || forwarder.timelockBlockNumber() != type(uint256).max
                || forwarder.feeRecipient() != recipient
        ) revert InvalidFeeCollector();
    }

    function _verifyLocks(address collector) private view {
        if (V3_POSITION_MANAGER.ownerOf(wethPositionTokenId) != address(v3FeeCollector)) {
            revert PositionNotLocked(wethPositionTokenId);
        }
        if (IERC721(address(V4_POSITION_MANAGER)).ownerOf(gmeNarrow1PositionTokenId) != collector) {
            revert PositionNotLocked(gmeNarrow1PositionTokenId);
        }
        if (IERC721(address(V4_POSITION_MANAGER)).ownerOf(gmeNarrow2PositionTokenId) != collector) {
            revert PositionNotLocked(gmeNarrow2PositionTokenId);
        }
        if (IERC721(address(V4_POSITION_MANAGER)).ownerOf(gmeMidPositionTokenId) != collector) {
            revert PositionNotLocked(gmeMidPositionTokenId);
        }
        if (IERC721(address(V4_POSITION_MANAGER)).ownerOf(gmeWidePositionTokenId) != collector) {
            revert PositionNotLocked(gmeWidePositionTokenId);
        }
        if (IERC721(address(V4_POSITION_MANAGER)).ownerOf(gmeTailPositionTokenId) != collector) {
            revert PositionNotLocked(gmeTailPositionTokenId);
        }
    }
}
Chain explorer5862msChain node87ms