Launchpad

0x49dca5ef4d432455abb39fa9c116df5d6bbc3110Verified
Native balance0USDX
Account typeSmart contract
NetworkTuven 99359

Overview

Contract name
Launchpad
Verification
Verified source
Balance updated at block
16859

Verified contract source

Verified
Contract name
Launchpad
Compiler
v0.8.29+commit.ab55807c
EVM version
prague
License
none
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Ownable, Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {LaunchToken} from "./LaunchToken.sol";
import {LpVault} from "./LpVault.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {IUniswapV2Factory, IUniswapV2Pair} from "./interfaces/IUniswapV2.sol";

/// @title Launchpad
/// @notice pump.fun-style bonding-curve launchpad for Tuven.
///
///         One contract hosts every launch. `create` deploys a fixed-supply
///         LaunchToken (100M) and opens a constant-product curve with virtual
///         reserves; `buy` / `sell` trade native USDX against it. When the curve's
///         sellable supply is exhausted the launch graduates in the same
///         transaction: the reserved LP supply plus the USDX raised (minus the
///         graduation fee) are deposited into the genesis-predeployed Uniswap V2
///         pair. The LP tokens go to the platform's `LpVault`, which locks the
///         principal forever and can harvest only the trading-fee growth.
///
///         Units: native USDX is 18-dec on `msg.value` / balances. Tuven's USDX
///         ERC-20 facade (`0x3600…0000`) exposes the same balance at 6 decimals, so
///         the pair deposit is scaled by `USDX_SCALE`; sub-6-dec dust goes to fees.
///
///         Trust model: the owner (admin multisig / timelock) can pause, change the
///         parameters for *future* launches and change the fee recipient. It cannot
///         touch a live launch's reserves, mint tokens, or upgrade the contract.
contract Launchpad is Ownable2Step, Pausable, ReentrancyGuard {
    using SafeCast for uint256;

    // ── Constants ────────────────────────────────────────────────────────
    /// @notice Supply every LaunchToken mints to the launchpad (mirrors LaunchToken.TOTAL_SUPPLY).
    uint256 public constant TOKEN_SUPPLY = 100_000_000e18;
    uint256 public constant BPS = 10_000;
    uint256 public constant MAX_TRADE_FEE_BPS = 1_000; // 10 %
    uint256 public constant USDX_SCALE = 1e12; // native wei → 6-dec ERC-20 units

    // ── Immutables ───────────────────────────────────────────────────────
    /// @notice USDX ERC-20 facade over the native balance (Arc FiatToken proxy).
    IERC20 public immutable usdx;
    /// @notice Uniswap V2 factory the launches graduate into (genesis predeploy).
    IUniswapV2Factory public immutable dexFactory;
    /// @notice Holds every graduation's LP; principal locked, fee growth harvestable.
    ///         UUPS proxy; upgrades are authorised by this contract's owner.
    LpVault public immutable lpVault;

    // ── Types ────────────────────────────────────────────────────────────
    /// @notice Parameters applied to launches created after they are set.
    struct Config {
        uint128 virtualUsdx; // initial virtual USDX reserve (wei)
        uint128 virtualToken; // initial virtual token reserve (wei); must exceed curveSupply
        uint128 curveSupply; // tokens sold on the curve; the rest goes to the LP
        uint128 graduationFee; // flat USDX (wei) taken from the raise at graduation
        uint96 creationFee; // flat USDX (wei) paid by the creator
        uint16 tradeFeeBps; // fee on the USDX side of every buy and sell
    }

    struct Launch {
        address creator;
        uint64 createdAt;
        bool graduated;
        uint16 tradeFeeBps;
        uint128 virtualUsdx;
        uint128 virtualToken;
        uint128 realUsdx; // USDX (wei) held for this launch, excluding fees
        uint128 tokensSold;
        uint128 curveSupply;
        uint128 graduationFee;
        address pair; // set at graduation
        string metadataURI;
    }

    // ── Storage ──────────────────────────────────────────────────────────
    Config public config;
    address public feeRecipient;
    /// @notice Fees (wei) collected and not yet pushed to `feeRecipient`.
    uint256 public accruedFees;

    mapping(address token => Launch) internal _launches;
    address[] public allTokens;

    // ── Events ───────────────────────────────────────────────────────────
    event Created(
        address indexed token,
        address indexed creator,
        string name,
        string symbol,
        string metadataURI,
        uint256 virtualUsdx,
        uint256 virtualToken,
        uint256 curveSupply
    );
    event Trade(
        address indexed token,
        address indexed trader,
        bool isBuy,
        uint256 usdxAmount, // wei moved into (buy) or out of (sell) the reserve, net of fee
        uint256 tokenAmount,
        uint256 fee,
        uint256 virtualUsdxAfter,
        uint256 virtualTokenAfter,
        uint256 realUsdxAfter,
        uint256 tokensSoldAfter
    );
    event Graduated(
        address indexed token,
        address indexed pair,
        uint256 usdxToLp, // wei actually deposited (6-dec aligned)
        uint256 tokensToLp,
        uint256 liquidity,
        uint256 fee
    );
    event FeesWithdrawn(address indexed to, uint256 amount);
    event ConfigUpdated(Config config);
    event FeeRecipientUpdated(address indexed feeRecipient);

    // ── Errors ───────────────────────────────────────────────────────────
    error UnknownLaunch();
    error AlreadyGraduated();
    error NotSoldOut();
    error Expired();
    error ZeroAmount();
    error SlippageExceeded();
    error InsufficientCreationFee();
    error InvalidConfig();
    error ZeroAddress();
    error TransferFailed();
    error AmountExceedsSold();

    // ── Constructor ──────────────────────────────────────────────────────
    constructor(address owner_, address feeRecipient_, IERC20 usdx_, IUniswapV2Factory dexFactory_, Config memory config_)
        Ownable(owner_)
    {
        if (feeRecipient_ == address(0) || address(usdx_) == address(0) || address(dexFactory_) == address(0)) {
            revert ZeroAddress();
        }
        usdx = usdx_;
        dexFactory = dexFactory_;
        feeRecipient = feeRecipient_;
        lpVault = LpVault(address(new ERC1967Proxy(address(new LpVault()), abi.encodeCall(LpVault.initialize, (address(this))))));
        _setConfig(config_);
    }

    // ── Launch lifecycle ─────────────────────────────────────────────────

    /// @notice Deploys a new token and opens its curve. Any value above the
    ///         creation fee is spent as the creator's first buy (sniping guard).
    /// @param salt Creator-scoped CREATE2 salt; see `computeTokenAddress`.
    /// @param minTokensOut Slippage bound for the optional first buy.
    function create(
        string calldata name,
        string calldata symbol,
        string calldata metadataURI,
        bytes32 salt,
        uint256 minTokensOut
    ) external payable whenNotPaused nonReentrant returns (address token, uint256 tokensOut) {
        Config memory c = config;
        if (msg.value < c.creationFee) revert InsufficientCreationFee();

        token = address(new LaunchToken{salt: _salt(msg.sender, salt)}(name, symbol));

        Launch storage l = _launches[token];
        l.creator = msg.sender;
        l.createdAt = uint64(block.timestamp);
        l.tradeFeeBps = c.tradeFeeBps;
        l.virtualUsdx = c.virtualUsdx;
        l.virtualToken = c.virtualToken;
        l.curveSupply = c.curveSupply;
        l.graduationFee = c.graduationFee;
        l.metadataURI = metadataURI;
        allTokens.push(token);
        accruedFees += c.creationFee;

        emit Created(token, msg.sender, name, symbol, metadataURI, c.virtualUsdx, c.virtualToken, c.curveSupply);

        uint256 buyValue = msg.value - c.creationFee;
        if (buyValue > 0) tokensOut = _buy(token, l, buyValue, minTokensOut);
    }

    /// @notice Buys tokens from the curve with native USDX (`msg.value`).
    ///         If the purchase would exceed what is left on the curve, only the
    ///         remainder is sold and the excess USDX is refunded; the launch then
    ///         graduates in this same transaction.
    function buy(address token, uint256 minTokensOut, uint256 deadline)
        external
        payable
        whenNotPaused
        nonReentrant
        returns (uint256 tokensOut)
    {
        if (block.timestamp > deadline) revert Expired();
        if (msg.value == 0) revert ZeroAmount();
        Launch storage l = _liveLaunch(token);
        tokensOut = _buy(token, l, msg.value, minTokensOut);
    }

    /// @notice Sells tokens back to the curve for native USDX.
    function sell(address token, uint256 tokenAmount, uint256 minUsdxOut, uint256 deadline)
        external
        whenNotPaused
        nonReentrant
        returns (uint256 usdxOut)
    {
        if (block.timestamp > deadline) revert Expired();
        if (tokenAmount == 0) revert ZeroAmount();
        Launch storage l = _liveLaunch(token);
        if (tokenAmount > l.tokensSold) revert AmountExceedsSold();

        uint256 vU = l.virtualUsdx;
        uint256 vT = l.virtualToken;
        uint256 gross = Math.mulDiv(tokenAmount, vU, vT + tokenAmount); // rounds toward the curve
        uint256 fee = gross * l.tradeFeeBps / BPS;
        usdxOut = gross - fee;
        if (usdxOut == 0) revert ZeroAmount();
        if (usdxOut < minUsdxOut) revert SlippageExceeded();
        // k never decreases, so the curve always holds at least `gross` (see _buy).
        assert(gross <= l.realUsdx);

        l.virtualUsdx = (vU - gross).toUint128();
        l.virtualToken = (vT + tokenAmount).toUint128();
        l.realUsdx -= gross.toUint128();
        l.tokensSold -= tokenAmount.toUint128();
        accruedFees += fee;

        // LaunchToken has no transfer hooks; the launchpad is always an allowed counterparty.
        LaunchToken(token).transferFrom(msg.sender, address(this), tokenAmount);
        _sendUsdx(msg.sender, usdxOut);

        _emitTrade(token, false, gross, tokenAmount, fee, l);
    }

    /// @notice Permissionless safety valve: graduates a sold-out launch that has
    ///         not graduated yet. Under normal operation the final buy does this.
    function graduate(address token) external whenNotPaused nonReentrant {
        Launch storage l = _liveLaunch(token);
        if (l.tokensSold != l.curveSupply) revert NotSoldOut();
        _graduate(token, l);
    }

    // ── Fees ─────────────────────────────────────────────────────────────

    /// @notice Pushes all accrued fees to `feeRecipient`. Callable by anyone.
    function withdrawFees() external nonReentrant returns (uint256 amount) {
        amount = accruedFees;
        if (amount == 0) return 0;
        accruedFees = 0;
        address to = feeRecipient;
        _sendUsdx(to, amount);
        emit FeesWithdrawn(to, amount);
    }

    // ── Admin ────────────────────────────────────────────────────────────

    /// @notice Sets the parameters for launches created from now on. Live
    ///         launches keep the parameters they were created with.
    function setConfig(Config calldata config_) external onlyOwner {
        _setConfig(config_);
    }

    function setFeeRecipient(address feeRecipient_) external onlyOwner {
        if (feeRecipient_ == address(0)) revert ZeroAddress();
        feeRecipient = feeRecipient_;
        emit FeeRecipientUpdated(feeRecipient_);
    }

    function pause() external onlyOwner {
        _pause();
    }

    function unpause() external onlyOwner {
        _unpause();
    }

    // ── Views ────────────────────────────────────────────────────────────

    function getLaunch(address token) external view returns (Launch memory) {
        return _launches[token];
    }

    function allTokensLength() external view returns (uint256) {
        return allTokens.length;
    }

    function isLaunch(address token) public view returns (bool) {
        return _launches[token].creator != address(0);
    }

    /// @notice Tokens still available on the curve.
    function remainingOnCurve(address token) external view returns (uint256) {
        Launch storage l = _launches[token];
        return l.curveSupply - l.tokensSold;
    }

    /// @notice Spot price in USDX wei per whole (1e18) token.
    function currentPrice(address token) external view returns (uint256) {
        Launch storage l = _launches[token];
        if (l.virtualToken == 0) return 0;
        return Math.mulDiv(l.virtualUsdx, 1e18, l.virtualToken);
    }

    /// @notice Quote for `buy` with `usdxIn` wei of value. `usdxUsed` is the
    ///         value actually consumed (less than `usdxIn` if the curve sells out).
    function quoteBuy(address token, uint256 usdxIn)
        external
        view
        returns (uint256 tokensOut, uint256 fee, uint256 usdxUsed)
    {
        Launch storage l = _launches[token];
        if (l.creator == address(0) || l.graduated) return (0, 0, 0);
        (tokensOut, fee, usdxUsed,) = _quoteBuy(l, usdxIn);
    }

    /// @notice Quote for `sell`.
    function quoteSell(address token, uint256 tokenAmount) external view returns (uint256 usdxOut, uint256 fee) {
        Launch storage l = _launches[token];
        if (l.creator == address(0) || l.graduated || tokenAmount > l.tokensSold) return (0, 0);
        uint256 gross = Math.mulDiv(tokenAmount, l.virtualUsdx, uint256(l.virtualToken) + tokenAmount);
        fee = gross * l.tradeFeeBps / BPS;
        usdxOut = gross - fee;
    }

    /// @notice Address `create` will deploy for (`creator`, `salt`, `name`, `symbol`).
    function computeTokenAddress(address creator, bytes32 salt, string calldata name, string calldata symbol)
        external
        view
        returns (address)
    {
        bytes32 initHash = keccak256(abi.encodePacked(type(LaunchToken).creationCode, abi.encode(name, symbol)));
        return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), _salt(creator, salt), initHash)))));
    }

    // ── Internals ────────────────────────────────────────────────────────

    function _liveLaunch(address token) internal view returns (Launch storage l) {
        l = _launches[token];
        if (l.creator == address(0)) revert UnknownLaunch();
        if (l.graduated) revert AlreadyGraduated();
    }

    function _salt(address creator, bytes32 salt) internal pure returns (bytes32) {
        return keccak256(abi.encode(creator, salt));
    }

    /// @dev Pure curve math for a buy of `value` wei. Returns the tokens out, the
    ///      fee, the gross value consumed, and the net amount added to the reserve.
    function _quoteBuy(Launch storage l, uint256 value)
        internal
        view
        returns (uint256 tokensOut, uint256 fee, uint256 gross, uint256 usdIn)
    {
        uint256 vU = l.virtualUsdx;
        uint256 vT = l.virtualToken;
        uint256 remaining = l.curveSupply - l.tokensSold;
        uint256 bps = l.tradeFeeBps;

        fee = value * bps / BPS;
        usdIn = value - fee;
        gross = value;
        tokensOut = Math.mulDiv(usdIn, vT, vU + usdIn); // rounds toward the curve

        if (tokensOut >= remaining) {
            // Sell exactly the remainder: smallest net input that yields it, then
            // gross it up by the fee. Never charge more than was sent.
            tokensOut = remaining;
            usdIn = Math.mulDiv(vU, remaining, vT - remaining, Math.Rounding.Ceil);
            gross = Math.mulDiv(usdIn, BPS, BPS - bps, Math.Rounding.Ceil);
            if (gross > value) gross = value;
            fee = gross - usdIn;
        }
    }

    function _buy(address token, Launch storage l, uint256 value, uint256 minTokensOut)
        internal
        returns (uint256 tokensOut)
    {
        uint256 fee;
        uint256 gross;
        uint256 usdIn;
        (tokensOut, fee, gross, usdIn) = _quoteBuy(l, value);
        if (tokensOut == 0) revert ZeroAmount();
        if (tokensOut < minTokensOut) revert SlippageExceeded();

        l.virtualUsdx += usdIn.toUint128();
        l.virtualToken -= tokensOut.toUint128();
        l.realUsdx += usdIn.toUint128();
        l.tokensSold += tokensOut.toUint128();
        accruedFees += fee;

        LaunchToken(token).transfer(msg.sender, tokensOut);
        if (value > gross) _sendUsdx(msg.sender, value - gross);

        _emitTrade(token, true, usdIn, tokensOut, fee, l);

        if (l.tokensSold == l.curveSupply) _graduate(token, l);
    }

    /// @dev Deposits the LP reserve + raised USDX into the Uniswap V2 pair by
    ///      transferring directly to the pair and calling `mint`, with the LP
    ///      minted to the LpVault. Direct deposit (instead of the router) makes the outcome
    ///      independent of any reserves the pair may already hold; combined with
    ///      the token's pre-graduation transfer lock, nobody can front-run the
    ///      price the pool opens at.
    function _graduate(address token, Launch storage l) internal {
        l.graduated = true;

        uint256 real = l.realUsdx;
        uint256 fee = l.graduationFee;
        if (fee > real) fee = real;
        uint256 usdxForLp = real - fee;
        uint256 usdx6 = usdxForLp / USDX_SCALE;
        uint256 usdxDeposited = usdx6 * USDX_SCALE;
        uint256 dust = usdxForLp - usdxDeposited;
        accruedFees += fee + dust;
        l.realUsdx = 0;

        // Sold out ⇒ what the launchpad still holds is exactly TOKEN_SUPPLY - curveSupply.
        uint256 tokensForLp = LaunchToken(token).balanceOf(address(this));

        address pair = dexFactory.getPair(token, address(usdx));
        if (pair == address(0)) pair = dexFactory.createPair(token, address(usdx));
        l.pair = pair;

        LaunchToken(token).openTrading();
        LaunchToken(token).transfer(pair, tokensForLp);
        if (!usdx.transfer(pair, usdx6)) revert TransferFailed();
        uint256 liquidity = IUniswapV2Pair(pair).mint(address(lpVault));
        lpVault.register(pair, token, liquidity);

        emit Graduated(token, pair, usdxDeposited, tokensForLp, liquidity, fee);
    }

    function _setConfig(Config memory c) internal {
        if (
            c.virtualUsdx == 0 || c.curveSupply == 0 || c.virtualToken <= c.curveSupply
                || c.curveSupply >= TOKEN_SUPPLY || c.tradeFeeBps > MAX_TRADE_FEE_BPS
        ) revert InvalidConfig();
        config = c;
        emit ConfigUpdated(c);
    }

    function _emitTrade(address token, bool isBuy, uint256 usdxAmount, uint256 tokenAmount, uint256 fee, Launch storage l)
        internal
    {
        emit Trade(
            token, msg.sender, isBuy, usdxAmount, tokenAmount, fee, l.virtualUsdx, l.virtualToken, l.realUsdx, l.tokensSold
        );
    }

    function _sendUsdx(address to, uint256 amount) internal {
        (bool ok,) = to.call{value: amount}("");
        if (!ok) revert TransferFailed();
    }
}
Contract ABI
JSON
[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "feeRecipient_",
        "type": "address"
      },
      {
        "internalType": "contract IERC20",
        "name": "usdx_",
        "type": "address"
      },
      {
        "internalType": "contract IUniswapV2Factory",
        "name": "dexFactory_",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "uint128",
            "name": "virtualUsdx",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "virtualToken",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "curveSupply",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "graduationFee",
            "type": "uint128"
          },
          {
            "internalType": "uint96",
            "name": "creationFee",
            "type": "uint96"
          },
          {
            "internalType": "uint16",
            "name": "tradeFeeBps",
            "type": "uint16"
          }
        ],
        "internalType": "struct Launchpad.Config",
        "name": "config_",
        "type": "tuple"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyGraduated",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "AmountExceedsSold",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EnforcedPause",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExpectedPause",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Expired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InsufficientCreationFee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidConfig",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotSoldOut",
    "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"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "bits",
        "type": "uint8"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "SafeCastOverflowedUintDowncast",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SlippageExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnknownLaunch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAmount",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "components": [
          {
            "internalType": "uint128",
            "name": "virtualUsdx",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "virtualToken",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "curveSupply",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "graduationFee",
            "type": "uint128"
          },
          {
            "internalType": "uint96",
            "name": "creationFee",
            "type": "uint96"
          },
          {
            "internalType": "uint16",
            "name": "tradeFeeBps",
            "type": "uint16"
          }
        ],
        "indexed": false,
        "internalType": "struct Launchpad.Config",
        "name": "config",
        "type": "tuple"
      }
    ],
    "name": "ConfigUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "virtualUsdx",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "virtualToken",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "curveSupply",
        "type": "uint256"
      }
    ],
    "name": "Created",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "feeRecipient",
        "type": "address"
      }
    ],
    "name": "FeeRecipientUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "FeesWithdrawn",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "pair",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "usdxToLp",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensToLp",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "liquidity",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }
    ],
    "name": "Graduated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "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": "trader",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "isBuy",
        "type": "bool"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "usdxAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "virtualUsdxAfter",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "virtualTokenAfter",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "realUsdxAfter",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "tokensSoldAfter",
        "type": "uint256"
      }
    ],
    "name": "Trade",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Unpaused",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_TRADE_FEE_BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOKEN_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "USDX_SCALE",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "accruedFees",
    "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"
      },
      {
        "internalType": "uint256",
        "name": "minTokensOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "name": "buy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokensOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      }
    ],
    "name": "computeTokenAddress",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "config",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "virtualUsdx",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "virtualToken",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "curveSupply",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "graduationFee",
        "type": "uint128"
      },
      {
        "internalType": "uint96",
        "name": "creationFee",
        "type": "uint96"
      },
      {
        "internalType": "uint16",
        "name": "tradeFeeBps",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "minTokensOut",
        "type": "uint256"
      }
    ],
    "name": "create",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokensOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "currentPrice",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "dexFactory",
    "outputs": [
      {
        "internalType": "contract IUniswapV2Factory",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "getLaunch",
    "outputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "uint64",
            "name": "createdAt",
            "type": "uint64"
          },
          {
            "internalType": "bool",
            "name": "graduated",
            "type": "bool"
          },
          {
            "internalType": "uint16",
            "name": "tradeFeeBps",
            "type": "uint16"
          },
          {
            "internalType": "uint128",
            "name": "virtualUsdx",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "virtualToken",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "realUsdx",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "tokensSold",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "curveSupply",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "graduationFee",
            "type": "uint128"
          },
          {
            "internalType": "address",
            "name": "pair",
            "type": "address"
          },
          {
            "internalType": "string",
            "name": "metadataURI",
            "type": "string"
          }
        ],
        "internalType": "struct Launchpad.Launch",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "graduate",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "isLaunch",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lpVault",
    "outputs": [
      {
        "internalType": "contract LpVault",
        "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": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "usdxIn",
        "type": "uint256"
      }
    ],
    "name": "quoteBuy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "tokensOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "usdxUsed",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      }
    ],
    "name": "quoteSell",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "usdxOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "remainingOnCurve",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minUsdxOut",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "name": "sell",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "usdxOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "uint128",
            "name": "virtualUsdx",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "virtualToken",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "curveSupply",
            "type": "uint128"
          },
          {
            "internalType": "uint128",
            "name": "graduationFee",
            "type": "uint128"
          },
          {
            "internalType": "uint96",
            "name": "creationFee",
            "type": "uint96"
          },
          {
            "internalType": "uint16",
            "name": "tradeFeeBps",
            "type": "uint16"
          }
        ],
        "internalType": "struct Launchpad.Config",
        "name": "config_",
        "type": "tuple"
      }
    ],
    "name": "setConfig",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "feeRecipient_",
        "type": "address"
      }
    ],
    "name": "setFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "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": "usdx",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "withdrawFees",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

YOUR GATEWAY TO TUVEN

Connect to possibility.

Choose a wallet. Your assets stay in your control.

No wallet detected. On mobile, open this page in your wallet’s built-in browser.
Get MetaMask

Tuven · Chain 99359 · USDX