InstantPoolLauncher
0x9eab33527fbfb6bffb918f7e18e797fe303a77d9
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
11,212 bytes
ABI entries
28
11 read · 3 write
License
none
Contract information
- Address
- 0x9eab33527fbfb6bffb918f7e18e797fe303a77d9
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0xC3980E7514…B115C2BcE3
- Creation tx
- 0x2fa5e0ff9c…4e473622e7
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (11)
DEFAULT_CREATOR_SHARE_BPS() → uint16
MAX_CREATOR_SHARE_BPS() → uint16
getLaunch(uint256) → tuple
hasPosition(address) → bool
initCodeHash(string, string, address) → bytes32
launchCount() → uint256
launches(uint256) → address, address, address, string, uint256, uint256
positions(uint256) → address, address, uint16
protocolTreasury() → address
tokenPosition(address) → uint256
tokenToLaunchId(address) → uint256
Events (2)
FeesCollectedInstantLaunched
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "protocolTreasury_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "BadName",
"type": "error"
},
{
"inputs": [],
"name": "BadShare",
"type": "error"
},
{
"inputs": [],
"name": "BadSymbol",
"type": "error"
},
{
"inputs": [],
"name": "BadTicks",
"type": "error"
},
{
"inputs": [],
"name": "DevBuyUnderpaid",
"type": "error"
},
{
"inputs": [],
"name": "NotToken0",
"type": "error"
},
{
"inputs": [],
"name": "PoolExists",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "RefundFailed",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethToCreator",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethToProtocol",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenToCreator",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenToProtocol",
"type": "uint256"
}
],
"name": "FeesCollected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "launchId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "InstantLaunched",
"type": "event"
},
{
"inputs": [],
"name": "DEFAULT_CREATOR_SHARE_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_CREATOR_SHARE_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "collect",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "collectForToken",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "getLaunch",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "string",
"name": "metadataUri",
"type": "string"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "createdAt",
"type": "uint256"
}
],
"internalType": "struct InstantPoolLauncher.LaunchInfo",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "hasPosition",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
},
{
"internalType": "string",
"name": "symbol_",
"type": "string"
},
{
"internalType": "address",
"name": "creator_",
"type": "address"
}
],
"name": "initCodeHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "metadataUri",
"type": "string"
},
{
"internalType": "uint256",
"name": "tokenSupply",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint16",
"name": "creatorShareBps",
"type": "uint16"
},
{
"internalType": "uint256",
"name": "developerBuyWei",
"type": "uint256"
},
{
"internalType": "address",
"name": "feeRecipient",
"type": "address"
}
],
"internalType": "struct InstantPoolLauncher.InstantConfig",
"name": "cfg",
"type": "tuple"
}
],
"name": "launch",
"outputs": [
{
"internalType": "address",
"name": "tokenAddr",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "launchCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "launches",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "string",
"name": "metadataUri",
"type": "string"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "createdAt",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "positions",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "feeRecipient",
"type": "address"
},
{
"internalType": "uint16",
"name": "creatorShareBps",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolTreasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "tokenPosition",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "tokenToLaunchId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {LaunchedToken} from "./LaunchedToken.sol";
import {RobinhoodAddresses} from "./RobinhoodAddresses.sol";
import {IUniswapV3Factory, IUniswapV3Pool, INonfungiblePositionManager} from "./interfaces/Uniswap.sol";
import {IWETH, ISwapRouter02} from "./interfaces/External.sol";
/// @title InstantPoolLauncher — launches a token straight into a Uniswap V3 pool.
/// @notice Instead of a bonding curve, the full supply is deposited as single-sided
/// concentrated liquidity (all token, zero ETH) from a low starting price up to a
/// target. The token is instantly tradeable on any Uniswap V3 aggregator (GMGN,
/// Dexscreener, wallets) from block one. The LP NFT is held here forever — there is
/// no withdraw/decreaseLiquidity path, so liquidity is unruggable — but its swap fees
/// are permissionlessly collectible, split between the creator and the protocol.
///
/// Price math is computed off-chain and passed in (sqrtPriceX96, tickLower, tickUpper).
/// To keep that math unambiguous the token is CREATE2-deployed with a salt mined so its
/// address sorts below WETH (token == token0), so price is always WETH-per-token.
contract InstantPoolLauncher is ReentrancyGuard {
using SafeERC20 for IERC20;
address public immutable protocolTreasury;
uint16 public constant MAX_CREATOR_SHARE_BPS = 9_000;
uint16 public constant DEFAULT_CREATOR_SHARE_BPS = 5_000;
struct InstantConfig {
string name;
string symbol;
string metadataUri;
uint256 tokenSupply; // 0 => 1e9 * 1e18
bytes32 salt; // CREATE2 salt, mined so token address < WETH
uint160 sqrtPriceX96; // initial pool price (= sqrt ratio at tickLower)
int24 tickLower;
int24 tickUpper;
uint16 creatorShareBps; // creator share of LP fees; 0 => default
uint256 developerBuyWei; // optional immediate buy through the pool
address feeRecipient; // creator wallet for LP fees; 0 => msg.sender
}
struct LaunchInfo {
address token;
address pool;
address creator;
string metadataUri;
uint256 tokenId;
uint256 createdAt;
}
struct Position {
address token;
address feeRecipient;
uint16 creatorShareBps;
}
LaunchInfo[] public launches;
mapping(address => uint256) public tokenToLaunchId;
mapping(uint256 => Position) public positions;
mapping(address => uint256) public tokenPosition;
mapping(address => bool) public hasPosition;
event InstantLaunched(
uint256 indexed launchId,
address indexed token,
address indexed pool,
address creator,
uint256 tokenId
);
event FeesCollected(
address indexed token,
uint256 indexed tokenId,
uint256 ethToCreator,
uint256 ethToProtocol,
uint256 tokenToCreator,
uint256 tokenToProtocol
);
error BadName();
error BadSymbol();
error BadShare();
error BadTicks();
error NotToken0();
error PoolExists();
error DevBuyUnderpaid();
error RefundFailed();
constructor(address protocolTreasury_) {
require(protocolTreasury_ != address(0), "treasury");
protocolTreasury = protocolTreasury_;
}
/// @notice Init-code hash for CREATE2 address prediction / salt mining off-chain.
function initCodeHash(string calldata name_, string calldata symbol_, address creator_)
external
view
returns (bytes32)
{
return keccak256(
abi.encodePacked(type(LaunchedToken).creationCode, abi.encode(name_, symbol_, creator_, address(this)))
);
}
function launch(InstantConfig calldata cfg)
external
payable
nonReentrant
returns (address tokenAddr, address pool, uint256 tokenId)
{
if (bytes(cfg.name).length == 0 || bytes(cfg.name).length > 30) revert BadName();
if (bytes(cfg.symbol).length == 0 || bytes(cfg.symbol).length > 10) revert BadSymbol();
uint16 share = cfg.creatorShareBps == 0 ? DEFAULT_CREATOR_SHARE_BPS : cfg.creatorShareBps;
if (share > MAX_CREATOR_SHARE_BPS) revert BadShare();
if (cfg.tickLower >= cfg.tickUpper) revert BadTicks();
uint256 supply = cfg.tokenSupply == 0 ? 1_000_000_000 ether : cfg.tokenSupply;
address weth = RobinhoodAddresses.WETH;
LaunchedToken token = new LaunchedToken{salt: cfg.salt}(cfg.name, cfg.symbol, msg.sender, address(this));
tokenAddr = address(token);
// Single-sided math assumes token == token0; caller mines a salt so this holds.
if (tokenAddr >= weth) revert NotToken0();
token.setCurve(address(this));
token.mintToCurve(supply); // mints the full supply to this launcher
IUniswapV3Factory f = IUniswapV3Factory(RobinhoodAddresses.V3_FACTORY);
if (f.getPool(tokenAddr, weth, RobinhoodAddresses.FEE_TIER) != address(0)) revert PoolExists();
pool = f.createPool(tokenAddr, weth, RobinhoodAddresses.FEE_TIER);
IUniswapV3Pool(pool).initialize(cfg.sqrtPriceX96);
tokenId = _mintSingleSided(tokenAddr, weth, supply, cfg.tickLower, cfg.tickUpper);
address feeRecipient = cfg.feeRecipient == address(0) ? msg.sender : cfg.feeRecipient;
positions[tokenId] = Position({token: tokenAddr, feeRecipient: feeRecipient, creatorShareBps: share});
tokenPosition[tokenAddr] = tokenId;
hasPosition[tokenAddr] = true;
uint256 launchId = launches.length;
launches.push(
LaunchInfo({
token: tokenAddr,
pool: pool,
creator: msg.sender,
metadataUri: cfg.metadataUri,
tokenId: tokenId,
createdAt: block.timestamp
})
);
tokenToLaunchId[tokenAddr] = launchId;
emit InstantLaunched(launchId, tokenAddr, pool, msg.sender, tokenId);
if (cfg.developerBuyWei > 0) {
if (msg.value < cfg.developerBuyWei) revert DevBuyUnderpaid();
IWETH(weth).deposit{value: cfg.developerBuyWei}();
IERC20(weth).forceApprove(RobinhoodAddresses.SWAP_ROUTER, cfg.developerBuyWei);
ISwapRouter02(RobinhoodAddresses.SWAP_ROUTER).exactInputSingle(
ISwapRouter02.ExactInputSingleParams({
tokenIn: weth,
tokenOut: tokenAddr,
fee: RobinhoodAddresses.FEE_TIER,
recipient: msg.sender,
amountIn: cfg.developerBuyWei,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
})
);
}
if (msg.value > cfg.developerBuyWei) {
(bool ok,) = msg.sender.call{value: msg.value - cfg.developerBuyWei}("");
if (!ok) revert RefundFailed();
}
}
function _mintSingleSided(address token, address weth, uint256 supply, int24 tickLower, int24 tickUpper)
internal
returns (uint256 tokenId)
{
IERC20(token).forceApprove(RobinhoodAddresses.POSITION_MANAGER, supply);
(tokenId,,,) = INonfungiblePositionManager(RobinhoodAddresses.POSITION_MANAGER).mint(
INonfungiblePositionManager.MintParams({
token0: token,
token1: weth,
fee: RobinhoodAddresses.FEE_TIER,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: supply,
amount1Desired: 0,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
})
);
}
function collectForToken(address token) external returns (uint256 amount0, uint256 amount1) {
require(hasPosition[token], "no position");
return _collect(tokenPosition[token]);
}
function collect(uint256 tokenId) external returns (uint256 amount0, uint256 amount1) {
return _collect(tokenId);
}
function _collect(uint256 tokenId) internal nonReentrant returns (uint256 amount0, uint256 amount1) {
Position memory pos = positions[tokenId];
require(pos.token != address(0), "unknown position");
(amount0, amount1) = INonfungiblePositionManager(RobinhoodAddresses.POSITION_MANAGER).collect(
INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
// token is token0 (enforced at launch), WETH is token1.
uint256 tokenAmt = amount0;
uint256 wethAmt = amount1;
address creator = pos.feeRecipient;
address protocol = protocolTreasury;
uint16 share = pos.creatorShareBps;
uint256 ethToCreator;
uint256 ethToProtocol;
uint256 tokenToCreator;
uint256 tokenToProtocol;
if (wethAmt > 0) {
IWETH(RobinhoodAddresses.WETH).withdraw(wethAmt);
ethToCreator = (wethAmt * share) / 10_000;
ethToProtocol = wethAmt - ethToCreator;
if (ethToCreator > 0) {
(bool ok,) = creator.call{value: ethToCreator}("");
require(ok, "eth creator");
}
if (ethToProtocol > 0) {
(bool ok,) = protocol.call{value: ethToProtocol}("");
require(ok, "eth protocol");
}
}
if (tokenAmt > 0) {
tokenToCreator = (tokenAmt * share) / 10_000;
tokenToProtocol = tokenAmt - tokenToCreator;
if (tokenToCreator > 0) IERC20(pos.token).safeTransfer(creator, tokenToCreator);
if (tokenToProtocol > 0) IERC20(pos.token).safeTransfer(protocol, tokenToProtocol);
}
emit FeesCollected(pos.token, tokenId, ethToCreator, ethToProtocol, tokenToCreator, tokenToProtocol);
}
function launchCount() external view returns (uint256) {
return launches.length;
}
function getLaunch(uint256 id) external view returns (LaunchInfo memory) {
return launches[id];
}
receive() external payable {}
}
Chain explorer2973msChain node82ms