PumpFactoryV3Seeded
0x6aa203ea119b3bc0a857071056b91a0e07bd66ee
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
9,061 bytes
ABI entries
20
14 read · 1 write
License
none
Contract information
- Address
- 0x6aa203ea119b3bc0a857071056b91a0e07bd66ee
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0x95948eb191…afA070d698
- Creation tx
- 0x64a1994269…dd51856199
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (14)
BPS() → uint256
DUST_BPS() → uint256
FEE() → uint24
MAX_USABLE_TICK() → int24
SPACING() → int24
SUPPLY() → uint256
WETH() → address
locker() → address
nfpm() → address
poolOf(address) → address
predictTokenAddress(bytes32, address) → address
tokenImplementation() → address
usedSalts(bytes32) → bool
v3Factory() → address
Events (1)
TokenLaunchedSeeded
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_tokenImplementation",
"type": "address"
},
{
"internalType": "address",
"name": "_v3Factory",
"type": "address"
},
{
"internalType": "address",
"name": "_nfpm",
"type": "address"
},
{
"internalType": "address",
"name": "_weth",
"type": "address"
},
{
"internalType": "address",
"name": "_locker",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "FailedDeployment",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "InsufficientBalance",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "seedingContract",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "posId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "seededTokens",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "floatTokens",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethSeeded",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
}
],
"name": "TokenLaunchedSeeded",
"type": "event"
},
{
"inputs": [],
"name": "BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DUST_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_USABLE_TICK",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SPACING",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "WETH",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "bytes32",
"name": "xpad",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "seededTokens",
"type": "uint256"
},
{
"internalType": "address",
"name": "seedingContract",
"type": "address"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
}
],
"name": "launchSeeded",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "uint256",
"name": "posId",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "locker",
"outputs": [
{
"internalType": "contract FeeLockerHolderYield",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "nfpm",
"outputs": [
{
"internalType": "contract INonfungiblePositionManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "poolOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "xpad",
"type": "bytes32"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
}
],
"name": "predictTokenAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenImplementation",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "usedSalts",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "v3Factory",
"outputs": [
{
"internalType": "contract IUniswapV3Factory",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IERC20} from "./interfaces/IERC20.sol";
import {IWETH} from "./interfaces/IWETH.sol";
import {IUniswapV3Factory} from "./interfaces/IUniswapV3Factory.sol";
import {IUniswapV3Pool} from "./interfaces/IUniswapV3Pool.sol";
import {INonfungiblePositionManager} from "./interfaces/INonfungiblePositionManager.sol";
import {TickMath} from "./libraries/TickMath.sol";
import {XpadTokenV3} from "./XpadTokenV3.sol";
import {FeeLockerHolderYield} from "./FeeLockerHolderYield.sol";
/**
* @title PumpFactoryV3Seeded
* @notice Two-sided variant of PumpFactoryV3 for the Community Seeder Pool (see SeederPool.sol).
* Instead of a single-sided token-only launch, it splits the supply and mints a TWO-SIDED,
* forever-locked V3 position from the remaining float PLUS the seeded ETH.
*
* launchSeeded (called only by a SeederPool): deploy def1 clone + mint SUPPLY to this factory ->
* transfer the seeded share (80-90%) to the seeding contract -> wrap the seeded ETH -> compute the
* pool price ON-CHAIN from (float tokens, seeded WETH) so there is no off-chain price param and no
* seat-count race -> create+initialize the pool -> full-range two-sided NFPM.mint (float + WETH)
* recipient = FeeLockerHolderYield -> register -> sweep bounded dust to the creator -> emit poolId.
*
* PRICE. The initial marginal price P = amount1/amount0 (raw units, token0/token1 sorted). A
* full-range position deposits token0:token1 in exactly the ratio 1:P, so setting
* sqrtPriceX96 = floor(sqrt(P) * 2^96) = floor(sqrt(amount1 * 2^192 / amount0)) makes the mint
* consume both provided amounts up to sub-unit rounding; the tiny remainder is swept. Everything is
* derived from the amounts read at launch, so launch() is deterministic. Same front-run tolerance as
* PumpFactoryV3: a griefer can pre-create/pre-init the (predictable) pool, but the post-init asserts
* are the sole price guard - a wrong pre-init price reverts, a correct one is harmless.
*
* The locker is FeeLockerHolderYield (40% creator / 35% treasury / 25% holderDistributor), so the
* seeding contract, as an 80-90% holder, receives its share of the holder-yield slice which it then
* splits equally among seeders. Immutable wiring; no owner, no upgrade.
*/
contract PumpFactoryV3Seeded is ReentrancyGuard {
// ─── Immutable wiring ─────────────────────────────────────────────
address public immutable tokenImplementation;
IUniswapV3Factory public immutable v3Factory;
INonfungiblePositionManager public immutable nfpm;
address public immutable WETH;
FeeLockerHolderYield public immutable locker;
// ─── Constants ────────────────────────────────────────────────────
uint24 public constant FEE = 10000; // 1%
int24 public constant SPACING = 200;
uint256 public constant SUPPLY = 1_000_000_000 * 1e18; // 1e27
int24 public constant MAX_USABLE_TICK = 887200; // floor(887272 / 200) * 200 (full-range bound)
// Per-side dust tolerance for the two-sided mint. Real remainder from the sqrt floor is a few
// wei; 1% is a loose ceiling that still reverts loudly on a gross imbalance. The NFPM amountMin
// floor and the residual ceiling use the SAME bound (invariant: a mint that clears amountMin
// leaves residual <= the ceiling), mirroring PumpFactoryV3's single-sided dust discipline.
uint256 public constant DUST_BPS = 100; // 1%
uint256 public constant BPS = 10000;
mapping(bytes32 => bool) public usedSalts;
mapping(address => address) public poolOf;
event TokenLaunchedSeeded(
address indexed token,
address indexed pool,
address indexed creator,
address seedingContract,
uint256 posId,
uint256 seededTokens,
uint256 floatTokens,
uint256 ethSeeded,
uint160 sqrtPriceX96
);
constructor(address _tokenImplementation, address _v3Factory, address _nfpm, address _weth, address _locker) {
require(_tokenImplementation != address(0), "impl zero");
require(_v3Factory != address(0), "v3Factory zero");
require(_nfpm != address(0), "nfpm zero");
require(_weth != address(0), "weth zero");
require(_locker != address(0), "locker zero");
tokenImplementation = _tokenImplementation;
v3Factory = IUniswapV3Factory(_v3Factory);
nfpm = INonfungiblePositionManager(_nfpm);
WETH = _weth;
locker = FeeLockerHolderYield(_locker);
}
/**
* @notice Atomic two-sided seeded launch. `msg.value` is the seeded ETH (all of it goes into the
* locked LP). Called by a SeederPool; `seedingContract` receives the seeded share and
* `creator` is the fee-recipient/vanity owner.
*/
function launchSeeded(
string calldata name,
string calldata symbol,
bytes32 xpad,
uint256 seededTokens,
address seedingContract,
address creator
) external payable nonReentrant returns (address token, address pool, uint256 posId) {
require(msg.value > 0, "no seed ETH");
require(seedingContract != address(0), "seeding zero");
require(creator != address(0), "creator zero");
require(seededTokens > 0 && seededTokens < SUPPLY, "bad seeded amount");
uint256 floatTokens = SUPPLY - seededTokens;
// (a) Deploy token clone (def1 vanity from the creator) + mint SUPPLY to this factory.
token = _deployToken(name, symbol, creator, xpad);
// (b) Attribute the seeded share to the seeding contract (locked there forever).
require(IERC20(token).transfer(seedingContract, seededTokens), "seed transfer");
// (c) Wrap the seeded ETH so both LP sides are ERC-20.
IWETH(WETH).deposit{value: msg.value}();
// (d) On-chain price -> create/init -> full-range two-sided mint -> register -> sweep dust.
uint160 sqrtPriceX96;
(pool, posId, sqrtPriceX96) = _initAndMint(token, creator, floatTokens, msg.value);
poolOf[token] = pool;
emit TokenLaunchedSeeded(
token, pool, creator, seedingContract, posId, seededTokens, floatTokens, msg.value, sqrtPriceX96
);
}
// ─── Steps ────────────────────────────────────────────────────────
function _deployToken(string calldata name, string calldata symbol, address creator, bytes32 xpad)
private
returns (address token)
{
if (xpad == bytes32(0)) {
token = Clones.clone(tokenImplementation);
} else {
// Vanity salt scoped to the creator (the dev), not the SeederPool caller, so the def1
// address is dev-predictable exactly like the single-sided launcher.
bytes32 eff = keccak256(abi.encodePacked(creator, xpad));
require(!usedSalts[eff], "Salt already used");
usedSalts[eff] = true;
token = Clones.cloneDeterministic(tokenImplementation, eff);
}
XpadTokenV3(token).initialize(name, symbol, creator, SUPPLY); // mints SUPPLY to this factory
require(IERC20(token).balanceOf(address(this)) == SUPPLY, "mint mismatch");
}
function _initAndMint(address token, address creator, uint256 floatTokens, uint256 wethAmt)
private
returns (address pool, uint256 posId, uint160 sqrtPriceX96)
{
bool tokenIsToken0 = token < WETH;
(address token0, address token1) = tokenIsToken0 ? (token, WETH) : (WETH, token);
(uint256 amount0, uint256 amount1) = tokenIsToken0 ? (floatTokens, wethAmt) : (wethAmt, floatTokens);
// sqrtPriceX96 = floor(sqrt(P) * 2^96), P = amount1/amount0 => sqrt(amount1 * 2^192 / amount0).
// Math.mulDiv gives full 512-bit precision so amount1 * 2^192 never overflows the intermediate.
uint256 priceX192 = Math.mulDiv(amount1, 1 << 192, amount0);
uint256 s = Math.sqrt(priceX192);
require(s >= TickMath.MIN_SQRT_RATIO && s < TickMath.MAX_SQRT_RATIO, "price out of range");
sqrtPriceX96 = uint160(s);
// Create + init, tolerating a front-run pre-create/pre-init; the asserts below are the guard.
pool = v3Factory.getPool(token, WETH, FEE);
if (pool == address(0)) pool = v3Factory.createPool(token, WETH, FEE);
require(pool != address(0), "createPool failed");
(uint160 existing,,,,,,) = IUniswapV3Pool(pool).slot0();
if (existing == 0) IUniswapV3Pool(pool).initialize(sqrtPriceX96);
(uint160 gotSqrt, int24 gotTick,,,,,) = IUniswapV3Pool(pool).slot0();
require(gotSqrt == sqrtPriceX96, "sqrt mismatch");
require(gotTick == TickMath.getTickAtSqrtRatio(sqrtPriceX96), "tick mismatch");
require(IUniswapV3Pool(pool).liquidity() == 0, "liquidity nonzero");
// Full-range two-sided position. The current tick must sit strictly inside for BOTH sides to
// be pulled (else the mint is single-sided and reverts on amountMin below).
int24 tickLower = -MAX_USABLE_TICK;
int24 tickUpper = MAX_USABLE_TICK;
require(gotTick > tickLower && gotTick < tickUpper, "tick outside range");
uint256 amt0Min = amount0 - (amount0 * DUST_BPS) / BPS;
uint256 amt1Min = amount1 - (amount1 * DUST_BPS) / BPS;
IERC20(token).approve(address(nfpm), floatTokens);
IWETH(WETH).approve(address(nfpm), wethAmt);
uint256 used0;
uint256 used1;
(posId,, used0, used1) = nfpm.mint(
INonfungiblePositionManager.MintParams({
token0: token0,
token1: token1,
fee: FEE,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: amount0,
amount1Desired: amount1,
amount0Min: amt0Min,
amount1Min: amt1Min,
recipient: address(locker),
deadline: block.timestamp
})
);
IERC20(token).approve(address(nfpm), 0);
IWETH(WETH).approve(address(nfpm), 0);
locker.register(posId, token, creator);
// Sweep only the UNUSED portion of what WE provided, computed from the mint's returned used
// amounts - NOT balanceOf(this). WETH is a shared, donatable asset on this predictable
// singleton; a balanceOf-based residual would let a griefer donate >1% WETH to trip the
// residual assert and brick every launch. Using `desired - used` ignores donations entirely
// (they stay inert in the factory, the griefer's loss) and the launch proceeds.
(uint256 usedToken, uint256 usedWeth) = tokenIsToken0 ? (used0, used1) : (used1, used0);
_sweep(token, creator, floatTokens - usedToken, floatTokens);
_sweep(WETH, creator, wethAmt - usedWeth, wethAmt);
}
/// @dev Send an exact residual to `to`, bounded to DUST_BPS of what was provided (mirrors the
/// amountMin floor: a mint that clears amountMin leaves residual <= this ceiling).
function _sweep(address asset, address to, uint256 residual, uint256 provided) private {
require(residual <= (provided * DUST_BPS) / BPS, "residual too high");
if (residual > 0) require(IERC20(asset).transfer(to, residual), "sweep");
}
// ─── Views ────────────────────────────────────────────────────────
function predictTokenAddress(bytes32 xpad, address creator) external view returns (address) {
bytes32 eff = keccak256(abi.encodePacked(creator, xpad));
return Clones.predictDeterministicAddress(tokenImplementation, eff, address(this));
}
}
Chain explorer3263msChain node92ms