MachineFunLauncher
0x2d02252060ad7d6902fa863d24ad9afa2f2a2bbf
Verification
Verified
0.8.26+commit.8a97fa7a
Type
Contract
12,009 bytes
ABI entries
40
12 read · 10 write
License
none
Contract information
- Address
- 0x2d02252060ad7d6902fa863d24ad9afa2f2a2bbf
- Chain
- Robinhood Chain (4663)
- Compiler
- 0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0xCE465f269c…0E0f59AF4f
- Creation tx
- 0xc78e0950e2…e36de08d89
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (12)
LP_FEE() → uint24
TICK_SPACING() → int24
TOKEN_SUPPLY() → uint256
allTokens(uint256) → address
allTokensLength() → uint256
creatorFeeBps() → uint16
defaultStartTick() → int24
launchFee() → uint256
owner() → address
poolManager() → address
protocolFeeRecipient() → address
tokenInfo(address) → address, int24, int24, tuple
Events (7)
CreatorFeeBpsSetDefaultStartTickSetFeesCollectedLaunchFeeSetOwnershipTransferredProtocolFeeRecipientSetTokenLaunched
ABI
[
{
"inputs": [
{
"internalType": "contract IPoolManager",
"name": "poolManager_",
"type": "address"
},
{
"internalType": "address",
"name": "protocolFeeRecipient_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "EthTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "FeeShareTooHigh",
"type": "error"
},
{
"inputs": [],
"name": "InvalidStartTick",
"type": "error"
},
{
"inputs": [],
"name": "NotPoolManager",
"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": "UnexpectedEthOwed",
"type": "error"
},
{
"inputs": [],
"name": "UnknownToken",
"type": "error"
},
{
"inputs": [],
"name": "WrongLaunchFee",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "bps",
"type": "uint16"
}
],
"name": "CreatorFeeBpsSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "int24",
"name": "startTick",
"type": "int24"
}
],
"name": "DefaultStartTickSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
}
],
"name": "FeesCollected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "fee",
"type": "uint256"
}
],
"name": "LaunchFeeSet",
"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": "recipient",
"type": "address"
}
],
"name": "ProtocolFeeRecipientSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": true,
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "int24",
"name": "startTick",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokensInPool",
"type": "uint256"
}
],
"name": "TokenLaunched",
"type": "event"
},
{
"inputs": [],
"name": "LP_FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TICK_SPACING",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TOKEN_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "allTokens",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "allTokensLength",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "collectFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "creatorFeeBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "defaultStartTick",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "metadataURI",
"type": "string"
},
{
"internalType": "int24",
"name": "startTickOverride",
"type": "int24"
}
],
"name": "launch",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "launchFee",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolFeeRecipient",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "bps",
"type": "uint16"
}
],
"name": "setCreatorFeeBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "startTick",
"type": "int24"
}
],
"name": "setDefaultStartTick",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
}
],
"name": "setLaunchFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "setProtocolFeeRecipient",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "tokenInfo",
"outputs": [
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "poolKey",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "rawData",
"type": "bytes"
}
],
"name": "unlockCallback",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "withdrawLaunchFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {ModifyLiquidityParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";
import {MachineToken} from "./MachineToken.sol";
/// @title MachineFunLauncher — machine.fun Uniswap v4 launchpad
///
/// @notice One-transaction fair launches on Uniswap v4:
///
/// 1. Deploys a fixed-supply ERC-20 (1B tokens, no mint, no owner powers).
/// 2. Creates a native-ETH/token v4 pool (1% LP fee, no hook).
/// 3. Deposits the ENTIRE supply as a single-sided liquidity position:
/// range [minUsableTick, startTick], pool initialized exactly at
/// startTick, so the position holds only tokens and every buy (ETH in)
/// moves the price down-tick through the range — i.e. up in USD terms.
///
/// Because the pair is native ETH (Currency = address(0), always currency0),
/// the launched token is always currency1 and "single-sided" means the
/// position is 100% token at initialization: the pool needs zero ETH to
/// launch, and the market cap floor is set purely by `startTick`.
///
/// Starting market cap: price/token at startTick is 1.0001^-startTick ETH, so
/// startTick = ln(supply * ethUsd / targetMcapUsd) / ln(1.0001)
/// The default (190600) targets ~$10k FDV on 1B supply with ETH ≈ $1,900.
/// Pass a per-launch override (or have the owner update the default) to track
/// the live ETH price.
///
/// The LP position is owned by this contract and can never be withdrawn —
/// liquidity is locked forever. Only the swap fees it earns are collectable,
/// split between the token's creator and the protocol.
contract MachineFunLauncher is IUnlockCallback, Ownable {
// ---------------------------------------------------------------- errors
error NotPoolManager();
error WrongLaunchFee();
error InvalidStartTick();
error UnknownToken();
error UnexpectedEthOwed();
error FeeShareTooHigh();
error ZeroAddress();
error EthTransferFailed();
// ---------------------------------------------------------------- events
event TokenLaunched(
address indexed token,
address indexed creator,
PoolId indexed poolId,
int24 startTick,
uint128 liquidity,
uint256 tokensInPool
);
event FeesCollected(address indexed token, uint256 ethAmount, uint256 tokenAmount);
event DefaultStartTickSet(int24 startTick);
event CreatorFeeBpsSet(uint16 bps);
event ProtocolFeeRecipientSet(address recipient);
event LaunchFeeSet(uint256 fee);
// ------------------------------------------------------------- constants
/// @notice Fixed supply of every launched token (1 billion, 18 decimals).
uint256 public constant TOKEN_SUPPLY = 1_000_000_000e18;
/// @notice LP fee of every pool: 1% (units of hundredths of a bip).
uint24 public constant LP_FEE = 10_000;
/// @notice Tick spacing of every pool.
int24 public constant TICK_SPACING = 200;
/// @notice Uniswap v4 PoolManager (canonical singleton on each chain).
IPoolManager public immutable poolManager;
// ---------------------------------------------------------------- config
/// @notice Default upper tick of new positions = starting price.
/// @dev 190600 ≈ 1.893e8 tokens/ETH ≈ $10k FDV at ETH $1,900.
int24 public defaultStartTick = 190_600;
/// @notice Share of collected LP fees paid to the token creator (bps).
uint16 public creatorFeeBps = 5_000; // 50%
/// @notice Receives the protocol's share of LP fees and launch fees.
address public protocolFeeRecipient;
/// @notice Optional flat ETH fee charged per launch (0 by default).
uint256 public launchFee;
// ----------------------------------------------------------------- state
struct TokenInfo {
address creator;
int24 tickLower;
int24 tickUpper;
PoolKey poolKey;
}
/// @notice Launch info for every token deployed through this contract.
mapping(address token => TokenInfo) internal _tokenInfo;
/// @notice All tokens ever launched (for indexers/frontends).
address[] public allTokens;
// -------------------------------------------------------------- callback
enum Action {
ADD_LIQUIDITY,
COLLECT_FEES
}
struct CallbackData {
Action action;
address token;
PoolKey key;
int24 tickLower;
int24 tickUpper;
uint128 liquidity;
}
constructor(IPoolManager poolManager_, address protocolFeeRecipient_) Ownable(msg.sender) {
if (address(poolManager_) == address(0) || protocolFeeRecipient_ == address(0)) revert ZeroAddress();
poolManager = poolManager_;
protocolFeeRecipient = protocolFeeRecipient_;
}
// ---------------------------------------------------------------- launch
/// @notice Deploy a token and launch it on Uniswap v4 in one transaction.
/// @param name Token name.
/// @param symbol Token symbol.
/// @param metadataURI Off-chain metadata URI (image/description), may be "".
/// @param startTickOverride Custom starting tick (multiple of TICK_SPACING),
/// or 0 to use `defaultStartTick`. Frontends should compute this
/// from the live ETH price to hit the $10k target exactly.
/// @return token The launched token address.
/// @return poolId The Uniswap v4 pool id.
function launch(
string calldata name,
string calldata symbol,
string calldata metadataURI,
int24 startTickOverride
) external payable returns (address token, PoolId poolId) {
if (msg.value != launchFee) revert WrongLaunchFee();
int24 startTick = startTickOverride == 0 ? defaultStartTick : startTickOverride;
int24 tickLower = TickMath.minUsableTick(TICK_SPACING);
if (
startTick % TICK_SPACING != 0 || startTick <= tickLower
|| startTick > TickMath.maxUsableTick(TICK_SPACING)
) revert InvalidStartTick();
token = address(new MachineToken(name, symbol, metadataURI, TOKEN_SUPPLY));
PoolKey memory key = PoolKey({
currency0: Currency.wrap(address(0)), // native ETH — always currency0
currency1: Currency.wrap(token),
fee: LP_FEE,
tickSpacing: TICK_SPACING,
hooks: IHooks(address(0))
});
poolId = key.toId();
// Initialize exactly at the top of the range: position is 100% token.
poolManager.initialize(key, TickMath.getSqrtPriceAtTick(startTick));
// Liquidity that a [tickLower, startTick] position needs to hold the
// full supply as token1: L = amount1 * Q96 / (sqrtUpper - sqrtLower).
uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(startTick);
uint128 liquidity =
uint128(FullMath.mulDiv(TOKEN_SUPPLY, FixedPoint96.Q96, sqrtUpper - sqrtLower));
poolManager.unlock(
abi.encode(
CallbackData({
action: Action.ADD_LIQUIDITY,
token: token,
key: key,
tickLower: tickLower,
tickUpper: startTick,
liquidity: liquidity
})
)
);
_tokenInfo[token] =
TokenInfo({creator: msg.sender, tickLower: tickLower, tickUpper: startTick, poolKey: key});
allTokens.push(token);
// Rounding leaves at most a few thousand wei of token behind — burn it
// so this contract never holds a spendable balance of launched tokens.
uint256 dust = IERC20(token).balanceOf(address(this));
if (dust > 0) IERC20(token).transfer(address(0xdead), dust);
uint256 tokensInPool = TOKEN_SUPPLY - dust;
emit TokenLaunched(token, msg.sender, poolId, startTick, liquidity, tokensInPool);
}
// ------------------------------------------------------------------ fees
/// @notice Collect the LP fees a token's position has earned and pay them
/// out (creatorFeeBps to the creator, remainder to the protocol).
/// Permissionless: anyone may trigger a collection.
function collectFees(address token) external {
TokenInfo memory info = _tokenInfo[token];
if (info.creator == address(0)) revert UnknownToken();
poolManager.unlock(
abi.encode(
CallbackData({
action: Action.COLLECT_FEES,
token: token,
key: info.poolKey,
tickLower: info.tickLower,
tickUpper: info.tickUpper,
liquidity: 0
})
)
);
}
/// @inheritdoc IUnlockCallback
function unlockCallback(bytes calldata rawData) external returns (bytes memory) {
if (msg.sender != address(poolManager)) revert NotPoolManager();
CallbackData memory data = abi.decode(rawData, (CallbackData));
if (data.action == Action.ADD_LIQUIDITY) {
(BalanceDelta delta,) = poolManager.modifyLiquidity(
data.key,
ModifyLiquidityParams({
tickLower: data.tickLower,
tickUpper: data.tickUpper,
liquidityDelta: int256(uint256(data.liquidity)),
salt: bytes32(0)
}),
""
);
// Initialized at tickUpper => the position needs no ETH at all.
if (delta.amount0() != 0) revert UnexpectedEthOwed();
// Pay the tokens we owe the pool.
uint256 owed = uint256(uint128(-delta.amount1()));
poolManager.sync(data.key.currency1);
IERC20(data.token).transfer(address(poolManager), owed);
poolManager.settle();
} else {
// liquidityDelta == 0 pokes the position; the returned delta is
// exactly the fees accrued since the last collection.
(BalanceDelta delta,) = poolManager.modifyLiquidity(
data.key,
ModifyLiquidityParams({
tickLower: data.tickLower,
tickUpper: data.tickUpper,
liquidityDelta: 0,
salt: bytes32(0)
}),
""
);
address creator = _tokenInfo[data.token].creator;
uint256 eth = uint256(uint128(delta.amount0()));
uint256 tok = uint256(uint128(delta.amount1()));
_payOut(data.key.currency0, eth, creator);
_payOut(data.key.currency1, tok, creator);
emit FeesCollected(data.token, eth, tok);
}
return "";
}
function _payOut(Currency currency, uint256 amount, address creator) internal {
if (amount == 0) return;
uint256 creatorShare = (amount * creatorFeeBps) / 10_000;
if (creatorShare > 0) poolManager.take(currency, creator, creatorShare);
if (amount - creatorShare > 0) {
poolManager.take(currency, protocolFeeRecipient, amount - creatorShare);
}
}
// ----------------------------------------------------------------- views
function tokenInfo(address token)
external
view
returns (address creator, int24 tickLower, int24 tickUpper, PoolKey memory poolKey)
{
TokenInfo memory info = _tokenInfo[token];
return (info.creator, info.tickLower, info.tickUpper, info.poolKey);
}
function allTokensLength() external view returns (uint256) {
return allTokens.length;
}
// ----------------------------------------------------------------- admin
function setDefaultStartTick(int24 startTick) external onlyOwner {
if (
startTick % TICK_SPACING != 0 || startTick <= TickMath.minUsableTick(TICK_SPACING)
|| startTick > TickMath.maxUsableTick(TICK_SPACING)
) revert InvalidStartTick();
defaultStartTick = startTick;
emit DefaultStartTickSet(startTick);
}
function setCreatorFeeBps(uint16 bps) external onlyOwner {
if (bps > 10_000) revert FeeShareTooHigh();
creatorFeeBps = bps;
emit CreatorFeeBpsSet(bps);
}
function setProtocolFeeRecipient(address recipient) external onlyOwner {
if (recipient == address(0)) revert ZeroAddress();
protocolFeeRecipient = recipient;
emit ProtocolFeeRecipientSet(recipient);
}
function setLaunchFee(uint256 fee) external onlyOwner {
launchFee = fee;
emit LaunchFeeSet(fee);
}
/// @notice Sweep accumulated launch fees (ETH) to the protocol recipient.
function withdrawLaunchFees() external {
(bool ok,) = protocolFeeRecipient.call{value: address(this).balance}("");
if (!ok) revert EthTransferFailed();
}
}
Chain explorer4562msChain node92ms