Launchpad
0xc12f4cc3d6a1a91b03827632c4d744454350a352
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
22,948 bytes
ABI entries
56
30 read · 15 write
License
none
Contract information
- Address
- 0xc12f4cc3d6a1a91b03827632c4d744454350a352
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x844E6F6Cab…6bD3Ea681e
- Creation tx
- 0xfc27156985…7a516ea316
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (30)
DEAD() → address
FEE() → uint24
MAX_BPS() → uint256
PRECISION() → uint256
TICK_SPACING() → int24
TOTAL_SUPPLY() → uint256
creationFee() → uint256
creatorFeeBps() → uint256
feeRecipient() → address
getClaimable(address, address) → uint256
getMCAP(address) → uint256
getPrice(address) → uint256
getToken(address) → tuple
getTokenCount() → uint256
getTokens(uint256, uint256) → tuple[], uint256
hr(address, address) → uint256
mcapsOf(address[]) → uint256[]
owner() → address
poolLiquidityEth(address) → uint256
protocolFeeBps() → uint256
reg(address) → bool
startTick() → int24
ti(address) → uint256
tickSpan() → int24
tokens(uint256) → address, address, string, string, string, string, string, string, string, uint256, bool, address, uint256, bool, bool, uint256, uint256
totalFeesCollected() → uint256
ufptp(address, address) → uint256
v3factory() → address
v3pm() → address
weth() → address
Events (6)
FeesDistributedLaunchedOwnershipTransferredRewardsClaimedTokenCreatedTrade
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_v3pm",
"type": "address"
},
{
"internalType": "address",
"name": "_v3factory",
"type": "address"
},
{
"internalType": "address",
"name": "_weth",
"type": "address"
},
{
"internalType": "address",
"name": "_fr",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"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"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "t",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethToProtocol",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethToCreatorOrHolders",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "toHolders",
"type": "bool"
}
],
"name": "FeesDistributed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "t",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokensInPool",
"type": "uint256"
}
],
"name": "Launched",
"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": true,
"internalType": "address",
"name": "t",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "h",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "a",
"type": "uint256"
}
],
"name": "RewardsClaimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "i",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "t",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "c",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "n",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "s",
"type": "string"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ts",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "d",
"type": "bool"
}
],
"name": "TokenCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "t",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "tr",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "buy",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenAmt",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethAmt",
"type": "uint256"
}
],
"name": "Trade",
"type": "event"
},
{
"inputs": [],
"name": "DEAD",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PRECISION",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TICK_SPACING",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TOTAL_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
},
{
"internalType": "uint256",
"name": "minOut",
"type": "uint256"
}
],
"name": "buy",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
}
],
"name": "claimRewards",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
}
],
"name": "collectFeesSelf",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "nm",
"type": "string"
},
{
"internalType": "string",
"name": "sy",
"type": "string"
},
{
"internalType": "string",
"name": "ds",
"type": "string"
},
{
"internalType": "string",
"name": "im",
"type": "string"
},
{
"internalType": "string",
"name": "tw",
"type": "string"
},
{
"internalType": "string",
"name": "tg",
"type": "string"
},
{
"internalType": "string",
"name": "ws",
"type": "string"
},
{
"internalType": "bool",
"name": "_distribute",
"type": "bool"
},
{
"internalType": "uint256",
"name": "_firstBuy",
"type": "uint256"
},
{
"internalType": "string",
"name": "md",
"type": "string"
}
],
"name": "createToken",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "creationFee",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "creatorFeeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeRecipient",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
},
{
"internalType": "address",
"name": "holder",
"type": "address"
}
],
"name": "getClaimable",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
}
],
"name": "getMCAP",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
}
],
"name": "getPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
}
],
"name": "getToken",
"outputs": [
{
"components": [
{
"internalType": "contract LaunchpadToken",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "description",
"type": "string"
},
{
"internalType": "string",
"name": "imageUrl",
"type": "string"
},
{
"internalType": "string",
"name": "twitter",
"type": "string"
},
{
"internalType": "string",
"name": "telegram",
"type": "string"
},
{
"internalType": "string",
"name": "website",
"type": "string"
},
{
"internalType": "uint256",
"name": "createdAt",
"type": "uint256"
},
{
"internalType": "bool",
"name": "graduated",
"type": "bool"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"internalType": "bool",
"name": "wethIsToken0",
"type": "bool"
},
{
"internalType": "bool",
"name": "distribute",
"type": "bool"
},
{
"internalType": "uint256",
"name": "hfp",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "fpta",
"type": "uint256"
}
],
"internalType": "struct Launchpad.TokenInfo",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getTokenCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "o",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "l",
"type": "uint256"
}
],
"name": "getTokens",
"outputs": [
{
"components": [
{
"internalType": "contract LaunchpadToken",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "description",
"type": "string"
},
{
"internalType": "string",
"name": "imageUrl",
"type": "string"
},
{
"internalType": "string",
"name": "twitter",
"type": "string"
},
{
"internalType": "string",
"name": "telegram",
"type": "string"
},
{
"internalType": "string",
"name": "website",
"type": "string"
},
{
"internalType": "uint256",
"name": "createdAt",
"type": "uint256"
},
{
"internalType": "bool",
"name": "graduated",
"type": "bool"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"internalType": "bool",
"name": "wethIsToken0",
"type": "bool"
},
{
"internalType": "bool",
"name": "distribute",
"type": "bool"
},
{
"internalType": "uint256",
"name": "hfp",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "fpta",
"type": "uint256"
}
],
"internalType": "struct Launchpad.TokenInfo[]",
"name": "r",
"type": "tuple[]"
},
{
"internalType": "uint256",
"name": "t",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "hr",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "tas",
"type": "address[]"
}
],
"name": "mcapsOf",
"outputs": [
{
"internalType": "uint256[]",
"name": "r",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
},
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "onTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
}
],
"name": "pokeFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
}
],
"name": "poolLiquidityEth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolFeeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "reg",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "ta",
"type": "address"
},
{
"internalType": "uint256",
"name": "amt",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minEth",
"type": "uint256"
}
],
"name": "sell",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "f",
"type": "uint256"
}
],
"name": "setCreationFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "b",
"type": "uint256"
}
],
"name": "setCreatorFeeBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "_startTick",
"type": "int24"
},
{
"internalType": "int24",
"name": "_tickSpan",
"type": "int24"
}
],
"name": "setCurve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "fr",
"type": "address"
}
],
"name": "setFeeRecipient",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "b",
"type": "uint256"
}
],
"name": "setProtocolFeeBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "startTick",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "ti",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tickSpan",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "tokens",
"outputs": [
{
"internalType": "contract LaunchpadToken",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "description",
"type": "string"
},
{
"internalType": "string",
"name": "imageUrl",
"type": "string"
},
{
"internalType": "string",
"name": "twitter",
"type": "string"
},
{
"internalType": "string",
"name": "telegram",
"type": "string"
},
{
"internalType": "string",
"name": "website",
"type": "string"
},
{
"internalType": "uint256",
"name": "createdAt",
"type": "uint256"
},
{
"internalType": "bool",
"name": "graduated",
"type": "bool"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"internalType": "bool",
"name": "wethIsToken0",
"type": "bool"
},
{
"internalType": "bool",
"name": "distribute",
"type": "bool"
},
{
"internalType": "uint256",
"name": "hfp",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "fpta",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalFeesCollected",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "ufptp",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int256",
"name": "amount0Delta",
"type": "int256"
},
{
"internalType": "int256",
"name": "amount1Delta",
"type": "int256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "uniswapV3SwapCallback",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "v3factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "v3pm",
"outputs": [
{
"internalType": "contract INonfungiblePositionManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {LaunchpadToken} from "./LaunchpadToken.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
interface IWETH9 {
function deposit() external payable;
function withdraw(uint256) external;
function approve(address, uint256) external returns (bool);
function transfer(address, uint256) external returns (bool);
function balanceOf(address) external view returns (uint256);
}
interface INonfungiblePositionManager {
struct MintParams {
address token0; address token1; uint24 fee;
int24 tickLower; int24 tickUpper;
uint256 amount0Desired; uint256 amount1Desired;
uint256 amount0Min; uint256 amount1Min;
address recipient; uint256 deadline;
}
struct CollectParams {
uint256 tokenId; address recipient;
uint128 amount0Max; uint128 amount1Max;
}
function mint(MintParams calldata params)
external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
function collect(CollectParams calldata params)
external payable returns (uint256 amount0, uint256 amount1);
function createAndInitializePoolIfNecessary(address, address, uint24, uint160)
external payable returns (address);
}
interface IUniswapV3Pool {
function token0() external view returns (address);
function token1() external view returns (address);
function slot0() external view returns (
uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, uint8, bool
);
function swap(
address recipient, bool zeroForOne, int256 amountSpecified,
uint160 sqrtPriceLimitX96, bytes calldata data
) external returns (int256 amount0, int256 amount1);
}
/// @title Launchpad (Uniswap V3 native)
/// @notice At launch, the entire supply is deployed as a single-sided Uniswap V3
/// position (no ETH from the creator, only a flat service fee). Trading
/// happens directly on the V3 pool. Fee sharing ("distribute" tokens) is
/// sourced from the pool's 1% LP fees, collected and distributed pro-rata.
contract Launchpad is Ownable, ReentrancyGuard {
INonfungiblePositionManager public immutable v3pm;
address public immutable v3factory;
address public immutable weth;
uint256 public constant TOTAL_SUPPLY = 1_000_000_000e18;
uint24 public constant FEE = 10000; // 1% tier
int24 public constant TICK_SPACING = 200; // for the 1% tier
address public constant DEAD = 0x000000000000000000000000000000000000dEaD;
uint256 public constant MAX_BPS = 10000;
uint256 public constant PRECISION = 1e36;
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
// Curve placement. startTick sets the launch price (=> starting FDV); tickSpan
// sets how far the single-sided range extends upward. Both must be multiples of
// TICK_SPACING. Default targets ~1.5 ETH starting FDV (tuned via fork test).
int24 public startTick = -203200;
int24 public tickSpan = 69000;
uint256 public creationFee = 0.001 ether;
uint256 public protocolFeeBps = 25; // share of collected LP fee (relative to protocol+creator)
uint256 public creatorFeeBps = 75;
address public feeRecipient;
uint256 public totalFeesCollected;
struct TokenInfo {
LaunchpadToken token; address creator;
string name; string symbol; string description;
string imageUrl; string twitter; string telegram; string website;
uint256 createdAt; bool graduated; address pool;
uint256 positionId; bool wethIsToken0;
bool distribute; uint256 hfp; uint256 fpta;
}
TokenInfo[] public tokens;
mapping(address => uint256) public ti; // token => index
mapping(address => bool) public reg; // token => registered
mapping(address => mapping(address => uint256)) public ufptp; // token => holder => checkpoint
mapping(address => mapping(address => uint256)) public hr; // token => holder => accrued rewards (wei)
event TokenCreated(uint256 indexed i, address indexed t, address indexed c, string n, string s, uint256 ts, bool d);
event Launched(address indexed t, address indexed pool, uint256 positionId, uint256 tokensInPool);
event Trade(address indexed t, address indexed tr, bool buy, uint256 tokenAmt, uint256 ethAmt);
event FeesDistributed(address indexed t, uint256 ethToProtocol, uint256 ethToCreatorOrHolders, bool toHolders);
event RewardsClaimed(address indexed t, address indexed h, uint256 a);
constructor(address _v3pm, address _v3factory, address _weth, address _fr) Ownable(msg.sender) {
require(_v3pm != address(0) && _v3factory != address(0) && _weth != address(0) && _fr != address(0), "0");
v3pm = INonfungiblePositionManager(_v3pm);
v3factory = _v3factory;
weth = _weth;
feeRecipient = _fr;
}
// ---------------------------------------------------------------- launch
function createToken(
string memory nm, string memory sy, string memory ds,
string memory im, string memory tw, string memory tg, string memory ws,
bool _distribute, uint256 _firstBuy, string memory md
) external payable nonReentrant returns (address) {
require(msg.value == creationFee + _firstBuy, "val");
if (creationFee > 0) {
totalFeesCollected += creationFee;
(bool s,) = payable(feeRecipient).call{value: creationFee}("");
require(s, "fee");
}
LaunchpadToken token = new LaunchpadToken(nm, sy, msg.sender, address(this), TOTAL_SUPPLY, md);
tokens.push(TokenInfo({
token: token, creator: msg.sender, name: nm, symbol: sy,
description: ds, imageUrl: im, twitter: tw, telegram: tg, website: ws,
createdAt: block.timestamp, graduated: false, pool: address(0),
positionId: 0, wethIsToken0: weth < address(token),
distribute: _distribute, hfp: 0, fpta: 0
}));
uint256 idx = tokens.length - 1;
ti[address(token)] = idx;
reg[address(token)] = true;
emit TokenCreated(idx, address(token), msg.sender, nm, sy, block.timestamp, _distribute);
_deployPool(idx);
if (_firstBuy > 0) {
_buy(address(token), _firstBuy, msg.sender);
}
return address(token);
}
/// @dev Creates the pool, initializes it at the launch price, and mints the
/// entire supply as a single-sided position owned by this contract.
function _deployPool(uint256 idx) internal {
TokenInfo storage info = tokens[idx];
address tok = address(info.token);
int24 tickLower; int24 tickUpper; int24 curTick;
if (info.wethIsToken0) {
// token is token1; range sits below the current price, priced at the top.
curTick = -startTick;
tickUpper = -startTick;
tickLower = -startTick - tickSpan;
} else {
// token is token0; range sits above the current price, priced at the bottom.
curTick = startTick;
tickLower = startTick;
tickUpper = startTick + tickSpan;
}
(address token0, address token1) = info.wethIsToken0 ? (weth, tok) : (tok, weth);
uint160 sqrtP = getSqrtRatioAtTick(curTick);
address pool = v3pm.createAndInitializePoolIfNecessary(token0, token1, FEE, sqrtP);
info.pool = pool;
uint256 supply = info.token.balanceOf(address(this));
info.token.approve(address(v3pm), supply);
(uint256 positionId,,,) = v3pm.mint(INonfungiblePositionManager.MintParams({
token0: token0, token1: token1, fee: FEE,
tickLower: tickLower, tickUpper: tickUpper,
amount0Desired: info.wethIsToken0 ? 0 : supply,
amount1Desired: info.wethIsToken0 ? supply : 0,
amount0Min: 0, amount1Min: 0,
recipient: address(this), deadline: block.timestamp
}));
info.positionId = positionId;
emit Launched(tok, pool, positionId, supply);
}
// ---------------------------------------------------------------- trading
function buy(address ta, uint256 minOut) external payable nonReentrant returns (uint256) {
require(msg.value > 0, "0");
_check(ta);
_tryCollect(ta);
uint256 out = _buy(ta, msg.value, msg.sender);
require(out >= minOut, "slip");
return out;
}
function sell(address ta, uint256 amt, uint256 minEth) external nonReentrant returns (uint256) {
require(amt > 0, "0");
TokenInfo storage info = _check(ta);
_tryCollect(ta);
info.token.transferFrom(msg.sender, address(this), amt);
uint256 wethOut = _swap(ta, address(info.token) == IUniswapV3Pool(info.pool).token0(), amt, address(this));
IWETH9(weth).withdraw(wethOut);
require(wethOut >= minEth, "slip");
(bool s,) = payable(msg.sender).call{value: wethOut}("");
require(s, "eth");
emit Trade(ta, msg.sender, false, amt, wethOut);
return wethOut;
}
/// @dev Spends `ethIn` (already held by this contract as ETH) buying `ta` for `to`.
function _buy(address ta, uint256 ethIn, address to) internal returns (uint256) {
TokenInfo storage info = tokens[ti[ta]];
IWETH9(weth).deposit{value: ethIn}();
// zeroForOne = selling weth for token = weth is the input(token0) side.
uint256 out = _swap(ta, info.wethIsToken0, ethIn, to);
emit Trade(ta, to, true, out, ethIn);
return out;
}
/// @dev Exact-input swap on the token's pool. Input token must already be held
/// by this contract (WETH for buys, the launch token for sells).
function _swap(address ta, bool zeroForOne, uint256 amountIn, address recipient)
internal returns (uint256 amountOut)
{
TokenInfo storage info = tokens[ti[ta]];
(int256 a0, int256 a1) = IUniswapV3Pool(info.pool).swap(
recipient, zeroForOne, int256(amountIn),
zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,
abi.encode(ta)
);
// output is the negative delta of the non-input side.
amountOut = uint256(-(zeroForOne ? a1 : a0));
}
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
address ta = abi.decode(data, (address));
TokenInfo storage info = _check(ta);
require(msg.sender == info.pool, "pool");
IUniswapV3Pool pool = IUniswapV3Pool(info.pool);
if (amount0Delta > 0) IERC20(pool.token0()).transfer(msg.sender, uint256(amount0Delta));
if (amount1Delta > 0) IERC20(pool.token1()).transfer(msg.sender, uint256(amount1Delta));
}
// ---------------------------------------------------------- fee sharing
/// @notice Permissionless: pull accrued LP fees and distribute them.
function pokeFees(address ta) external nonReentrant {
_check(ta);
_collectFees(ta);
}
/// @dev Best-effort fee collection during trades: never let a fee-conversion
/// revert block buying/selling.
function _tryCollect(address ta) internal {
try this.collectFeesSelf(ta) {} catch {}
}
function collectFeesSelf(address ta) external {
require(msg.sender == address(this), "self");
_collectFees(ta);
}
function _collectFees(address ta) internal {
TokenInfo storage info = tokens[ti[ta]];
(uint256 a0, uint256 a1) = v3pm.collect(INonfungiblePositionManager.CollectParams({
tokenId: info.positionId, recipient: address(this),
amount0Max: type(uint128).max, amount1Max: type(uint128).max
}));
(uint256 tokenFee, uint256 wethFee) = info.wethIsToken0 ? (a1, a0) : (a0, a1);
if (tokenFee == 0 && wethFee == 0) return;
// Convert the token-denominated portion of fees into WETH so everything is
// distributed in ETH, matching the claim model.
if (tokenFee > 0) {
wethFee += _swap(ta, address(info.token) == IUniswapV3Pool(info.pool).token0(), tokenFee, address(this));
}
if (wethFee == 0) return;
IWETH9(weth).withdraw(wethFee); // WETH -> ETH held by this contract
uint256 denom = protocolFeeBps + creatorFeeBps;
uint256 toProtocol = denom == 0 ? 0 : (wethFee * protocolFeeBps) / denom;
uint256 toCreator = wethFee - toProtocol;
if (toProtocol > 0) {
totalFeesCollected += toProtocol;
(bool ps,) = payable(feeRecipient).call{value: toProtocol}("");
require(ps, "pf");
}
if (toCreator > 0) {
if (info.distribute) {
_addHF(ta, toCreator); // pooled pro-rata to holders (ETH kept in contract)
} else {
(bool cs,) = payable(info.creator).call{value: toCreator}("");
require(cs, "cf");
}
}
emit FeesDistributed(ta, toProtocol, toCreator, info.distribute);
}
// -------------------------------------------------- reward accounting
function onTransfer(address ta, address from, address to) external {
TokenInfo storage info = tokens[ti[ta]];
require(msg.sender == address(info.token), "only token");
if (!info.distribute) return;
_settle(ta, from);
_settle(ta, to);
}
function _excluded(TokenInfo storage info, address a) internal view returns (bool) {
return a == address(0) || a == DEAD || a == address(this) || a == info.pool || a == address(v3pm);
}
function _circulating(TokenInfo storage info) internal view returns (uint256) {
uint256 s = TOTAL_SUPPLY;
s -= info.token.balanceOf(address(this));
s -= info.token.balanceOf(info.pool);
s -= info.token.balanceOf(DEAD);
s -= info.token.balanceOf(address(v3pm));
return s;
}
function _settle(address ta, address holder) internal {
TokenInfo storage info = tokens[ti[ta]];
if (!info.distribute || _excluded(info, holder)) return;
uint256 bal = info.token.balanceOf(holder);
if (bal == 0 || info.fpta <= ufptp[ta][holder]) {
ufptp[ta][holder] = info.fpta;
return;
}
uint256 pending = (bal * (info.fpta - ufptp[ta][holder])) / PRECISION;
if (pending > 0) hr[ta][holder] += pending;
ufptp[ta][holder] = info.fpta;
}
function _addHF(address ta, uint256 amt) internal {
TokenInfo storage info = tokens[ti[ta]];
if (!info.distribute || amt == 0) return;
uint256 circ = _circulating(info);
if (circ < 100e18) {
// no eligible holders yet; route to creator so ETH is not stranded.
(bool cs,) = payable(info.creator).call{value: amt}("");
require(cs, "cf2");
return;
}
info.hfp += amt;
info.fpta += (amt * PRECISION) / circ;
}
function getClaimable(address ta, address holder) public view returns (uint256) {
TokenInfo storage info = tokens[ti[ta]];
if (!info.distribute || _excluded(info, holder)) return 0;
uint256 bal = info.token.balanceOf(holder);
uint256 pending = bal == 0 ? 0 : (bal * (info.fpta - ufptp[ta][holder])) / PRECISION;
return hr[ta][holder] + pending;
}
function claimRewards(address ta) external nonReentrant returns (uint256) {
TokenInfo storage info = _check(ta);
require(info.distribute, "off");
_tryCollect(ta); // sweep latest LP fees (incl. external Uniswap trades) before paying
_settle(ta, msg.sender);
uint256 amt = hr[ta][msg.sender];
require(amt > 0, "0");
hr[ta][msg.sender] = 0;
info.hfp -= amt;
(bool s,) = payable(msg.sender).call{value: amt}("");
require(s, "eth");
emit RewardsClaimed(ta, msg.sender, amt);
return amt;
}
// ------------------------------------------------------------- views
/// @return price ETH (wei) per whole token, from the live pool price.
function getPrice(address ta) public view returns (uint256) {
TokenInfo storage info = _check(ta);
(uint160 sqrtP,,,,,,) = IUniswapV3Pool(info.pool).slot0();
// p = rawPrice(token1/token0) * 2^96
uint256 p = (uint256(sqrtP) * uint256(sqrtP)) >> 96;
if (p == 0) return 0;
// return value = (ETH per whole token) * 1e18
if (info.wethIsToken0) {
// rawPrice = token/weth => ETH per token = 2^96 / p
return (uint256(1e18) << 96) / p;
} else {
// rawPrice = weth/token => ETH per token = p / 2^96
return (p * 1e18) >> 96;
}
}
function getMCAP(address ta) public view returns (uint256) {
return (TOTAL_SUPPLY * getPrice(ta)) / 1e18;
}
/// @notice Batch market caps (wei ETH) for a list of tokens — one RPC call for cards.
function mcapsOf(address[] calldata tas) external view returns (uint256[] memory r) {
r = new uint256[](tas.length);
for (uint256 i; i < tas.length; i++) {
if (reg[tas[i]]) r[i] = getMCAP(tas[i]);
}
}
/// @notice ETH liquidity (wei) currently sitting in a token's pool.
function poolLiquidityEth(address ta) external view returns (uint256) {
return IWETH9(weth).balanceOf(_check(ta).pool);
}
function getTokenCount() external view returns (uint256) { return tokens.length; }
function getTokens(uint256 o, uint256 l) external view returns (TokenInfo[] memory r, uint256 t) {
t = tokens.length;
uint256 e = o + l; if (e > t) e = t;
if (o >= e) return (new TokenInfo[](0), t);
r = new TokenInfo[](e - o);
for (uint256 i = o; i < e; i++) r[i - o] = tokens[i];
}
function getToken(address ta) external view returns (TokenInfo memory) { return _check(ta); }
// ------------------------------------------------------------- admin
function setCreationFee(uint256 f) external onlyOwner { creationFee = f; }
function setProtocolFeeBps(uint256 b) external onlyOwner { protocolFeeBps = b; }
function setCreatorFeeBps(uint256 b) external onlyOwner { creatorFeeBps = b; }
function setFeeRecipient(address fr) external onlyOwner { require(fr != address(0), "0"); feeRecipient = fr; }
function setCurve(int24 _startTick, int24 _tickSpan) external onlyOwner {
require(_startTick % TICK_SPACING == 0 && _tickSpan % TICK_SPACING == 0 && _tickSpan > 0, "align");
startTick = _startTick; tickSpan = _tickSpan;
}
function _check(address ta) internal view returns (TokenInfo storage) {
require(reg[ta], "reg");
return tokens[ti[ta]];
}
// --------------------------------------------------- TickMath (0.8 port)
/// @notice Uniswap V3 TickMath.getSqrtRatioAtTick, ported to Solidity 0.8.
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
unchecked {
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
require(absTick <= 887272, "T");
uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
if (tick > 0) ratio = type(uint256).max / ratio;
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
}
}
receive() external payable {}
}
Chain explorer3589msChain node85ms