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
- Creator
- 0xD9255e7Bf5…efAab88950
- 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