TokenLauncher
0x9e2d2581fc04cae50c21b4ba7b44b9a099c4fe6f
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
7,386 bytes
ABI entries
36
13 read · 9 write
License
none
Contract information
- Address
- 0x9e2d2581fc04cae50c21b4ba7b44b9a099c4fe6f
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0xd1C1167a56…6dCcBd202A
- Creation tx
- 0xe07776495c…a9b332a57a
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (13)
TOTAL_SUPPLY() → uint256
complianceGate() → address
configRegistry() → address
dexAdapters(uint256) → address
feeRouter() → address
lastLaunchAt(address) → uint256
launchCooldown() → uint256
lpLocker() → address
owner() → address
paused() → bool
swapRouter() → address
tokenImplementation() → address
weth() → address
Events (6)
DexAdapterSetLaunchCooldownSetOwnershipTransferredPausedTokenLaunchedUnpaused
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "owner_",
"type": "address"
},
{
"internalType": "address",
"name": "weth_",
"type": "address"
},
{
"internalType": "address",
"name": "swapRouter_",
"type": "address"
},
{
"internalType": "address",
"name": "treasury_",
"type": "address"
},
{
"internalType": "address",
"name": "development_",
"type": "address"
},
{
"internalType": "contract INonfungiblePositionManager",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "contract ConfigRegistry",
"name": "configRegistry_",
"type": "address"
},
{
"internalType": "contract IComplianceGate",
"name": "complianceGate_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "EnforcedPause",
"type": "error"
},
{
"inputs": [],
"name": "ExpectedPause",
"type": "error"
},
{
"inputs": [],
"name": "FailedDeployment",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "InsufficientBalance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "dexId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "adapter",
"type": "address"
}
],
"name": "DexAdapterSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "cooldown",
"type": "uint256"
}
],
"name": "LaunchCooldownSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "configId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "dexId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "ipfsMetadata",
"type": "string"
}
],
"name": "TokenLaunched",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [],
"name": "TOTAL_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "complianceGate",
"outputs": [
{
"internalType": "contract IComplianceGate",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "configRegistry",
"outputs": [
{
"internalType": "contract ConfigRegistry",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "dexAdapters",
"outputs": [
{
"internalType": "contract IDexAdapter",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeRouter",
"outputs": [
{
"internalType": "contract FeeRouter",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "lastLaunchAt",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launchCooldown",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "ipfsMetadata",
"type": "string"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "uint256",
"name": "configId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dexId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "initialBuyWeth",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"name": "launchToken",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "lpLocker",
"outputs": [
{
"internalType": "contract LPLocker",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IComplianceGate",
"name": "gate",
"type": "address"
}
],
"name": "setComplianceGate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ConfigRegistry",
"name": "registry",
"type": "address"
}
],
"name": "setConfigRegistry",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "dexId",
"type": "uint256"
},
{
"internalType": "contract IDexAdapter",
"name": "adapter",
"type": "address"
}
],
"name": "setDexAdapter",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "cooldown",
"type": "uint256"
}
],
"name": "setLaunchCooldown",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "swapRouter",
"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": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "unpause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { INonfungiblePositionManager, ISwapRouter, IWETH9 } from "./interfaces/external/IUniswapV3.sol";
import { IComplianceGate } from "./interfaces/IComplianceGate.sol";
import { IDexAdapter } from "./interfaces/IDexAdapter.sol";
import { ConfigRegistry } from "./ConfigRegistry.sol";
import { FeeRouter } from "./FeeRouter.sol";
import { LPLocker } from "./LPLocker.sol";
import { ChadToken } from "./ChadToken.sol";
/// @notice The launchpad entrypoint. A single `launchToken` call deploys a fixed-supply token,
/// creates a Uniswap V3 pool with single-sided liquidity, and permanently locks the LP —
/// mirroring NOXA's `launchToken(name, symbol, ipfsMetadata, creator, configId, dexId)` shape.
///
/// Anti-abuse: launches pass through a pluggable compliance gate (blocklist by default), a
/// per-sender cooldown, a non-refundable creation fee, and launch-metadata validation. The whole
/// entrypoint is pausable (incident kill-switch) and reentrancy-guarded.
contract TokenLauncher is Ownable, Pausable, ReentrancyGuard {
uint256 public constant TOTAL_SUPPLY = 69_000_000_000 ether; // 69B, 18 decimals
address public immutable weth;
address public immutable swapRouter; // Uniswap V3 SwapRouter, for the optional atomic launch buy
address public immutable tokenImplementation; // cloned per launch (EIP-1167)
ConfigRegistry public configRegistry;
IComplianceGate public complianceGate;
FeeRouter public immutable feeRouter;
LPLocker public immutable lpLocker;
// Anti-spam: minimum seconds between launches from the same sender (0 = disabled).
uint256 public launchCooldown;
mapping(address => uint256) public lastLaunchAt;
mapping(uint256 => IDexAdapter) public dexAdapters; // dexId => adapter
event DexAdapterSet(uint256 indexed dexId, address adapter);
event LaunchCooldownSet(uint256 cooldown);
event TokenLaunched(
address indexed token,
address indexed creator,
address pool,
uint256 positionId,
uint256 configId,
uint256 dexId,
string ipfsMetadata
);
constructor(
address owner_,
address weth_,
address swapRouter_,
address treasury_,
address development_,
INonfungiblePositionManager positionManager_,
ConfigRegistry configRegistry_,
IComplianceGate complianceGate_
) Ownable(owner_) {
weth = weth_;
swapRouter = swapRouter_;
configRegistry = configRegistry_;
complianceGate = complianceGate_;
tokenImplementation = address(new ChadToken());
feeRouter = new FeeRouter(address(this), owner_, treasury_, development_);
lpLocker = new LPLocker(address(this), positionManager_, feeRouter);
feeRouter.setLocker(address(lpLocker));
}
function setDexAdapter(uint256 dexId, IDexAdapter adapter) external onlyOwner {
dexAdapters[dexId] = adapter;
emit DexAdapterSet(dexId, address(adapter));
}
function setComplianceGate(IComplianceGate gate) external onlyOwner {
complianceGate = gate;
}
function setConfigRegistry(ConfigRegistry registry) external onlyOwner {
configRegistry = registry;
}
function setLaunchCooldown(uint256 cooldown) external onlyOwner {
launchCooldown = cooldown;
emit LaunchCooldownSet(cooldown);
}
/// @notice Owner kill-switch: halts all new launches during an incident. Fee collection on
/// already-launched tokens (LPLocker/FeeRouter) is unaffected.
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
/// @param salt A caller-mined salt. Every launched token must sort BEFORE WETH by address
/// (become Uniswap's token0) so a single fixed tick range yields the same market-cap curve for
/// every launch — see the "smooth/unbounded" liquidity model. The frontend mines a salt whose
/// deterministic clone address is < WETH; the salt is namespaced per sender to prevent griefing.
/// @param initialBuyWeth Optional ETH (on top of the creation fee) the creator spends on an
/// atomic first buy of their own token in this same transaction — guaranteed, front-run-proof,
/// and bounded by the config's `maxInitialBuyBps` (0 = unlimited).
function launchToken(
string calldata name,
string calldata symbol,
string calldata ipfsMetadata,
address creator,
uint256 configId,
uint256 dexId,
uint256 initialBuyWeth,
bytes32 salt
) external payable whenNotPaused nonReentrant returns (address token, address pool, uint256 positionId) {
require(complianceGate.isAllowed(msg.sender), "not allowed");
require(creator != address(0), "zero creator");
_validateMetadata(name, symbol, ipfsMetadata);
if (launchCooldown != 0) {
require(block.timestamp >= lastLaunchAt[msg.sender] + launchCooldown, "cooldown");
}
lastLaunchAt[msg.sender] = block.timestamp;
IDexAdapter adapter = dexAdapters[dexId];
require(address(adapter) != address(0), "unknown dex");
ConfigRegistry.LaunchConfig memory cfg = configRegistry.getConfig(configId);
require(msg.value >= cfg.creationFee + initialBuyWeth, "insufficient value");
token = Clones.cloneDeterministic(tokenImplementation, keccak256(abi.encodePacked(msg.sender, salt)));
require(token < weth, "token must sort first"); // enforce token0 ordering for a uniform curve
ChadToken(token).initialize(
name,
symbol,
TOTAL_SUPPLY,
address(this),
cfg.maxWalletBps,
cfg.maxWalletBlocks,
cfg.antiSnipeBlocks,
cfg.antiSnipeMaxTxBps
);
// Exempt the adapter up front so pulling the full supply for seeding doesn't trip the
// wallet cap (the cap itself stays disabled until finalizeLaunch, below).
ChadToken(token).setCapExempt(address(adapter), true);
IERC20(token).approve(address(adapter), TOTAL_SUPPLY);
(pool, positionId) = adapter.createPoolAndMintSingleSided(
IDexAdapter.SingleSidedParams({
token: token,
pairedToken: weth,
tokenAmount: TOTAL_SUPPLY,
amountTokenMin: TOTAL_SUPPLY - (TOTAL_SUPPLY * cfg.slippageBps) / 10_000,
tickLower: cfg.tickLower,
tickUpper: cfg.tickUpper,
feeTier: cfg.feeTier,
positionRecipient: address(lpLocker)
})
);
// Pool must stay exempt permanently so sells back into it never trip the cap.
ChadToken(token).setCapExempt(pool, true);
ChadToken(token).setCapExempt(address(lpLocker), true);
// Optional atomic creator buy — done while the token is still pre-launch (caps off) so it
// can't be front-run and isn't subject to the anti-snipe/wallet caps. Bounded separately by
// maxInitialBuyBps below.
if (initialBuyWeth != 0) {
_initialBuy(token, creator, initialBuyWeth, cfg.feeTier, cfg.maxInitialBuyBps);
}
ChadToken(token).finalizeLaunch();
lpLocker.registerPosition(positionId, token, weth);
feeRouter.registerPosition(
positionId, creator, token, weth, pool, cfg.treasuryFeeBps, cfg.devFeeBps, cfg.graduationTick
);
_settleCreationFee(cfg.creationFee, cfg.treasuryFeeBps, cfg.devFeeBps, initialBuyWeth);
emit TokenLaunched(token, creator, pool, positionId, configId, dexId, ipfsMetadata);
}
/// @dev Wraps the creator's ETH and swaps it for the freshly-launched token via the V3 router,
/// sending the tokens straight to the creator. Runs inside launchToken (pre-finalize), so it's
/// atomic with the launch — no MEV/sandwich is possible, hence amountOutMinimum can be 0. The
/// resulting balance is bounded by maxInitialBuyBps (0 = unlimited).
function _initialBuy(address token, address creator, uint256 amountWeth, uint24 feeTier, uint16 maxInitialBuyBps)
internal
{
IWETH9(weth).deposit{ value: amountWeth }();
IWETH9(weth).approve(swapRouter, amountWeth);
uint256 out = ISwapRouter(swapRouter).exactInputSingle(
ISwapRouter.ExactInputSingleParams({
tokenIn: weth,
tokenOut: token,
fee: feeTier,
recipient: creator,
deadline: block.timestamp,
amountIn: amountWeth,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
})
);
IWETH9(weth).approve(swapRouter, 0); // clear any residual allowance (defense-in-depth)
if (maxInitialBuyBps != 0) {
require(out <= (TOTAL_SUPPLY * maxInitialBuyBps) / 10_000, "initial buy exceeds max");
}
}
function _validateMetadata(string calldata name, string calldata symbol, string calldata ipfsMetadata)
internal
pure
{
uint256 nameLen = bytes(name).length;
uint256 symLen = bytes(symbol).length;
require(nameLen != 0 && nameLen <= 64, "bad name");
require(symLen != 0 && symLen <= 16, "bad symbol");
require(bytes(ipfsMetadata).length != 0, "no metadata");
}
/// @notice The creation fee is an anti-spam charge paid by the launcher, so no creator share:
/// it is split between treasury and development by the config's treasury:dev weight (all to
/// treasury if both weights are zero). Any overpayment is refunded to the sender.
function _settleCreationFee(uint256 fee, uint16 treasuryFeeBps, uint16 devFeeBps, uint256 initialBuyWeth)
internal
{
if (fee != 0) {
uint256 denom = uint256(treasuryFeeBps) + devFeeBps;
uint256 toDev = denom == 0 ? 0 : (fee * devFeeBps) / denom;
uint256 toTreasury = fee - toDev;
if (toTreasury != 0) {
(bool okT,) = feeRouter.treasury().call{ value: toTreasury }("");
require(okT, "treasury xfer failed");
}
if (toDev != 0) {
(bool okD,) = feeRouter.development().call{ value: toDev }("");
require(okD, "dev xfer failed");
}
}
// initialBuyWeth was already spent on the atomic buy; refund only the true overpayment.
uint256 refund = msg.value - fee - initialBuyWeth;
if (refund != 0) {
(bool okR,) = msg.sender.call{ value: refund }("");
require(okR, "refund failed");
}
}
}
Chain explorer6940msChain node89ms