LaunchToken
0x3b0263ea75c5217f3b5d7b45602f43ded118d11c
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
1,433 bytes
ABI entries
12
6 read · 3 write
License
none
Contract information
- Address
- 0x3b0263ea75c5217f3b5d7b45602f43ded118d11c
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0xEc1e2c83F4…2Be388294a
- Creation tx
- 0x06641dd9f2…8e9a1e0430
Token
- Name
- Test Alpha
- Symbol
- TSTA
- Decimals
- 18
- Holders
- 3
- Market
- View token page →
Read contract (6)
allowance(address, address) → uint256
balanceOf(address) → uint256
decimals() → uint8
name() → string
symbol() → string
totalSupply() → uint256
Events (2)
ApprovalTransfer
ABI
[
{
"inputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
},
{
"internalType": "string",
"name": "symbol_",
"type": "string"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// One-transaction token launcher for Uniswap V3 on Robinhood Chain (4663).
//
// createToken(name, symbol) payable:
// 1. deploys a fixed-supply 1B ERC20 (no owner, no mint — renounced by construction)
// 2. creates + initializes the TOKEN/WETH 1% pool at ~8.4e-9 ETH per token
// (tick ±186001 — same curve the STIMULUS launch used)
// 3. mints two one-sided LP tranches: MAIN 900M across a 16x price range,
// TAIL (remaining balance) out to the range edge. Both NFTs stay in this
// contract forever — liquidity is locked, creators collect the trading fees.
// 4. if ETH was sent, buys the token atomically (dev buy) to msg.sender.
//
// The frontend discovers the token address from the TokenCreated event.
interface IERC20Like {
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address a) external view returns (uint256);
}
interface INonfungiblePositionManager {
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
function createAndInitializePoolIfNecessary(address token0, address token1, uint24 fee, uint160 sqrtPriceX96)
external payable returns (address pool);
function mint(MintParams calldata params)
external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
function collect(CollectParams calldata params)
external payable returns (uint256 amount0, uint256 amount1);
}
interface ISwapRouter02 {
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);
}
interface IUniswapV3PoolMinimal {
function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, uint8, bool);
function swap(address recipient, bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes calldata data)
external returns (int256 amount0, int256 amount1);
}
contract LaunchToken {
string public name;
string public symbol;
uint8 public constant decimals = 18;
uint256 public constant totalSupply = 1_000_000_000e18;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(string memory name_, string memory symbol_, address to) {
name = name_;
symbol = symbol_;
balanceOf[to] = totalSupply;
emit Transfer(address(0), to, totalSupply);
}
function transfer(address to, uint256 amount) external returns (bool) {
return _transfer(msg.sender, to, amount);
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
uint256 allowed = allowance[from][msg.sender];
if (allowed != type(uint256).max) {
require(allowed >= amount, "allowance");
allowance[from][msg.sender] = allowed - amount;
}
return _transfer(from, to, amount);
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function _transfer(address from, address to, uint256 amount) internal returns (bool) {
require(balanceOf[from] >= amount, "balance");
unchecked {
balanceOf[from] -= amount;
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
}
contract Launcher {
INonfungiblePositionManager public constant NPM =
INonfungiblePositionManager(0x73991a25C818Bf1f1128dEAaB1492D45638DE0D3);
ISwapRouter02 public constant ROUTER = ISwapRouter02(0xCaf681a66D020601342297493863E78C959E5cb2);
address public constant WETH = 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73;
uint24 public constant FEE = 10000; // 1%, tick spacing 200
// Price curve (mirrors the live STIMULUS launch):
// start ~1.19e8 tokens per ETH → 1B supply ≈ 8.4 ETH starting FDV.
// MAIN 900M sits across ticks [158000, 186000] (a 16.4x price climb),
// TAIL covers the rest of the range so the pool never runs dry.
int24 internal constant TICK_A = 158000;
int24 internal constant TICK_B = 186000;
int24 internal constant TICK_EDGE = 887200;
// sqrtPriceX96 at tick +186001 / -186001 (token above / below WETH by address sort)
uint160 internal constant SQRT_HI = 866239624055612229356474014367744;
uint160 internal constant SQRT_LO = 7246380286782740616495968;
uint256 internal constant MAIN_AMOUNT = 900_000_000e18;
struct Launch {
address creator;
address pool;
uint256 mainId;
uint256 tailId;
}
mapping(address => Launch) public launches;
address[] public allTokens;
address public owner;
// The frontend matches /create|launch/i on the event name and reads args.token.
event TokenCreated(address indexed creator, address indexed token, string name, string symbol);
event FeesCollected(address indexed token, address indexed creator, uint256 amount0, uint256 amount1);
event OwnershipTransferred(address indexed from, address indexed to);
event Rescued(address indexed token, address indexed to, uint256 amount);
address internal expectedPool; // transient guard for the swap callback
constructor() {
owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
function transferOwnership(address to) external onlyOwner {
emit OwnershipTransferred(owner, to);
owner = to;
}
function tokenCount() external view returns (uint256) {
return allTokens.length;
}
// --- Rescue (safety net) -------------------------------------------------
// Recovers ERC20s or ETH that get accidentally sent to this contract.
// It CANNOT touch launched liquidity: the LP positions are Uniswap v3
// ERC721 NFTs, and there is no function anywhere in this contract that
// transfers those NFTs or reduces their liquidity — so every launch's LP
// stays permanently locked, rescue or not.
function rescueERC20(address token, address to, uint256 amount) external onlyOwner {
IERC20Like(token).transfer(to, amount);
emit Rescued(token, to, amount);
}
function rescueETH(address to, uint256 amount) external onlyOwner {
(bool ok, ) = payable(to).call{value: amount}("");
require(ok, "eth transfer failed");
emit Rescued(address(0), to, amount);
}
receive() external payable {} // accept stray ETH so it can be rescued
function createToken(string calldata name, string calldata symbol) external payable returns (address token) {
// Salt includes block.number so a griefed (pre-created, badly priced) pool
// never permanently blocks the same predicted address.
bytes32 salt = keccak256(abi.encode(msg.sender, name, symbol, block.number, allTokens.length));
token = address(new LaunchToken{salt: salt}(name, symbol, address(this)));
bool tokenIs1 = token > WETH;
(address t0, address t1) = tokenIs1 ? (WETH, token) : (token, WETH);
uint160 sqrtPrice = tokenIs1 ? SQRT_HI : SQRT_LO;
address pool = NPM.createAndInitializePoolIfNecessary(t0, t1, FEE, sqrtPrice);
_ensureStartPrice(pool, tokenIs1, sqrtPrice);
IERC20Like(token).approve(address(NPM), type(uint256).max);
uint256 mainId = _mintSide(t0, t1, tokenIs1,
tokenIs1 ? TICK_A : -TICK_B,
tokenIs1 ? TICK_B : -TICK_A,
MAIN_AMOUNT);
uint256 tailId = _mintSide(t0, t1, tokenIs1,
tokenIs1 ? -TICK_EDGE : -TICK_A,
tokenIs1 ? TICK_A : TICK_EDGE,
IERC20Like(token).balanceOf(address(this)));
launches[token] = Launch(msg.sender, pool, mainId, tailId);
allTokens.push(token);
emit TokenCreated(msg.sender, token, name, symbol);
if (msg.value > 0) {
ROUTER.exactInputSingle{value: msg.value}(ISwapRouter02.ExactInputSingleParams({
tokenIn: WETH,
tokenOut: token,
fee: FEE,
recipient: msg.sender,
amountIn: msg.value,
amountOutMinimum: 0, // atomic with pool creation — nothing can interpose
sqrtPriceLimitX96: 0
}));
}
}
/// LP fees accrue to the locked positions; the token's creator can sweep them.
function collectFees(address token) external returns (uint256 amount0, uint256 amount1) {
Launch storage l = launches[token];
require(msg.sender == l.creator, "not creator");
(uint256 a0, uint256 a1) = NPM.collect(INonfungiblePositionManager.CollectParams(
l.mainId, l.creator, type(uint128).max, type(uint128).max));
(uint256 b0, uint256 b1) = NPM.collect(INonfungiblePositionManager.CollectParams(
l.tailId, l.creator, type(uint128).max, type(uint128).max));
amount0 = a0 + b0;
amount1 = a1 + b1;
emit FeesCollected(token, l.creator, amount0, amount1);
}
function _mintSide(address t0, address t1, bool tokenIs1, int24 lower, int24 upper, uint256 amount)
internal returns (uint256 tokenId)
{
(tokenId,,,) = NPM.mint(INonfungiblePositionManager.MintParams({
token0: t0,
token1: t1,
fee: FEE,
tickLower: lower,
tickUpper: upper,
amount0Desired: tokenIs1 ? 0 : amount,
amount1Desired: tokenIs1 ? amount : 0,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
}));
}
// If someone pre-created the pool at a different price (it had to be priced
// before our token existed, so it holds no liquidity we care about), walk the
// price back to our start via a swap through the empty book. Crossing zero
// liquidity owes ~nothing; any dust owed is paid from our 1B token balance,
// which the balance-based TAIL mint then absorbs.
function _ensureStartPrice(address pool, bool tokenIs1, uint160 target) internal {
(uint160 current,,,,,,) = IUniswapV3PoolMinimal(pool).slot0();
if (current == target) return;
expectedPool = pool;
// moving toward target: zeroForOne=true pushes sqrtPrice down
bool zeroForOne = current > target;
// paying side must be our token, which we hold — never WETH
require(zeroForOne != tokenIs1, "pool griefed: bad direction");
IUniswapV3PoolMinimal(pool).swap(address(this), zeroForOne, int256(1), target, "");
expectedPool = address(0);
(current,,,,,,) = IUniswapV3PoolMinimal(pool).slot0();
require(current == target, "pool griefed: price stuck");
// token side of a fresh launch must be fully one-sided
(, int24 tick,,,,,) = IUniswapV3PoolMinimal(pool).slot0();
require(tokenIs1 ? tick >= TICK_B : tick < -TICK_B, "pool griefed: tick");
}
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata) external {
require(msg.sender == expectedPool, "bad callback");
if (amount0Delta > 0) {
IERC20Like(IUniswapV3PoolLike(msg.sender).token0()).transfer(msg.sender, uint256(amount0Delta));
}
if (amount1Delta > 0) {
IERC20Like(IUniswapV3PoolLike(msg.sender).token1()).transfer(msg.sender, uint256(amount1Delta));
}
}
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
return this.onERC721Received.selector;
}
}
interface IUniswapV3PoolLike {
function token0() external view returns (address);
function token1() external view returns (address);
}
Chain explorer3404msChain node109ms