PonsLaunchFactory
0xa5aab3f0c6eeadf30ef1d3eb997108e976351feb
Verification
Verified
v0.8.30+commit.73712a01
Type
Contract
24,353 bytes
ABI entries
58
13 read · 11 write
License
none
Contract information
- Address
- 0xa5aab3f0c6eeadf30ef1d3eb997108e976351feb
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.30+commit.73712a01
- Optimization
- Enabled
- Creator
- 0xda4bCee76B…2042043968
- Creation tx
- 0x836c5e41d4…dfbe0c0e67
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (13)
dexConfigCount() → uint256
getDexConfig(uint256) → tuple
getLaunchConfig(uint256) → tuple
getLaunchedToken(address) → tuple
graduationStatus(address) → uint256, uint256, bool
launchConfigCount() → uint256
launchEnabled() → bool
launchFee() → uint256
locker() → address
owner() → address
pendingOwner() → address
predictTokenAddress(tuple, uint256, uint256, bytes32, address) → address
whitelistedLaunchers(address) → bool
Events (11)
DexConfigAddedDexStatusUpdatedLaunchConfigAddedLaunchConfigUpdatedLaunchEnabledUpdatedLaunchFeeUpdatedOwnershipTransferStartedOwnershipTransferredTokenDeployedTokenLaunchedWhitelistedLauncherUpdated
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
},
{
"internalType": "address",
"name": "locker_",
"type": "address"
},
{
"internalType": "uint256",
"name": "initialLaunchFee",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "DexDisabled",
"type": "error"
},
{
"inputs": [],
"name": "FeeTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "InvalidBasisPoints",
"type": "error"
},
{
"inputs": [],
"name": "InvalidDexConfig",
"type": "error"
},
{
"inputs": [],
"name": "InvalidDexId",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLaunchConfigId",
"type": "error"
},
{
"inputs": [],
"name": "InvalidMaxTxBasisPoints",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTick",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTokenParams",
"type": "error"
},
{
"inputs": [],
"name": "LaunchConfigDisabled",
"type": "error"
},
{
"inputs": [],
"name": "LaunchFeeNotPaid",
"type": "error"
},
{
"inputs": [],
"name": "NotWhitelisted",
"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": "PoolAlreadyExists",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "RouterNotSet",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "SupplyTooLow",
"type": "error"
},
{
"inputs": [],
"name": "TokenDeploymentFailed",
"type": "error"
},
{
"inputs": [],
"name": "TokenNotFound",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "address",
"name": "factory",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "positionManager",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "swapRouter",
"type": "address"
},
{
"indexed": false,
"internalType": "uint24",
"name": "poolFee",
"type": "uint24"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"name": "DexConfigAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "DexStatusUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "pairToken",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "graduationThreshold",
"type": "uint256"
},
{
"indexed": false,
"internalType": "int24",
"name": "initialTick",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint256",
"name": "supply",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint16",
"name": "maxWalletBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "maxTxBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint32",
"name": "restrictionBlocks",
"type": "uint32"
},
{
"indexed": false,
"internalType": "uint24",
"name": "reservedFee",
"type": "uint24"
},
{
"indexed": false,
"internalType": "bool",
"name": "enabled",
"type": "bool"
},
{
"indexed": false,
"internalType": "bool",
"name": "routerRequiresDeadline",
"type": "bool"
}
],
"name": "LaunchConfigAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "pairToken",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "graduationThreshold",
"type": "uint256"
},
{
"indexed": false,
"internalType": "int24",
"name": "initialTick",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint256",
"name": "supply",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint16",
"name": "maxWalletBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "maxTxBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint32",
"name": "restrictionBlocks",
"type": "uint32"
},
{
"indexed": false,
"internalType": "uint24",
"name": "reservedFee",
"type": "uint24"
},
{
"indexed": false,
"internalType": "bool",
"name": "enabled",
"type": "bool"
},
{
"indexed": false,
"internalType": "bool",
"name": "routerRequiresDeadline",
"type": "bool"
}
],
"name": "LaunchConfigUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "LaunchEnabledUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "launchFee",
"type": "uint256"
}
],
"name": "LaunchFeeUpdated",
"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": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "dexFactory",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "pairToken",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "dexId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "launchConfigId",
"type": "uint256"
}
],
"name": "TokenDeployed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "dexFactory",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "pairToken",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "dexId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "launchConfigId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "restrictionsEndBlock",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "initialBuyAmount",
"type": "uint256"
}
],
"name": "TokenLaunched",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "launcher",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "WhitelistedLauncherUpdated",
"type": "event"
},
{
"inputs": [],
"name": "acceptOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "address",
"name": "factory",
"type": "address"
},
{
"internalType": "address",
"name": "positionManager",
"type": "address"
},
{
"internalType": "address",
"name": "swapRouter",
"type": "address"
},
{
"internalType": "uint24",
"name": "poolFee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"internalType": "struct PonsLaunchFactory.DexConfig",
"name": "config",
"type": "tuple"
}
],
"name": "addDexConfig",
"outputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "pairToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "graduationThreshold",
"type": "uint256"
},
{
"internalType": "int24",
"name": "initialTick",
"type": "int24"
},
{
"internalType": "uint256",
"name": "supply",
"type": "uint256"
},
{
"internalType": "uint16",
"name": "maxWalletBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "maxTxBps",
"type": "uint16"
},
{
"internalType": "uint32",
"name": "restrictionBlocks",
"type": "uint32"
},
{
"internalType": "uint24",
"name": "reservedFee",
"type": "uint24"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
},
{
"internalType": "bool",
"name": "routerRequiresDeadline",
"type": "bool"
}
],
"internalType": "struct PonsLaunchFactory.LaunchConfig",
"name": "config",
"type": "tuple"
}
],
"name": "addLaunchConfig",
"outputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "dexConfigCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "getDexConfig",
"outputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "address",
"name": "factory",
"type": "address"
},
{
"internalType": "address",
"name": "positionManager",
"type": "address"
},
{
"internalType": "address",
"name": "swapRouter",
"type": "address"
},
{
"internalType": "uint24",
"name": "poolFee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"internalType": "struct PonsLaunchFactory.DexConfig",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "getLaunchConfig",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "pairToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "graduationThreshold",
"type": "uint256"
},
{
"internalType": "int24",
"name": "initialTick",
"type": "int24"
},
{
"internalType": "uint256",
"name": "supply",
"type": "uint256"
},
{
"internalType": "uint16",
"name": "maxWalletBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "maxTxBps",
"type": "uint16"
},
{
"internalType": "uint32",
"name": "restrictionBlocks",
"type": "uint32"
},
{
"internalType": "uint24",
"name": "reservedFee",
"type": "uint24"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
},
{
"internalType": "bool",
"name": "routerRequiresDeadline",
"type": "bool"
}
],
"internalType": "struct PonsLaunchFactory.LaunchConfig",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "getLaunchedToken",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"internalType": "address",
"name": "pairedToken",
"type": "address"
},
{
"internalType": "address",
"name": "positionManager",
"type": "address"
},
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dexId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "launchConfigId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "restrictionsEndBlock",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "supply",
"type": "uint256"
},
{
"internalType": "bool",
"name": "isToken0",
"type": "bool"
},
{
"internalType": "uint24",
"name": "poolFee",
"type": "uint24"
},
{
"internalType": "bool",
"name": "exists",
"type": "bool"
},
{
"internalType": "uint256",
"name": "initialBuyAmount",
"type": "uint256"
}
],
"internalType": "struct IPonsLaunchFactory.LaunchedToken",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "graduationStatus",
"outputs": [
{
"internalType": "uint256",
"name": "pairedPrincipal",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "threshold",
"type": "uint256"
},
{
"internalType": "bool",
"name": "graduated",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launchConfigCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launchEnabled",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launchFee",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "logo",
"type": "string"
},
{
"internalType": "string",
"name": "description",
"type": "string"
},
{
"components": [
{
"internalType": "string",
"name": "twitter",
"type": "string"
},
{
"internalType": "string",
"name": "telegram",
"type": "string"
},
{
"internalType": "string",
"name": "discord",
"type": "string"
},
{
"internalType": "string",
"name": "website",
"type": "string"
},
{
"internalType": "string",
"name": "farcaster",
"type": "string"
}
],
"internalType": "struct PonsLaunchFactory.Socials",
"name": "socials",
"type": "tuple"
},
{
"internalType": "address",
"name": "feeWallet",
"type": "address"
}
],
"internalType": "struct PonsLaunchFactory.TokenParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "launchConfigId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dexId",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"name": "launchToken",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "locker",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "logo",
"type": "string"
},
{
"internalType": "string",
"name": "description",
"type": "string"
},
{
"components": [
{
"internalType": "string",
"name": "twitter",
"type": "string"
},
{
"internalType": "string",
"name": "telegram",
"type": "string"
},
{
"internalType": "string",
"name": "discord",
"type": "string"
},
{
"internalType": "string",
"name": "website",
"type": "string"
},
{
"internalType": "string",
"name": "farcaster",
"type": "string"
}
],
"internalType": "struct PonsLaunchFactory.Socials",
"name": "socials",
"type": "tuple"
},
{
"internalType": "address",
"name": "feeWallet",
"type": "address"
}
],
"internalType": "struct PonsLaunchFactory.TokenParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "launchConfigId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dexId",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "address",
"name": "tokenDeployer",
"type": "address"
}
],
"name": "predictTokenAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "setDexStatus",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "setLaunchEnabled",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "newLaunchFee",
"type": "uint256"
}
],
"name": "setLaunchFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "launcher",
"type": "address"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "setWhitelistedLauncher",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "pairToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "graduationThreshold",
"type": "uint256"
},
{
"internalType": "int24",
"name": "initialTick",
"type": "int24"
},
{
"internalType": "uint256",
"name": "supply",
"type": "uint256"
},
{
"internalType": "uint16",
"name": "maxWalletBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "maxTxBps",
"type": "uint16"
},
{
"internalType": "uint32",
"name": "restrictionBlocks",
"type": "uint32"
},
{
"internalType": "uint24",
"name": "reservedFee",
"type": "uint24"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
},
{
"internalType": "bool",
"name": "routerRequiresDeadline",
"type": "bool"
}
],
"internalType": "struct PonsLaunchFactory.LaunchConfig",
"name": "config",
"type": "tuple"
}
],
"name": "updateLaunchConfig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "launcher",
"type": "address"
}
],
"name": "whitelistedLaunchers",
"outputs": [
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {PonsLauncherToken} from "./PonsLauncherToken.sol";
import {PonsLiquidityMath} from "./libraries/PonsLiquidityMath.sol";
import {PonsTickMath} from "./libraries/PonsTickMath.sol";
import {
INonfungiblePositionManagerLike,
IPonsLaunchFactory,
IPonsLaunchLocker,
ISwapRouter02Like,
ISwapRouterV3Like,
IUniswapV3FactoryLike,
IUniswapV3PoolStateLike
} from "./interfaces/ILaunchpad.sol";
/**
* @title PonsLaunchFactory
* @notice Deploys fixed-supply tokens directly into one-sided Uniswap V3
* positions, permanently locks the position NFT, and optionally executes an
* initial native-token buy in the same atomic transaction.
*/
contract PonsLaunchFactory is Ownable2Step, ReentrancyGuard, IPonsLaunchFactory {
using SafeERC20 for IERC20;
int24 private constant MIN_TICK = -887272;
int24 private constant MAX_TICK = 887272;
uint256 private constant BASIS_POINTS = 10_000;
struct Socials {
string twitter;
string telegram;
string discord;
string website;
string farcaster;
}
struct TokenParams {
string name;
string symbol;
string logo;
string description;
Socials socials;
address feeWallet;
}
struct DexConfig {
string name;
address factory;
address positionManager;
address swapRouter;
uint24 poolFee;
int24 tickSpacing;
bool enabled;
}
struct LaunchConfig {
address pairToken;
uint256 graduationThreshold;
int24 initialTick;
uint256 supply;
uint16 maxWalletBps;
uint16 maxTxBps;
uint32 restrictionBlocks;
uint24 reservedFee;
bool enabled;
bool routerRequiresDeadline;
}
error InvalidDexConfig();
error InvalidDexId();
error DexDisabled();
error InvalidLaunchConfigId();
error LaunchConfigDisabled();
error InvalidBasisPoints();
error InvalidMaxTxBasisPoints();
error SupplyTooLow();
error LaunchFeeNotPaid();
error NotWhitelisted();
error FeeTransferFailed();
error TokenDeploymentFailed();
error PoolAlreadyExists();
error RouterNotSet();
error ZeroAddress();
error InvalidTokenParams();
error TokenNotFound();
event TokenDeployed(
address indexed token,
address indexed deployer,
address indexed dexFactory,
address pairToken,
uint256 dexId,
uint256 launchConfigId
);
event TokenLaunched(
address indexed token,
address indexed deployer,
address indexed dexFactory,
address pairToken,
address pool,
uint256 dexId,
uint256 launchConfigId,
uint256 positionId,
uint256 restrictionsEndBlock,
uint256 initialBuyAmount
);
event DexConfigAdded(
uint256 indexed id,
string name,
address factory,
address positionManager,
address swapRouter,
uint24 poolFee,
int24 tickSpacing
);
event DexStatusUpdated(uint256 indexed id, bool enabled);
event LaunchConfigAdded(
uint256 indexed id,
address pairToken,
uint256 graduationThreshold,
int24 initialTick,
uint256 supply,
uint16 maxWalletBps,
uint16 maxTxBps,
uint32 restrictionBlocks,
uint24 reservedFee,
bool enabled,
bool routerRequiresDeadline
);
event LaunchConfigUpdated(
uint256 indexed id,
address pairToken,
uint256 graduationThreshold,
int24 initialTick,
uint256 supply,
uint16 maxWalletBps,
uint16 maxTxBps,
uint32 restrictionBlocks,
uint24 reservedFee,
bool enabled,
bool routerRequiresDeadline
);
event LaunchFeeUpdated(uint256 launchFee);
event LaunchEnabledUpdated(bool enabled);
event WhitelistedLauncherUpdated(address indexed launcher, bool enabled);
address public immutable locker;
uint256 public launchFee;
bool public launchEnabled;
mapping(address launcher => bool enabled) public whitelistedLaunchers;
mapping(address token => LaunchedToken launched) private _launchedTokens;
mapping(address token => uint256 threshold) private _graduationThresholds;
DexConfig[] private _dexConfigs;
LaunchConfig[] private _launchConfigs;
constructor(address initialOwner, address locker_, uint256 initialLaunchFee) Ownable(initialOwner) {
if (locker_ == address(0)) revert ZeroAddress();
locker = locker_;
launchFee = initialLaunchFee;
}
/**
* @notice Returns the number of configured V3 deployments.
*/
function dexConfigCount() external view returns (uint256) {
return _dexConfigs.length;
}
/**
* @notice Returns the number of launch configurations.
*/
function launchConfigCount() external view returns (uint256) {
return _launchConfigs.length;
}
/**
* @notice Returns one V3 deployment configuration.
*/
function getDexConfig(uint256 id) external view returns (DexConfig memory) {
if (id >= _dexConfigs.length) revert InvalidDexId();
return _dexConfigs[id];
}
/**
* @notice Returns one token launch configuration.
*/
function getLaunchConfig(uint256 id) external view returns (LaunchConfig memory) {
if (id >= _launchConfigs.length) revert InvalidLaunchConfigId();
return _launchConfigs[id];
}
/**
* @notice Returns the immutable record for a token created by this factory.
*/
function getLaunchedToken(address token) external view override returns (LaunchedToken memory) {
return _launchedTokens[token];
}
/**
* @notice Derives graduation from paired-token principal in the locked V3 position.
* Direct token donations to the pool do not affect this value. A zero threshold
* disables graduation for the token.
*/
function graduationStatus(address token)
external
view
returns (uint256 pairedPrincipal, uint256 threshold, bool graduated)
{
LaunchedToken memory launched = _launchedTokens[token];
if (!launched.exists) revert TokenNotFound();
address pool = PonsLauncherToken(token).liquidityPool();
(uint160 sqrtPriceX96,,,,,,) = IUniswapV3PoolStateLike(pool).slot0();
(,,,,, int24 tickLower, int24 tickUpper, uint128 liquidity,,,,) =
INonfungiblePositionManagerLike(launched.positionManager).positions(launched.positionId);
(uint256 amount0, uint256 amount1) = PonsLiquidityMath.getAmountsForLiquidity(
sqrtPriceX96,
PonsTickMath.getSqrtRatioAtTick(tickLower),
PonsTickMath.getSqrtRatioAtTick(tickUpper),
liquidity
);
pairedPrincipal = launched.isToken0 ? amount1 : amount0;
threshold = _graduationThresholds[token];
graduated = threshold != 0 && pairedPrincipal >= threshold;
}
/**
* @notice Adds a V3 factory, position manager, and router combination.
*/
function addDexConfig(DexConfig calldata config) external onlyOwner returns (uint256 id) {
_validateDexConfig(config);
id = _dexConfigs.length;
_dexConfigs.push(config);
emit DexConfigAdded(
id,
config.name,
config.factory,
config.positionManager,
config.swapRouter,
config.poolFee,
config.tickSpacing
);
}
/**
* @notice Enables or disables a configured V3 deployment.
*/
function setDexStatus(uint256 id, bool enabled) external onlyOwner {
if (id >= _dexConfigs.length) revert InvalidDexId();
_dexConfigs[id].enabled = enabled;
emit DexStatusUpdated(id, enabled);
}
/**
* @notice Adds a launch configuration.
*/
function addLaunchConfig(LaunchConfig calldata config) external onlyOwner returns (uint256 id) {
_validateLaunchConfig(config);
id = _launchConfigs.length;
_launchConfigs.push(config);
_emitLaunchConfigAdded(id, config);
}
/**
* @notice Replaces an existing launch configuration.
*/
function updateLaunchConfig(uint256 id, LaunchConfig calldata config) external onlyOwner {
if (id >= _launchConfigs.length) revert InvalidLaunchConfigId();
_validateLaunchConfig(config);
_launchConfigs[id] = config;
emit LaunchConfigUpdated(
id,
config.pairToken,
config.graduationThreshold,
config.initialTick,
config.supply,
config.maxWalletBps,
config.maxTxBps,
config.restrictionBlocks,
config.reservedFee,
config.enabled,
config.routerRequiresDeadline
);
}
/**
* @notice Changes the fixed native launch fee.
*/
function setLaunchFee(uint256 newLaunchFee) external onlyOwner {
launchFee = newLaunchFee;
emit LaunchFeeUpdated(newLaunchFee);
}
/**
* @notice Opens or closes launches to non-whitelisted callers.
*/
function setLaunchEnabled(bool enabled) external onlyOwner {
launchEnabled = enabled;
emit LaunchEnabledUpdated(enabled);
}
/**
* @notice Grants or revokes permission to launch while the public gate is closed.
*/
function setWhitelistedLauncher(address launcher, bool enabled) external onlyOwner {
if (launcher == address(0)) revert ZeroAddress();
whitelistedLaunchers[launcher] = enabled;
emit WhitelistedLauncherUpdated(launcher, enabled);
}
/**
* @notice Atomically deploys, pools, locks, records, and optionally buys a token.
*/
function launchToken(TokenParams calldata params, uint256 launchConfigId, uint256 dexId, bytes32 salt)
external
payable
nonReentrant
returns (address token)
{
if (!launchEnabled && !whitelistedLaunchers[msg.sender]) {
revert NotWhitelisted();
}
if (msg.value < launchFee) revert LaunchFeeNotPaid();
if (dexId >= _dexConfigs.length) revert InvalidDexId();
if (launchConfigId >= _launchConfigs.length) {
revert InvalidLaunchConfigId();
}
if (bytes(params.name).length == 0 || bytes(params.symbol).length == 0) {
revert InvalidTokenParams();
}
DexConfig memory dex = _dexConfigs[dexId];
LaunchConfig memory config = _launchConfigs[launchConfigId];
if (!dex.enabled) revert DexDisabled();
if (!config.enabled) revert LaunchConfigDisabled();
address initialBuyRecipient = params.feeWallet == address(0) ? msg.sender : params.feeWallet;
bytes memory creationCode = _buildTokenCreationCode(params, config, dex, msg.sender);
address predictedToken = _computeCreate2Address(salt, keccak256(creationCode));
if (IUniswapV3FactoryLike(dex.factory).getPool(predictedToken, config.pairToken, dex.poolFee) != address(0)) {
revert PoolAlreadyExists();
}
_payLaunchFee();
token = _deployToken(salt, creationCode);
emit TokenDeployed(token, msg.sender, dex.factory, config.pairToken, dexId, launchConfigId);
bool isToken0 = token < config.pairToken;
int24 poolTick = isToken0 ? config.initialTick : -config.initialTick;
(int24 tickLower, int24 tickUpper) = _positionRange(isToken0, config.initialTick, dex.tickSpacing);
INonfungiblePositionManagerLike manager = INonfungiblePositionManagerLike(dex.positionManager);
(address token0, address token1) = isToken0 ? (token, config.pairToken) : (config.pairToken, token);
address pool = manager.createAndInitializePoolIfNecessary(
token0, token1, dex.poolFee, PonsTickMath.getSqrtRatioAtTick(poolTick)
);
IERC20(token).forceApprove(dex.positionManager, config.supply);
(uint256 positionId,,,) = manager.mint(
INonfungiblePositionManagerLike.MintParams({
token0: token0,
token1: token1,
fee: dex.poolFee,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: isToken0 ? config.supply : 0,
amount1Desired: isToken0 ? 0 : config.supply,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
})
);
IERC20(token).forceApprove(dex.positionManager, 0);
uint256 initialBuyAmount = msg.value - launchFee;
uint256 restrictionEndBlock = PonsLauncherToken(token).restrictionEndBlock();
_launchedTokens[token] = LaunchedToken({
token: token,
deployer: msg.sender,
pairedToken: config.pairToken,
positionManager: dex.positionManager,
positionId: positionId,
dexId: dexId,
launchConfigId: launchConfigId,
restrictionsEndBlock: restrictionEndBlock,
supply: config.supply,
isToken0: isToken0,
poolFee: dex.poolFee,
exists: true,
initialBuyAmount: initialBuyAmount
});
_graduationThresholds[token] = config.graduationThreshold;
manager.safeTransferFrom(address(this), locker, positionId);
IPonsLaunchLocker(locker).lockPosition(token);
// Wire Advanced creator wallet to fee payouts, not only the developer buy.
if (params.feeWallet != address(0)) {
IPonsLaunchLocker(locker).setFeeRedirect(token, params.feeWallet);
}
emit TokenLaunched(
token,
msg.sender,
dex.factory,
config.pairToken,
pool,
dexId,
launchConfigId,
positionId,
restrictionEndBlock,
initialBuyAmount
);
if (initialBuyAmount != 0) {
if (dex.swapRouter == address(0)) revert RouterNotSet();
PonsLauncherToken(token).setInitialBuyRecipient(initialBuyRecipient);
_executeInitialBuy(dex, config, token, initialBuyRecipient, initialBuyAmount);
PonsLauncherToken(token).setInitialBuyRecipient(address(0));
}
}
/**
* @notice Computes the deterministic token address for launch inputs.
*/
function predictTokenAddress(
TokenParams calldata params,
uint256 launchConfigId,
uint256 dexId,
bytes32 salt,
address tokenDeployer
) external view returns (address) {
if (dexId >= _dexConfigs.length) revert InvalidDexId();
if (launchConfigId >= _launchConfigs.length) {
revert InvalidLaunchConfigId();
}
bytes memory creationCode =
_buildTokenCreationCode(params, _launchConfigs[launchConfigId], _dexConfigs[dexId], tokenDeployer);
return _computeCreate2Address(salt, keccak256(creationCode));
}
function _buildTokenCreationCode(
TokenParams calldata params,
LaunchConfig memory config,
DexConfig memory dex,
address tokenDeployer
) private pure returns (bytes memory) {
PonsLauncherToken.Socials memory tokenSocials = PonsLauncherToken.Socials({
twitter: params.socials.twitter,
telegram: params.socials.telegram,
discord: params.socials.discord,
website: params.socials.website,
farcaster: params.socials.farcaster
});
return abi.encodePacked(
type(PonsLauncherToken).creationCode,
abi.encode(
params.name,
params.symbol,
params.logo,
params.description,
tokenSocials,
tokenDeployer,
dex.factory,
dex.positionManager,
config.pairToken,
dex.poolFee,
config.supply,
config.maxWalletBps,
config.maxTxBps,
config.restrictionBlocks
)
);
}
function _deployToken(bytes32 salt, bytes memory creationCode) private returns (address token) {
assembly ("memory-safe") {
token := create2(0, add(creationCode, 0x20), mload(creationCode), salt)
}
if (token == address(0)) revert TokenDeploymentFailed();
}
function _payLaunchFee() private {
if (launchFee == 0) return;
address recipient = IPonsLaunchLocker(locker).protocolFeeRecipient();
if (recipient == address(0)) revert ZeroAddress();
(bool sent,) = payable(recipient).call{value: launchFee}("");
if (!sent) revert FeeTransferFailed();
}
function _computeCreate2Address(bytes32 salt, bytes32 initCodeHash) private view returns (address) {
return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initCodeHash)))));
}
function _positionRange(bool isToken0, int24 initialTick, int24 tickSpacing)
private
pure
returns (int24 tickLower, int24 tickUpper)
{
// Truncation toward zero is required to derive V3's usable boundary ticks.
// forge-lint: disable-next-line(divide-before-multiply)
int24 minUsableTick = (MIN_TICK / tickSpacing) * tickSpacing;
// forge-lint: disable-next-line(divide-before-multiply)
int24 maxUsableTick = (MAX_TICK / tickSpacing) * tickSpacing;
if (isToken0) {
return (initialTick, maxUsableTick);
}
return (minUsableTick, -initialTick);
}
function _validateDexConfig(DexConfig calldata config) private pure {
if (
bytes(config.name).length == 0 || config.factory == address(0) || config.positionManager == address(0)
|| config.poolFee == 0 || config.tickSpacing <= 0
) {
revert InvalidDexConfig();
}
}
function _validateLaunchConfig(LaunchConfig calldata config) private pure {
if (config.pairToken == address(0)) revert ZeroAddress();
if (config.maxWalletBps > BASIS_POINTS || config.maxTxBps > BASIS_POINTS) {
revert InvalidBasisPoints();
}
uint16 expectedMaxTxBps = _tokenMaxTxBps(config.maxWalletBps);
if (config.maxTxBps != expectedMaxTxBps) {
revert InvalidMaxTxBasisPoints();
}
if (config.supply < 1 ether) revert SupplyTooLow();
if (config.initialTick < MIN_TICK || config.initialTick > MAX_TICK || config.initialTick == 0) {
revert InvalidLaunchConfigId();
}
}
function _tokenMaxTxBps(uint16 maxWalletBps) private pure returns (uint16) {
uint256 derived = (uint256(maxWalletBps) * 110) / 100;
uint256 capped = derived > BASIS_POINTS ? BASIS_POINTS : derived;
// The basis-point cap guarantees that the value fits uint16.
// forge-lint: disable-next-line(unsafe-typecast)
return uint16(capped);
}
function _emitLaunchConfigAdded(uint256 id, LaunchConfig calldata config) private {
emit LaunchConfigAdded(
id,
config.pairToken,
config.graduationThreshold,
config.initialTick,
config.supply,
config.maxWalletBps,
config.maxTxBps,
config.restrictionBlocks,
config.reservedFee,
config.enabled,
config.routerRequiresDeadline
);
}
function _executeInitialBuy(
DexConfig memory dex,
LaunchConfig memory config,
address token,
address recipient,
uint256 amountIn
) private {
if (config.routerRequiresDeadline) {
ISwapRouterV3Like(dex.swapRouter).exactInputSingle{value: amountIn}(
ISwapRouterV3Like.ExactInputSingleParams({
tokenIn: config.pairToken,
tokenOut: token,
fee: dex.poolFee,
recipient: recipient,
deadline: block.timestamp,
amountIn: amountIn,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
})
);
return;
}
ISwapRouter02Like(dex.swapRouter).exactInputSingle{value: amountIn}(
ISwapRouter02Like.ExactInputSingleParams({
tokenIn: config.pairToken,
tokenOut: token,
fee: dex.poolFee,
recipient: recipient,
amountIn: amountIn,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
})
);
}
}
Chain explorer2122msChain node97ms