Launchpad
0x49dca5ef4d432455abb39fa9c116df5d6bbc3110VerifiedNative balance0USDX
Account typeSmart contract
NetworkTuven 99359
Overview
- Contract name
- Launchpad
- Verification
- Verified source
- Creation transaction
- 0x13a49c2f1179dff22aa4d272b7ea2a5194937943c06ab6cd0b08a771db1a0a07
- Balance updated at block
- 16859
// 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
[
{
"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"
}
]