TokenFactory

0x1bed2687321074a198302c004036b51923812b18

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
5,725 bytes
ABI entries
23
17 read · 1 write
License
none

Contract information

Address
0x1bed2687321074a198302c004036b51923812b18
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x790ca46e28…4299fe197f

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (17)

CURVE_BUCKETS()uint8
END_TICK()int24
GRAD_TICK()int24
MAX_VESTING_BPS()uint16
START_TICK()int24
TAIL_BUCKETS()uint8
TICK_SPACING()int24
feeRouter()address
hook()address
launchCount()uint256
locker()address
poolManager()address
predictToken(address, string, uint256)address
registry()address
tokenImplementation()address
vestingVault()address
weth()address

Events (1)

TokenCreated

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "poolManager_",
        "type": "address"
      },
      {
        "internalType": "contract LaunchHook",
        "name": "hook_",
        "type": "address"
      },
      {
        "internalType": "contract PermanentLocker",
        "name": "locker_",
        "type": "address"
      },
      {
        "internalType": "contract FeeRouter",
        "name": "feeRouter_",
        "type": "address"
      },
      {
        "internalType": "contract Registry",
        "name": "registry_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "tokenImplementation_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "vestingVault_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "FailedDeployment",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "InsufficientBalance",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SymbolRequired",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "socialClaimId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "PoolId",
        "name": "poolId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "launchBlock",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      }
    ],
    "name": "TokenCreated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "CURVE_BUCKETS",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "END_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "GRAD_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_VESTING_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "START_TICK",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TAIL_BUCKETS",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TICK_SPACING",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "string",
            "name": "name",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "symbol",
            "type": "string"
          },
          {
            "internalType": "string",
            "name": "metadataURI",
            "type": "string"
          },
          {
            "internalType": "bytes32",
            "name": "socialClaimId",
            "type": "bytes32"
          },
          {
            "internalType": "uint16",
            "name": "creatorFeeBps",
            "type": "uint16"
          },
          {
            "internalType": "uint16",
            "name": "claimerShareBps",
            "type": "uint16"
          },
          {
            "internalType": "address",
            "name": "referrer",
            "type": "address"
          },
          {
            "internalType": "bool",
            "name": "vestingEnabled",
            "type": "bool"
          }
        ],
        "internalType": "struct TokenFactory.LaunchParams",
        "name": "p",
        "type": "tuple"
      }
    ],
    "name": "createToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "PoolId",
        "name": "poolId",
        "type": "bytes32"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeRouter",
    "outputs": [
      {
        "internalType": "contract FeeRouter",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "hook",
    "outputs": [
      {
        "internalType": "contract LaunchHook",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "launchCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "locker",
    "outputs": [
      {
        "internalType": "contract PermanentLocker",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolManager",
    "outputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "name": "predictToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "registry",
    "outputs": [
      {
        "internalType": "contract Registry",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenImplementation",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "vestingVault",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "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.26;

import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {IHooks} from "v4-core/interfaces/IHooks.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {PoolId} from "v4-core/types/PoolId.sol";
import {Currency} from "v4-core/types/Currency.sol";
import {TickMath} from "v4-core/libraries/TickMath.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {LaunchToken} from "./LaunchToken.sol";
import {LaunchHook} from "./LaunchHook.sol";
import {PermanentLocker} from "./PermanentLocker.sol";
import {FeeRouter} from "./FeeRouter.sol";
import {Registry} from "./Registry.sol";

interface IWETH9 {
    function deposit() external payable;
    function transfer(address to, uint256 amount) external returns (bool);
}

/// @title TokenFactory
/// @notice Single entrypoint: one transaction deploys the token, initializes the v4 pool
///         behind the LaunchHook, seeds the curve-shaped liquidity ladder into the
///         PermanentLocker, optionally executes the creator's atomic dev-buy, and registers
///         everything. Standardization is the product: fixed 1B supply, fixed curve shape;
///         creators choose name/symbol/metadata and fee routing only.
///
///         The factory is upgrade-free. New versions are new factories registered into the
///         same Registry.
contract TokenFactory {
    uint16 public constant MAX_VESTING_BPS = 200; // <=2% creator slice
    // Canonical ladder (token-as-currency0 ticks; mirrored by the locker when token sorts
    // as currency1). Start FDV ~$4-5K in WETH terms; graduation ~22x above start.
    int24 public constant TICK_SPACING = 100;
    int24 public constant START_TICK = -204_800;
    int24 public constant GRAD_TICK = -173_800;
    int24 public constant END_TICK = -143_800;
    uint8 public constant CURVE_BUCKETS = 10;
    uint8 public constant TAIL_BUCKETS = 2;

    struct LaunchParams {
        string name;
        string symbol;
        string metadataURI; // IPFS/CDN pointer; immutable post-launch
        bytes32 socialClaimId; // optional: keccak256("x.com/<handle>")
        uint16 creatorFeeBps; // curve-phase creator share, 1000-5500
        uint16 claimerShareBps; // portion of creator side routed to socialClaimId
        address referrer; // launch referrer (interface/bot), earns on this pool forever
        bool vestingEnabled; // 2% creator slice, 30d cliff + linear
        // creator dev-buy: msg.value is wrapped to WETH and swapped atomically inside this
        // transaction — the only guaranteed first fill under strict FCFS sequencing.
        // No cap: a dev buy may take up to the entire bonding curve (UI caps input at graduationQuote).
    }

    IPoolManager public immutable poolManager;
    LaunchHook public immutable hook;
    PermanentLocker public immutable locker;
    FeeRouter public immutable feeRouter;
    Registry public immutable registry;
    address public immutable weth;
    address public immutable tokenImplementation;
    address public immutable vestingVault;

    uint256 public launchCount;

    event TokenCreated(
        address indexed token,
        address indexed creator,
        bytes32 indexed socialClaimId,
        PoolId poolId,
        uint256 launchBlock,
        string metadataURI
    );

    error SymbolRequired();

    constructor(
        IPoolManager poolManager_,
        LaunchHook hook_,
        PermanentLocker locker_,
        FeeRouter feeRouter_,
        Registry registry_,
        address weth_,
        address tokenImplementation_,
        address vestingVault_
    ) {
        poolManager = poolManager_;
        hook = hook_;
        locker = locker_;
        feeRouter = feeRouter_;
        registry = registry_;
        weth = weth_;
        tokenImplementation = tokenImplementation_;
        vestingVault = vestingVault_;
    }

    function createToken(LaunchParams calldata p) external payable returns (address token, PoolId poolId) {
        if (bytes(p.symbol).length == 0) revert SymbolRequired();
        address creator = msg.sender;

        // deterministic, front-run-resistant token address
        uint256 nonce = launchCount++;
        bytes32 salt = keccak256(abi.encodePacked(creator, p.symbol, nonce));
        token = Clones.cloneDeterministic(tokenImplementation, salt);

        uint256 vestAmount =
            p.vestingEnabled ? (LaunchToken(token).TOTAL_SUPPLY() * MAX_VESTING_BPS) / 10_000 : 0;
        LaunchToken(token).initialize(p.name, p.symbol, address(locker), vestingVault, vestAmount);
        if (vestAmount > 0) IVestingVault(vestingVault).register(token, creator, vestAmount);

        bool tokenIs0 = token < weth;
        PoolKey memory key = PoolKey({
            currency0: Currency.wrap(tokenIs0 ? token : weth),
            currency1: Currency.wrap(tokenIs0 ? weth : token),
            fee: 0, // hook takes the fee; LP fee is zero
            tickSpacing: TICK_SPACING,
            hooks: IHooks(address(hook))
        });
        poolId = key.toId();

        hook.register(key, token, tokenIs0);
        poolManager.initialize(key, TickMath.getSqrtPriceAtTick(tokenIs0 ? START_TICK : -START_TICK));

        feeRouter.setPoolConfig(poolId, creator, p.referrer, p.socialClaimId, p.creatorFeeBps, p.claimerShareBps);

        // wrap the dev-buy ETH and hand it to the locker for the atomic first fill
        if (msg.value > 0) {
            IWETH9(weth).deposit{value: msg.value}();
            IWETH9(weth).transfer(address(locker), msg.value);
        }

        locker.seed(
            PermanentLocker.SeedParams({
                key: key,
                token: token,
                tokenIs0: tokenIs0,
                startTick: START_TICK,
                gradTick: GRAD_TICK,
                endTick: END_TICK,
                curveBuckets: CURVE_BUCKETS,
                tailBuckets: TAIL_BUCKETS,
                supplyToSeed: LaunchToken(token).TOTAL_SUPPLY() - vestAmount,
                initialBuyQuote: msg.value,
                buyRecipient: creator
            })
        );

        registry.register(token, creator, poolId, p.socialClaimId, p.metadataURI);
        emit TokenCreated(token, creator, p.socialClaimId, poolId, block.number, p.metadataURI);
    }

    /// @notice Predict the token address for (creator, symbol, nonce) — CREATE2-deterministic,
    ///         so the address cannot be squatted between announce and deploy, and the SDK can
    ///         mine vanity addresses off-chain.
    function predictToken(address creator, string calldata symbol, uint256 nonce) external view returns (address) {
        bytes32 salt = keccak256(abi.encodePacked(creator, symbol, nonce));
        return Clones.predictDeterministicAddress(tokenImplementation, salt, address(this));
    }
}

interface IVestingVault {
    function register(address token, address creator, uint256 amount) external;
}
Chain explorer1965msChain node82ms