MachineFunLauncher
0xb9c0d81f2380c2c69b560aebb84c6954d584e038
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
16,672 bytes
ABI entries
52
16 read · 15 write
License
none
Contract information
- Address
- 0xb9c0d81f2380c2c69b560aebb84c6954d584e038
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0xB2fbf0cFBc…0327aa4096
- Creation tx
- 0x0f43a37d42…e297283d0f
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (16)
LP_FEE() → uint24
TICK_SPACING() → int24
TOKEN_SUPPLY() → uint256
allTokens(uint256) → address
allTokensLength() → uint256
buyTaxBps() → uint16
creatorFeeBps() → uint16
defaultStartTick() → int24
feeHook() → address
launchFee() → uint256
maxBuyBps() → uint16
owner() → address
poolManager() → address
protocolFeeRecipient() → address
sellTaxBps() → uint16
tokenInfo(address) → address, int24, int24, uint128, tuple
Events (9)
CreatorFeeBpsSetDefaultStartTickSetFeesCollectedLaunchFeeSetLiquidityRemovedMaxBuyBpsSetOwnershipTransferredProtocolFeeRecipientSetTokenLaunched
ABI
[
{
"inputs": [
{
"internalType": "contract IPoolManager",
"name": "poolManager_",
"type": "address"
},
{
"internalType": "address",
"name": "protocolFeeRecipient_",
"type": "address"
},
{
"internalType": "contract MachineFeeHook",
"name": "feeHook_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "EthTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "FeeShareTooHigh",
"type": "error"
},
{
"inputs": [],
"name": "InvalidStartTick",
"type": "error"
},
{
"inputs": [],
"name": "NoLiquidity",
"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": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "LiquidityRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "bps",
"type": "uint16"
}
],
"name": "MaxBuyBpsSet",
"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": [],
"name": "buyTaxBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"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": [],
"name": "feeHook",
"outputs": [
{
"internalType": "contract MachineFeeHook",
"name": "",
"type": "address"
}
],
"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": "maxBuyBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"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": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "removeLiquidity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "sellTaxBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"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": "uint16",
"name": "buyBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "sellBps",
"type": "uint16"
}
],
"name": "setInitialTaxes",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
}
],
"name": "setLaunchFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "bps",
"type": "uint16"
}
],
"name": "setMaxBuyBps",
"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"
},
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "bool",
"name": "exempt",
"type": "bool"
}
],
"name": "setTokenExempt",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "setTokenLimits",
"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"
},
{
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
},
{
"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";
import {MachineFeeHook} from "./MachineFeeHook.sol";
/// @title MachineFunLauncher (v2) — machine.fun Uniswap v4 launchpad
///
/// @notice One-transaction fair launches on Uniswap v4:
///
/// 1. Deploys a fixed-supply ERC-20 (1B tokens, max-buy limited).
/// 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.
///
/// v2 changes vs v1:
/// - Anti-whale: each launched token enforces a max buy of `maxBuyBps`
/// (default 0.77% of supply) per transfer out of the pool. The owner can
/// lift or re-enable limits per token (`setTokenLimits`) and manage
/// exemptions (`setTokenExempt`).
/// - Emergency exit: the owner can remove a token's LP position partially
/// or fully (`removeLiquidity`) and receive the underlying ETH + tokens.
/// NOTE: this makes liquidity owner-custodied rather than locked — it is
/// a safety hatch, and holders can see it exists on-chain.
/// - ETH taxes: every pool is created with the MachineFeeHook attached,
/// charging buy/sell taxes purely in ETH (default 10%/15%), lowerable
/// but never raisable via the hook's `lowerTaxes`.
contract MachineFunLauncher is IUnlockCallback, Ownable {
// ---------------------------------------------------------------- errors
error NotPoolManager();
error WrongLaunchFee();
error InvalidStartTick();
error UnknownToken();
error UnexpectedEthOwed();
error FeeShareTooHigh();
error ZeroAddress();
error EthTransferFailed();
error NoLiquidity();
// ---------------------------------------------------------------- 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 LiquidityRemoved(
address indexed token,
uint128 liquidity,
uint256 ethAmount,
uint256 tokenAmount,
address recipient
);
event DefaultStartTickSet(int24 startTick);
event CreatorFeeBpsSet(uint16 bps);
event MaxBuyBpsSet(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;
/// @notice ETH tax hook attached to every launched pool.
MachineFeeHook public immutable feeHook;
// ---------------------------------------------------------------- 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 Max buy per swap for newly launched tokens (bps of supply).
uint16 public maxBuyBps = 77; // 0.77%
/// @notice Initial ETH taxes for newly launched pools (bps).
uint16 public buyTaxBps = 1_000; // 10%
uint16 public sellTaxBps = 1_500; // 15%
/// @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;
uint128 liquidity;
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,
REMOVE_LIQUIDITY
}
struct CallbackData {
Action action;
address token;
PoolKey key;
int24 tickLower;
int24 tickUpper;
uint128 liquidity;
address recipient;
}
constructor(IPoolManager poolManager_, address protocolFeeRecipient_, MachineFeeHook feeHook_)
Ownable(msg.sender)
{
if (
address(poolManager_) == address(0) || protocolFeeRecipient_ == address(0)
|| address(feeHook_) == address(0)
) revert ZeroAddress();
poolManager = poolManager_;
protocolFeeRecipient = protocolFeeRecipient_;
feeHook = feeHook_;
}
// ---------------------------------------------------------------- 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();
MachineToken t = new MachineToken(
name, symbol, metadataURI, TOKEN_SUPPLY, address(poolManager), maxBuyBps
);
token = address(t);
// Fee payouts flow straight from the PoolManager and must not trip
// the max-buy check.
t.setExempt(msg.sender, true);
t.setExempt(protocolFeeRecipient, true);
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(feeHook))
});
poolId = key.toId();
// Arm the ETH taxes before the pool can trade.
feeHook.registerPool(poolId, buyTaxBps, sellTaxBps);
// 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,
recipient: address(0)
})
)
);
_tokenInfo[token] = TokenInfo({
creator: msg.sender,
tickLower: tickLower,
tickUpper: startTick,
liquidity: liquidity,
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 = t.balanceOf(address(this));
if (dust > 0) t.transfer(address(0xdead), dust);
emit TokenLaunched(token, msg.sender, poolId, startTick, liquidity, TOKEN_SUPPLY - dust);
}
// ------------------------------------------------------------------ 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,
recipient: address(0)
})
)
);
}
// ------------------------------------------------------- emergency exit
/// @notice Remove some or all of a token's LP position and send the
/// underlying ETH + tokens (plus any accrued fees) to `recipient`.
/// Owner only. Pass type(uint128).max to remove everything.
function removeLiquidity(address token, uint128 liquidity, address recipient)
external
onlyOwner
{
if (recipient == address(0)) revert ZeroAddress();
TokenInfo storage info = _tokenInfo[token];
if (info.creator == address(0)) revert UnknownToken();
if (info.liquidity == 0) revert NoLiquidity();
if (liquidity > info.liquidity) liquidity = info.liquidity;
info.liquidity -= liquidity;
// The withdrawal itself is a large transfer out of the PoolManager.
MachineToken(token).setExempt(recipient, true);
poolManager.unlock(
abi.encode(
CallbackData({
action: Action.REMOVE_LIQUIDITY,
token: token,
key: info.poolKey,
tickLower: info.tickLower,
tickUpper: info.tickUpper,
liquidity: liquidity,
recipient: recipient
})
)
);
}
/// @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 if (data.action == Action.COLLECT_FEES) {
// 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);
} else {
// REMOVE_LIQUIDITY: withdraw principal (+ any accrued fees) to
// the owner-chosen recipient.
(BalanceDelta delta,) = poolManager.modifyLiquidity(
data.key,
ModifyLiquidityParams({
tickLower: data.tickLower,
tickUpper: data.tickUpper,
liquidityDelta: -int256(uint256(data.liquidity)),
salt: bytes32(0)
}),
""
);
uint256 eth = uint256(uint128(delta.amount0()));
uint256 tok = uint256(uint128(delta.amount1()));
if (eth > 0) poolManager.take(data.key.currency0, data.recipient, eth);
if (tok > 0) poolManager.take(data.key.currency1, data.recipient, tok);
emit LiquidityRemoved(data.token, data.liquidity, eth, tok, data.recipient);
}
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,
uint128 liquidity,
PoolKey memory poolKey
)
{
TokenInfo memory info = _tokenInfo[token];
return (info.creator, info.tickLower, info.tickUpper, info.liquidity, info.poolKey);
}
function allTokensLength() external view returns (uint256) {
return allTokens.length;
}
// ----------------------------------------------------------------- admin
/// @notice Enable/disable a token's max-buy limit.
function setTokenLimits(address token, bool enabled) external onlyOwner {
if (_tokenInfo[token].creator == address(0)) revert UnknownToken();
MachineToken(token).setLimitsEnabled(enabled);
}
/// @notice Manage a token's max-buy exemption list.
function setTokenExempt(address token, address account, bool exempt) external onlyOwner {
if (_tokenInfo[token].creator == address(0)) revert UnknownToken();
MachineToken(token).setExempt(account, exempt);
}
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);
}
/// @notice Max buy (bps of supply) applied to FUTURE launches.
function setMaxBuyBps(uint16 bps) external onlyOwner {
if (bps > 10_000) revert FeeShareTooHigh();
maxBuyBps = bps;
emit MaxBuyBpsSet(bps);
}
/// @notice Initial ETH taxes applied to FUTURE launches (hard cap 25%).
function setInitialTaxes(uint16 buyBps, uint16 sellBps) external onlyOwner {
if (buyBps > 2_500 || sellBps > 2_500) revert FeeShareTooHigh();
buyTaxBps = buyBps;
sellTaxBps = sellBps;
}
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 explorer7478msChain node87ms