Description:
Decentralized Finance (DeFi) protocol contract providing Swap, Liquidity, Factory functionality.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/fairLaunch.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.21;\r
\r
/*\r
* FairLaunchMigrator — used for both SUN and MOON deployments\r
* -----------------------------------------------------------------\r
* Overview\r
* - Finalizes the launch of SUN and MOON by moving liquidity from a\r
* near-fixed-price Uniswap V3 sale pool into an UNTAXED Uniswap\r
* V2-style pair (e.g. PulseX v1 on PulseChain).\r
* - Each token begins with roughly half its supply sold from a tightly\r
* bounded V3 position. When that pool is fully sold out, anyone may\r
* call `migrate()` to automatically seed and lock liquidity in the\r
* V2-style pair using all remaining tokens plus the proceeds raised.\r
*\r
* Networks & target DEXes\r
* - PulseChain: PulseX v1 (Uniswap v2 implementation, labeled “v1”).\r
* - Ethereum: PancakeSwap v2.\r
*\r
* Launch lifecycle\r
* 1) Pre-launch (V3):\r
* - Off-chain, ~50% of total supply is deposited into a tight-range\r
* Uniswap V3 position (near fixed price). Buyers purchase from this pool.\r
* 2) Migration trigger:\r
* - Once all tokens are sold (meeting the `minRaised` threshold in the\r
* pairing token), anyone may call `migrate()`.\r
* 3) Migration steps (atomic):\r
* - Pull all principal + fees from the recorded V3 position NFT.\r
* - If a V2 pair exists with reserves, perform a corrective swap so the\r
* wallet’s token ratio ≈ the pool price (minimizes price shock).\r
* - Add 100% of balances to the V2-style pair (PulseX v1 or PancakeSwap v2).\r
* - Send newly minted LP to `0x...dEaD` and push any dust to the pair; call `sync()`.\r
* 4) Aftermath:\r
* - LP burned → liquidity locked.\r
* - Deployer keys burned → fully decentralized and immutable.\r
*\r
* Trust & usage\r
* - Purpose-built for the SUN & MOON fair launches.\r
* - No owner/admin; `migrate()` is public and one-shot.\r
* - `recordNft(tokenId)` registers the Uniswap V3 position NFT (must be owned/approved).\r
* - Enforces `minRaised` before migrating; the destination pair is UNTAXED.\r
*\r
* TL;DR\r
* - Each token sells half its supply from a near-fixed-price V3 pool.\r
* - When sold out, `migrate()` moves liquidity + remaining tokens to an UNTAXED\r
* V2-style pool and burns the LP → fair, locked, immutable setup.\r
*/\r
\r
interface IERC20 {\r
function approve(address spender, uint256 amount) external returns (bool);\r
function balanceOf(address owner) external view returns (uint256);\r
function transfer(address to, uint256 value) external returns (bool);\r
}\r
\r
interface IUniswapV2Router02 {\r
function addLiquidity(\r
address tokenA,\r
address tokenB,\r
uint amountADesired,\r
uint amountBDesired,\r
uint amountAMin,\r
uint amountBMin,\r
address to,\r
uint deadline\r
) external returns (uint amountA, uint amountB, uint liquidity);\r
}\r
\r
interface IUniswapV2Factory {\r
function getPair(address tokenA, address tokenB) external view returns (address);\r
}\r
\r
interface IUniswapV2Pair {\r
function token0() external view returns (address);\r
function token1() external view returns (address);\r
\r
function getReserves()\r
external\r
view\r
returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);\r
\r
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;\r
function sync() external;\r
}\r
\r
/* ---------------- Uniswap V3 minimal ---------------- */\r
interface IUniswapV3Factory {\r
function getPool(address token0, address token1, uint24 fee) external view returns (address);\r
}\r
\r
interface INonfungiblePositionManager {\r
struct DecreaseLiquidityParams {\r
uint256 tokenId;\r
uint128 liquidity;\r
uint256 amount0Min;\r
uint256 amount1Min;\r
uint256 deadline;\r
}\r
\r
struct CollectParams {\r
uint256 tokenId;\r
address recipient;\r
uint128 amount0Max;\r
uint128 amount1Max;\r
}\r
\r
function positions(uint256 tokenId)\r
external\r
view\r
returns (\r
uint96 nonce,\r
address operator,\r
address token0,\r
address token1,\r
uint24 fee,\r
int24 tickLower,\r
int24 tickUpper,\r
uint128 liquidity,\r
uint256 feeGrowthInside0LastX128,\r
uint256 feeGrowthInside1LastX128,\r
uint128 tokensOwed0,\r
uint128 tokensOwed1\r
);\r
\r
function decreaseLiquidity(DecreaseLiquidityParams calldata params)\r
external\r
returns (uint256 amount0, uint256 amount1);\r
\r
function collect(CollectParams calldata params) external returns (uint256 amount0, uint256 amount1);\r
\r
function factory() external view returns (address);\r
}\r
\r
interface IERC721Like {\r
function ownerOf(uint256 tokenId) external view returns (address);\r
function getApproved(uint256 tokenId) external view returns (address);\r
function isApprovedForAll(address owner, address operator) external view returns (bool);\r
}\r
\r
interface IUniswapV3PoolLike {\r
function slot0()\r
external\r
view\r
returns (\r
uint160 sqrtPriceX96,\r
int24 tick,\r
uint16 observationIndex,\r
uint16 observationCardinality,\r
uint16 observationCardinalityNext,\r
uint8 feeProtocol, \r
bool unlocked\r
);\r
}\r
\r
/* -------------------- Lightweight SafeERC20 -------------------------- */\r
library SafeERC20 {\r
function safeApprove(IERC20 token, address spender, uint256 value) internal {\r
(bool ok, bytes memory data) =\r
address(token).call(abi.encodeWithSelector(token.approve.selector, spender, value));\r
require(ok && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: approve failed");\r
}\r
\r
function safeTransfer(IERC20 token, address to, uint256 value) internal {\r
(bool ok, bytes memory data) =\r
address(token).call(abi.encodeWithSelector(token.transfer.selector, to, value));\r
require(ok && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: transfer failed");\r
}\r
}\r
\r
/* -------------------- MulDiv -------------------------- */\r
library FullMulDiv {\r
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\r
unchecked {\r
uint256 prod0;\r
uint256 prod1;\r
\r
assembly {\r
let mm := mulmod(x, y, not(0))\r
prod0 := mul(x, y)\r
prod1 := sub(sub(mm, prod0), lt(mm, prod0))\r
}\r
\r
if (prod1 == 0) {\r
require(denominator != 0, "mulDiv: denom 0");\r
\r
assembly {\r
result := div(prod0, denominator)\r
}\r
return result;\r
}\r
\r
require(denominator > prod1, "mulDiv: overflow");\r
\r
uint256 remainder;\r
assembly {\r
remainder := mulmod(x, y, denominator)\r
prod1 := sub(prod1, gt(remainder, prod0))\r
prod0 := sub(prod0, remainder)\r
}\r
\r
uint256 twos = denominator & (~denominator + 1);\r
assembly {\r
denominator := div(denominator, twos)\r
prod0 := div(prod0, twos)\r
twos := add(div(sub(0, twos), twos), 1)\r
}\r
prod0 |= prod1 * twos;\r
\r
uint256 inv = (3 * denominator) ^ 2;\r
inv *= 2 - denominator * inv;\r
inv *= 2 - denominator * inv;\r
inv *= 2 - denominator * inv;\r
inv *= 2 - denominator * inv;\r
inv *= 2 - denominator * inv;\r
inv *= 2 - denominator * inv;\r
\r
result = prod0 * inv;\r
}\r
}\r
}\r
\r
/* -------------------- TickMath (from Uniswap v3-core) ---------------- */\r
library TickMath {\r
int24 internal constant MIN_TICK = -887272;\r
int24 internal constant MAX_TICK = 887272;\r
\r
uint160 internal constant MIN_SQRT_RATIO = 4295128739;\r
uint160 internal constant MAX_SQRT_RATIO =\r
1461446703485210103287273052203988822378723970342;\r
\r
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {\r
unchecked {\r
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));\r
require(absTick <= uint256(uint24(MAX_TICK)), "T_OOB");\r
\r
uint256 ratio =\r
(absTick & 0x1 != 0)\r
? 0xfffcb933bd6fad37aa2d162d1a594001\r
: 0x100000000000000000000000000000000;\r
\r
if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;\r
if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;\r
if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;\r
if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;\r
if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;\r
if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;\r
if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;\r
if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;\r
if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;\r
if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;\r
if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;\r
if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;\r
if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;\r
if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;\r
if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;\r
if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;\r
if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;\r
if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;\r
if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;\r
\r
if (tick > 0) ratio = type(uint256).max / ratio;\r
\r
// downshift by 32 and round up to 96-bit precision\r
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));\r
}\r
}\r
}\r
\r
/* -------------------- LiquidityAmounts (lite) ---------------- */\r
library LiquidityAmountsLite {\r
uint256 internal constant Q96 = 2**96;\r
\r
function getAmount0ForLiquidity(\r
uint160 sqrtRatioAX96,\r
uint160 sqrtRatioBX96,\r
uint128 liquidity\r
) internal pure returns (uint256 amount0) {\r
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) =\r
(sqrtRatioBX96, sqrtRatioAX96);\r
\r
uint256 numerator1 = uint256(liquidity) << 96;\r
uint256 numerator2 = uint256(sqrtRatioBX96) - uint256(sqrtRatioAX96);\r
uint256 intermediate = FullMulDiv.mulDiv(numerator1, numerator2, sqrtRatioBX96);\r
amount0 = FullMulDiv.mulDiv(intermediate, 1, sqrtRatioAX96);\r
}\r
\r
function getAmount1ForLiquidity(\r
uint160 sqrtRatioAX96,\r
uint160 sqrtRatioBX96,\r
uint128 liquidity\r
) internal pure returns (uint256 amount1) {\r
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) =\r
(sqrtRatioBX96, sqrtRatioAX96);\r
\r
amount1 = FullMulDiv.mulDiv(uint256(liquidity), uint256(sqrtRatioBX96) - uint256(sqrtRatioAX96), Q96);\r
}\r
\r
function getAmountsForLiquidity(\r
uint160 sqrtRatioX96,\r
uint160 sqrtRatioAX96,\r
uint160 sqrtRatioBX96,\r
uint128 liquidity\r
) internal pure returns (uint256 amount0, uint256 amount1) {\r
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) =\r
(sqrtRatioBX96, sqrtRatioAX96);\r
\r
if (sqrtRatioX96 <= sqrtRatioAX96) {\r
amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);\r
} else if (sqrtRatioX96 < sqrtRatioBX96) {\r
amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);\r
amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);\r
} else {\r
amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);\r
}\r
}\r
}\r
\r
/* ==================================================================== */\r
/* FairLaunchMigrator */\r
/* ==================================================================== */\r
contract FairLaunchMigrator {\r
using SafeERC20 for IERC20;\r
\r
/* ------------------ Immutable config ------------------ */\r
address public immutable token; // launch token address\r
address public immutable pairingToken; // pairing token\r
INonfungiblePositionManager public immutable positionManager;\r
address public immutable v2Router;\r
address public immutable v2Factory; \r
address public immutable v3Factory; \r
uint24 public immutable v3Fee;\r
uint256 public immutable minRaised; // enforced threshold in pairingToken units\r
\r
// V2 fee expressed as numerator/denominator (e.g., 997/1000 for 0.30%)\r
uint256 private immutable V2_FEE_NUM;\r
uint256 private immutable V2_FEE_DEN;\r
\r
/* ------------------ State ------------------------------ */\r
bool public migrated;\r
uint256 public v3NftId; // recorded once\r
\r
/* ------------------ Constants -------------------------- */\r
address internal constant DEAD = 0x000000000000000000000000000000000000dEaD;\r
\r
// Safety margin on requested amountOut to avoid revert if reserves move slightly (0.10%)\r
uint256 private constant OUT_SAFETY_BPS = 10; // 10 bps = 0.10%\r
uint256 private constant BPS_DEN = 10_000;\r
\r
/* ------------------ Events ----------------------------- */\r
\r
event V3NftRecorded(uint256 tokenId);\r
event MigratedToV2(uint256 tokenUsed, uint256 pairingUsed, uint256 lpMinted, address caller);\r
\r
constructor(\r
address _token,\r
address _pairingToken,\r
address _positionMgr,\r
address _v2Router,\r
address _v2Factory, \r
address _v3Factory, \r
uint24 _v3Fee,\r
uint256 _minRaised,\r
uint256 _v2FeeNumerator, \r
uint256 _v2FeeDenominator \r
) {\r
require(\r
_token != address(0) && _pairingToken != address(0) && _positionMgr != address(0)\r
&& _v2Router != address(0) && _v2Factory != address(0),\r
"zero addr"\r
);\r
require(_token != _pairingToken, "same token");\r
\r
// Basic validation for V2 fee\r
require(_v2FeeDenominator != 0, "v2 fee: den=0");\r
require(_v2FeeNumerator < _v2FeeDenominator, "v2 fee: num>=den");\r
\r
token = _token;\r
pairingToken = _pairingToken;\r
positionManager = INonfungiblePositionManager(_positionMgr);\r
v2Router = _v2Router;\r
v2Factory = _v2Factory; \r
v3Factory = _v3Factory;\r
v3Fee = _v3Fee;\r
minRaised = _minRaised;\r
\r
V2_FEE_NUM = _v2FeeNumerator;\r
V2_FEE_DEN = _v2FeeDenominator;\r
}\r
\r
/* ---------------------- Record the NFT (one-shot) ------ */\r
function recordNft(uint256 tokenId) external {\r
require(v3NftId == 0, "NFT already set");\r
\r
// Verify migrator currently owns the NFT at NFPM (ERC721)\r
IERC721Like erc721 = IERC721Like(address(positionManager));\r
require(erc721.ownerOf(tokenId) == address(this), "migrator not owner");\r
\r
// Sanity: token pair + fee match our config\r
(, , address token0, address token1, uint24 fee, , , , , , , ) = positionManager.positions(tokenId);\r
require(fee == v3Fee, "wrong fee");\r
require(\r
(token0 == token && token1 == pairingToken) || (token0 == pairingToken && token1 == token),\r
"wrong pair"\r
);\r
\r
v3NftId = tokenId;\r
emit V3NftRecorded(tokenId);\r
}\r
\r
/* ---------------------- Migrate (one-shot) -------------- */\r
function migrate() external {\r
require(!migrated, "Already migrated");\r
\r
require(v3NftId != 0, "no NFT");\r
\r
// --- Pre-check: ensure minRaised (pairingToken) is met BEFORE pulling V3\r
( ,uint256 wPairing) = _withdrawableNow();\r
\r
require(wPairing >= minRaised, "minRaised not met");\r
\r
// --- Pull all V3 liquidity + fees to this contract\r
address token0;\r
address token1;\r
uint128 liq;\r
{\r
(, , address _t0, address _t1, , , , uint128 _liq, , , , ) = positionManager.positions(v3NftId);\r
token0 = _t0;\r
token1 = _t1;\r
liq = _liq;\r
\r
// Ensure we are authorized to operate the NFT\r
IERC721Like erc721 = IERC721Like(address(positionManager));\r
address owner = erc721.ownerOf(v3NftId);\r
require(\r
owner == address(this) || erc721.getApproved(v3NftId) == address(this)\r
|| erc721.isApprovedForAll(owner, address(this)),\r
"Migrator not approved"\r
);\r
\r
if (liq > 0) {\r
positionManager.decreaseLiquidity(\r
INonfungiblePositionManager.DecreaseLiquidityParams({\r
tokenId: v3NftId,\r
liquidity: liq,\r
amount0Min: 0,\r
amount1Min: 0,\r
deadline: block.timestamp\r
})\r
);\r
}\r
}\r
\r
positionManager.collect(\r
INonfungiblePositionManager.CollectParams({\r
tokenId: v3NftId,\r
recipient: address(this),\r
amount0Max: type(uint128).max,\r
amount1Max: type(uint128).max\r
})\r
);\r
\r
\r
\r
uint256 tokenBal = IERC20(token).balanceOf(address(this));\r
uint256 pairingBal = IERC20(pairingToken).balanceOf(address(this));\r
require(tokenBal > 0 && pairingBal > 0, "Nothing to migrate"); // left as-is intentionally\r
\r
// --- Corrective swap through existing V2 pair (if exists & has reserves)\r
address pair = IUniswapV2Factory(v2Factory).getPair(token, pairingToken); // NEW: use constructor-supplied factory\r
\r
if (pair != address(0)) {\r
(uint112 r0, uint112 r1, ) = IUniswapV2Pair(pair).getReserves();\r
if (r0 > 0 && r1 > 0) {\r
address t0 = IUniswapV2Pair(pair).token0();\r
\r
// Map reserves to (A=launch token, B=pairing token)\r
uint256 Ra = (t0 == token) ? uint256(r0) : uint256(r1);\r
uint256 Rb = (t0 == token) ? uint256(r1) : uint256(r0);\r
\r
// Compare ratios: if A-heavy, swap A->B; if B-heavy, swap B->A\r
// Compare tokenBal / pairingBal vs Ra / Rb using cross-multiplication\r
uint256 left = tokenBal * Rb; // NOTE: not worried about overflow given expected inputs\r
uint256 right = pairingBal * Ra; \r
\r
if (left > right) {\r
// ---- swap A -> B ----\r
(uint256 xIn, uint256 yOut) = _optimalSwapIn(tokenBal, pairingBal, Ra, Rb);\r
if (xIn > 0 && yOut > 0) {\r
// shave safety bps on requested out to avoid revert on tiny reserve movements\r
yOut = (yOut * (BPS_DEN - OUT_SAFETY_BPS)) / BPS_DEN;\r
if (yOut > 0) {\r
IERC20(token).safeTransfer(pair, xIn);\r
if (t0 == token) {\r
IUniswapV2Pair(pair).swap(0, yOut, address(this), new bytes(0));\r
} else {\r
IUniswapV2Pair(pair).swap(yOut, 0, address(this), new bytes(0));\r
}\r
tokenBal = IERC20(token).balanceOf(address(this));\r
pairingBal = IERC20(pairingToken).balanceOf(address(this));\r
}\r
}\r
} else if (left < right) {\r
// ---- swap B -> A ---- (symmetric)\r
(uint256 xInB, uint256 yOutA) = _optimalSwapIn(pairingBal, tokenBal, Rb, Ra);\r
if (xInB > 0 && yOutA > 0) {\r
yOutA = (yOutA * (BPS_DEN - OUT_SAFETY_BPS)) / BPS_DEN;\r
if (yOutA > 0) {\r
IERC20(pairingToken).safeTransfer(pair, xInB);\r
if (t0 == token) {\r
IUniswapV2Pair(pair).swap(yOutA, 0, address(this), new bytes(0));\r
} else {\r
IUniswapV2Pair(pair).swap(0, yOutA, address(this), new bytes(0));\r
}\r
tokenBal = IERC20(token).balanceOf(address(this));\r
pairingBal = IERC20(pairingToken).balanceOf(address(this));\r
}\r
}\r
}\r
}\r
}\r
\r
// --- Add everything to V2, burn LP to DEAD\r
IERC20(token).safeApprove(v2Router, 0);\r
IERC20(pairingToken).safeApprove(v2Router, 0);\r
IERC20(token).safeApprove(v2Router, tokenBal);\r
IERC20(pairingToken).safeApprove(v2Router, pairingBal);\r
\r
(uint256 usedA, uint256 usedB, uint256 lp) = IUniswapV2Router02(v2Router).addLiquidity(\r
pairingToken, // A\r
token, // B\r
pairingBal,\r
tokenBal,\r
0,\r
0,\r
DEAD,\r
block.timestamp\r
);\r
\r
migrated = true;\r
\r
// --- Push leftovers to pair & sync (avoid stranded dust)\r
address pair2 = pair;\r
if (pair2 == address(0)) {\r
pair2 = IUniswapV2Factory(v2Factory).getPair(token, pairingToken); // NEW: use constructor-supplied factory\r
}\r
require(pair2 != address(0), "V2 pair not found");\r
\r
uint256 leftoverToken = IERC20(token).balanceOf(address(this));\r
uint256 leftoverPairing = IERC20(pairingToken).balanceOf(address(this));\r
\r
if (leftoverToken > 0) IERC20(token).safeTransfer(pair2, leftoverToken);\r
if (leftoverPairing > 0) IERC20(pairingToken).safeTransfer(pair2, leftoverPairing);\r
\r
IUniswapV2Pair(pair2).sync();\r
\r
IERC20(token).safeApprove(v2Router, 0);\r
IERC20(pairingToken).safeApprove(v2Router, 0);\r
\r
// fix 1: emit in correct order: tokenUsed = usedB, pairingUsed = usedA\r
emit MigratedToV2(usedB, usedA, lp, msg.sender);\r
}\r
\r
/* ---------------------- Views -------------------------- */\r
function getPool() external view returns (address) {\r
require(v3NftId != 0, "no NFT");\r
(, , address token0, address token1, , , , , , , , ) = positionManager.positions(v3NftId);\r
return IUniswapV3Factory(positionManager.factory()).getPool(token0, token1, v3Fee);\r
}\r
\r
/* ---------------------- Internal helpers --------------- */\r
// --- Robust slot0() reader that works across non-canonical forks.\r
// Reads only the first 32 bytes (sqrtPriceX96) and ignores the rest.\r
function _slot0SqrtP_raw(address pool) private view returns (uint160 sp) {\r
// Call slot0() without using the ABI decoder\r
(bool ok, bytes memory ret) = pool.staticcall(\r
abi.encodeWithSelector(IUniswapV3PoolLike.slot0.selector)\r
);\r
\r
// Must return at least one 32-byte word\r
if (!ok || ret.length < 32) revert("slot0 bad ret");\r
\r
bytes32 w0;\r
assembly { w0 := mload(add(ret, 0x20)) }\r
\r
// Truncate to 160 bits (sqrtPriceX96 is uint160)\r
sp = uint160(uint256(w0));\r
// Optional: guard against nonsense high bits (uncomment if you want strictness)\r
// if (uint256(w0) >> 160 != 0) revert("slot0 sqrtP >160");\r
}\r
\r
// Returns withdrawable amounts (launch token, pairing token) if we pulled all V3 now.\r
// NOTE: unchanged except the slot0() read now uses _slot0SqrtP_raw().\r
function _withdrawableNow() internal view returns (uint256 wToken, uint256 wPairing) {\r
require(v3NftId != 0, "no NFT");\r
\r
(\r
, , \r
address token0, \r
address token1, \r
, \r
int24 tickLower, \r
int24 tickUpper, \r
uint128 liq, \r
, , \r
uint128 owed0, \r
uint128 owed1\r
) = positionManager.positions(v3NftId);\r
\r
// You can use either the immutable v3Factory or NFPM.factory(); both are fine if correct.\r
// Using the immutable here to avoid any surprises from a misconfigured NFPM.\r
address pool = IUniswapV3Factory(v3Factory).getPool(token0, token1, v3Fee);\r
require(pool != address(0) && pool.code.length > 0, "pool=0");\r
\r
uint160 sqrtP = _slot0SqrtP_raw(pool); // <-- robust read\r
\r
uint160 sqrtL = TickMath.getSqrtRatioAtTick(tickLower);\r
uint160 sqrtU = TickMath.getSqrtRatioAtTick(tickUpper);\r
\r
(uint256 amt0Now, uint256 amt1Now) = LiquidityAmountsLite.getAmountsForLiquidity(\r
sqrtP, sqrtL, sqrtU, liq\r
);\r
\r
uint256 withdraw0 = amt0Now + uint256(owed0);\r
uint256 withdraw1 = amt1Now + uint256(owed1);\r
\r
if (token == token0) {\r
wToken = withdraw0;\r
wPairing = withdraw1;\r
} else {\r
wToken = withdraw1;\r
wPairing = withdraw0;\r
}\r
}\r
\r
\r
/// @dev Closed-form optimal swap-in for Uniswap V2 to match post-swap pool price to post-swap wallet ratio.\r
/// Inputs are in terms of A (launch token) and B (pairing token), reserves Ra/Rb, holdings a0/b0.\r
/// Returns (xIn, yOut) for swapping A->B. Symmetry applies if you swap roles for B->A.\r
function _optimalSwapIn(\r
uint256 a0,\r
uint256 b0,\r
uint256 Ra,\r
uint256 Rb\r
) internal view returns (uint256 xIn, uint256 yOut) {\r
if (a0 == 0 || Rb == 0) return (0, 0);\r
\r
uint256 gN = V2_FEE_NUM;\r
uint256 gD = V2_FEE_DEN;\r
\r
uint256 lo = 0;\r
uint256 hi = a0;\r
\r
for (uint i = 0; i < 32; ++i) {\r
uint256 mid = (lo + hi) >> 1;\r
\r
// y(mid) = (γ * mid * Rb) / (Ra + γ * mid)\r
uint256 gmid = gN * mid;\r
uint256 y = (gmid * Rb) / (gD * Ra + gmid);\r
\r
// Compare (a0 - mid)/(b0 + y) ? (Ra + γ*mid/gD)/(Rb - y)\r
// Cross-multiply to avoid division\r
uint256 lhs = (a0 - mid) * (Rb - y);\r
uint256 rhs = (b0 + y) * (Ra + gmid / gD);\r
\r
if (lhs > rhs) lo = mid + 1; // still A-heavy → swap more A\r
else hi = mid;\r
}\r
\r
xIn = hi;\r
uint256 gxi = gN * xIn;\r
yOut = (gxi * Rb) / (gD * Ra + gxi);\r
}\r
\r
/* ---------------------- Math utils --------------------- */\r
function _sqrt(uint256 y) internal pure returns (uint256 z) {\r
if (y == 0) return 0;\r
\r
// Babylonian method\r
uint256 x = y / 2 + 1;\r
z = y;\r
\r
while (x < z) {\r
z = x;\r
x = (y / x + x) / 2;\r
}\r
}\r
\r
function _unsafeMul(uint256 a, uint256 b) private pure returns (uint256) {\r
unchecked {\r
return a * b;\r
}\r
}\r
\r
function _unsafeAdd(uint256 a, uint256 b) private pure returns (uint256) {\r
unchecked {\r
return a + b;\r
}\r
}\r
}\r
"
}
},
"settings": {
"evmVersion": "paris",
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 6000
},
"remappings": [],
"viaIR": true,
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}
}}
Submitted on: 2025-10-30 13:39:47
Comments
Log in to comment.
No comments yet.