LaunchFactory
0x56cc4630b7cb942add483d81e6d3cad954d3fbe4
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
16,101 bytes
ABI entries
27
6 read · 7 write
License
none
Contract information
- Address
- 0x56cc4630b7cb942add483d81e6d3cad954d3fbe4
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0x3D54a86827…25cEc62aF3
- Creation tx
- 0xe284b563b3…d801b2b892
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (6)
curveOf(address) → address
feeManager() → address
liquidityManager() → address
owner() → address
paused() → bool
treasury() → address
Events (6)
OwnershipTransferredPausedTokenCreatedTreasuryUpdatedUniswapWiringUpdatedUnpaused
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "owner_",
"type": "address"
},
{
"internalType": "address",
"name": "treasury_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "EnforcedPause",
"type": "error"
},
{
"inputs": [],
"name": "ExpectedPause",
"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": [
{
"internalType": "uint160",
"name": "expected",
"type": "uint160"
},
{
"internalType": "uint160",
"name": "actual",
"type": "uint160"
}
],
"name": "PoolPriceMismatch",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"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": "curve",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "uri",
"type": "string"
},
{
"components": [
{
"internalType": "string",
"name": "website",
"type": "string"
},
{
"internalType": "string",
"name": "twitter",
"type": "string"
},
{
"internalType": "string",
"name": "telegram",
"type": "string"
}
],
"indexed": false,
"internalType": "struct LaunchFactory.Socials",
"name": "socials",
"type": "tuple"
},
{
"indexed": false,
"internalType": "uint256",
"name": "devBuyEth",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "devTokens",
"type": "uint256"
}
],
"name": "TokenCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousTreasury",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newTreasury",
"type": "address"
}
],
"name": "TreasuryUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "feeManager",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "liquidityManager",
"type": "address"
}
],
"name": "UniswapWiringUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "uri",
"type": "string"
},
{
"components": [
{
"internalType": "string",
"name": "website",
"type": "string"
},
{
"internalType": "string",
"name": "twitter",
"type": "string"
},
{
"internalType": "string",
"name": "telegram",
"type": "string"
}
],
"internalType": "struct LaunchFactory.Socials",
"name": "socials",
"type": "tuple"
}
],
"name": "createToken",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "curve",
"type": "address"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "curveOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "liquidityManager",
"outputs": [
{
"internalType": "address",
"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": "address",
"name": "newTreasury",
"type": "address"
}
],
"name": "setTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "feeManager_",
"type": "address"
},
{
"internalType": "address",
"name": "liquidityManager_",
"type": "address"
}
],
"name": "setUniswapWiring",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "treasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "unpause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
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 {LaunchToken} from "./LaunchToken.sol";
import {BondingCurve} from "./BondingCurve.sol";
import {FeeManager} from "./FeeManager.sol";
import {PoolMath} from "./PoolMath.sol";
import {UniswapAddresses} from "./UniswapAddresses.sol";
import {INonfungiblePositionManager, IUniswapV3Pool} from "./interfaces/IUniswapV3.sol";
/// @title LaunchFactory
/// @notice Deploys a `LaunchToken` + `BondingCurve` pair via CREATE2 and, when the
/// factory is wired to Uniswap (production), also CREATES AND INITIALIZES
/// the graduation v3 pool at the precomputed final price — all in one tx.
/// @dev Launching is permissionless. Access control (Ownable) governs *params*
/// (treasury, Uniswap wiring) and the factory-level pause — never the curves.
/// The CREATE2 salt binds to `msg.sender`, so a front-runner copying the same
/// parameters cannot occupy a victim's deterministic address.
///
/// Initializing the pool here (not in graduate()) is the front-run defense:
/// graduate() only ever mints into an already-initialized pool and reverts if
/// the price drifted. See docs/GRADUATION.md.
contract LaunchFactory is Ownable, Pausable, ReentrancyGuard {
/// @notice Optional social links stored off-chain-indexable in the event.
struct Socials {
string website;
string twitter;
string telegram;
}
INonfungiblePositionManager internal constant NPM =
INonfungiblePositionManager(UniswapAddresses.NONFUNGIBLE_POSITION_MANAGER);
address internal constant WETH9 = UniswapAddresses.WETH9;
uint24 internal constant FEE_TIER = UniswapAddresses.FEE_TIER;
/// @notice Fallback trade-fee recipient for curves when Uniswap wiring is unset.
address public treasury;
/// @notice Canonical fee parameters for new launches (set via wiring).
address public feeManager;
/// @notice LiquidityManager curves delegate to at graduation (set via wiring).
address public liquidityManager;
/// @notice Registry of launch token -> its BondingCurve, set at creation. Lets
/// off-chain callers and the FeeRouter resolve a token's curve on-chain.
mapping(address => address) public curveOf;
/// @notice Emitted for every launch with everything the indexer needs.
event TokenCreated(
address indexed token,
address indexed curve,
address indexed creator,
address pool,
string name,
string symbol,
string uri,
Socials socials,
uint256 devBuyEth,
uint256 devTokens
);
/// @notice Emitted when the treasury param changes.
event TreasuryUpdated(address indexed previousTreasury, address indexed newTreasury);
/// @notice Emitted when Uniswap wiring changes.
event UniswapWiringUpdated(address indexed feeManager, address indexed liquidityManager);
error ZeroAddress();
error PoolPriceMismatch(uint160 expected, uint160 actual);
/// @param owner_ Initial owner (params + pause authority).
/// @param treasury_ Initial fallback trade-fee recipient (non-zero).
constructor(address owner_, address treasury_) Ownable(owner_) {
if (treasury_ == address(0)) revert ZeroAddress();
treasury = treasury_;
}
// --------------------------------------------------------------------- //
// Launch //
// --------------------------------------------------------------------- //
/// @notice Deploy a token + bonding curve (+ graduation v3 pool when wired) and
/// optionally perform a dev-buy, all in one transaction.
/// @dev The full token supply is minted to the curve. When the factory is wired
/// to Uniswap, the curve's economics are snapshotted from the FeeManager and
/// the v3 pool is created + initialized at the precomputed price. Any
/// `msg.value` is an atomic dev-buy credited to the caller.
/// @param name Token name (immutable).
/// @param symbol Token symbol (immutable).
/// @param uri Metadata URI (immutable).
/// @param socials Social links (indexed off-chain via the event).
/// @return token The deployed token address.
/// @return curve The deployed bonding-curve address.
function createToken(string calldata name, string calldata symbol, string calldata uri, Socials calldata socials)
external
payable
whenNotPaused
nonReentrant
returns (address token, address curve)
{
address pool;
(token, curve, pool) = _deploy(name, symbol, uri);
uint256 devTokens = msg.value > 0 ? BondingCurve(curve).buyFor{value: msg.value}(msg.sender, 0) : 0;
emit TokenCreated(token, curve, msg.sender, pool, name, symbol, uri, socials, msg.value, devTokens);
}
/// @dev Deploys the curve + token via CREATE2, wires them, and (when the factory
/// is Uniswap-wired) creates + initializes the graduation pool and snapshots
/// economics onto the curve. Isolated so locals free before the emit.
function _deploy(string calldata name, string calldata symbol, string calldata uri)
internal
returns (address token, address curve, address pool)
{
bytes32 salt = keccak256(abi.encode(msg.sender, name, symbol, uri));
address lm = liquidityManager;
address curveTreasury = lm != address(0) ? FeeManager(feeManager).treasury() : treasury;
BondingCurve curveContract = new BondingCurve{salt: salt}(curveTreasury);
LaunchToken tokenContract =
new LaunchToken{salt: salt}(name, symbol, uri, curveContract.TOTAL_SUPPLY(), address(curveContract));
curveContract.initialize(tokenContract);
curveOf[address(tokenContract)] = address(curveContract);
if (lm != address(0)) {
pool = _setupPool(address(tokenContract), curveContract, lm, msg.sender);
}
return (address(tokenContract), address(curveContract), pool);
}
/// @dev Creates + initializes the v3 pool at the precomputed price and snapshots
/// economics onto the curve. Reverts if the pool was pre-created at a wrong
/// price (createToken front-run) — fail fast rather than bind a bad pool.
function _setupPool(address token, BondingCurve curveContract, address lm, address creator)
internal
returns (address pool)
{
uint160 sp = PoolMath.sqrtPriceX96(token);
{
(address token0, address token1) = token < WETH9 ? (token, WETH9) : (WETH9, token);
pool = NPM.createAndInitializePoolIfNecessary(token0, token1, FEE_TIER, sp);
(uint160 current,,,,,,) = IUniswapV3Pool(pool).slot0();
if (current != sp) revert PoolPriceMismatch(sp, current);
}
// Snapshot economics + the creator fee split onto the curve (immutable after).
FeeManager fm = FeeManager(feeManager);
(uint256 tradeFeeBps, uint256 graduationFee, uint256 gradBounty, uint256 threshold,) = fm.params();
curveContract.configureGraduation(
BondingCurve.GradConfig({
tradeFeeBps: tradeFeeBps,
graduationFee: graduationFee,
gradBounty: gradBounty,
threshold: threshold,
liquidityManager: lm,
pool: pool,
sqrtPriceX96Expected: sp,
creator: creator,
creatorFeeBps: fm.creatorFeeBps()
})
);
}
// --------------------------------------------------------------------- //
// Params //
// --------------------------------------------------------------------- //
/// @notice Wire the factory to the FeeManager + LiquidityManager (production).
/// @param feeManager_ Canonical fee params source (non-zero).
/// @param liquidityManager_ Graduation liquidity manager (non-zero).
function setUniswapWiring(address feeManager_, address liquidityManager_) external onlyOwner {
if (feeManager_ == address(0) || liquidityManager_ == address(0)) revert ZeroAddress();
feeManager = feeManager_;
liquidityManager = liquidityManager_;
emit UniswapWiringUpdated(feeManager_, liquidityManager_);
}
/// @notice Update the fallback trade-fee recipient (used when Uniswap wiring unset).
/// @param newTreasury The new treasury (non-zero).
function setTreasury(address newTreasury) external onlyOwner {
if (newTreasury == address(0)) revert ZeroAddress();
emit TreasuryUpdated(treasury, newTreasury);
treasury = newTreasury;
}
/// @notice Pause new launches (does not affect already-deployed curves).
function pause() external onlyOwner {
_pause();
}
/// @notice Resume new launches.
function unpause() external onlyOwner {
_unpause();
}
}
Chain explorer4529msChain node85ms