LemonCurveFactory
0xbd65151cd894245ef980e136380efcf34ea2fec0
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
21,646 bytes
ABI entries
19
11 read · 4 write
License
none
Contract information
- Address
- 0xbd65151cd894245ef980e136380efcf34ea2fec0
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x5988E14444…a8F6665e35
- Creation tx
- 0xc3fd402367…776777e135
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (11)
ANTI_SNIPE_MAX_WALLET() → uint256
ANTI_SNIPE_WINDOW_SECS() → uint256
POOL_FEE() → uint24
TOKEN_SUPPLY() → uint256
admin() → address
curveDeployer() → address
getTokenInfo(address) → tuple
keeper() → address
launches(address) → address, address, address, uint256, uint16, uint8, address, address, uint16
platformFeeReceiver() → address
weth() → address
Events (3)
CurveLaunchedKeeperSetTokenizedLaunched
ABI
[
{
"inputs": [
{
"internalType": "contract IWETH",
"name": "_weth",
"type": "address"
},
{
"internalType": "address",
"name": "_platformFeeReceiver",
"type": "address"
},
{
"internalType": "contract CurveDeployer",
"name": "_curveDeployer",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "curve",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "dexFactory",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "positionManager",
"type": "address"
},
{
"indexed": false,
"internalType": "uint8",
"name": "dexKind",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint256",
"name": "graduationThreshold",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint16",
"name": "creatorFeeBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "tokenURI",
"type": "string"
},
{
"indexed": false,
"internalType": "uint256",
"name": "timestamp",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "virtualTokenReserve",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "virtualEthReserve",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalSupply",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "initialTokenReserve",
"type": "uint256"
}
],
"name": "CurveLaunched",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "keeper",
"type": "address"
}
],
"name": "KeeperSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "curve",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "vault",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "stockToken",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "stockSymbol",
"type": "string"
},
{
"indexed": false,
"internalType": "uint16",
"name": "vaultBps",
"type": "uint16"
}
],
"name": "TokenizedLaunched",
"type": "event"
},
{
"inputs": [],
"name": "ANTI_SNIPE_MAX_WALLET",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ANTI_SNIPE_WINDOW_SECS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "POOL_FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TOKEN_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "admin",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "curveDeployer",
"outputs": [
{
"internalType": "contract CurveDeployer",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "getTokenInfo",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "curve",
"type": "address"
},
{
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"internalType": "uint256",
"name": "graduationThreshold",
"type": "uint256"
},
{
"internalType": "uint16",
"name": "creatorFeeBps",
"type": "uint16"
},
{
"internalType": "uint8",
"name": "dexKind",
"type": "uint8"
},
{
"internalType": "bool",
"name": "graduated",
"type": "bool"
},
{
"internalType": "address",
"name": "poolAfterGraduation",
"type": "address"
},
{
"internalType": "address",
"name": "lpLocker",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenReserve",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "ethReserve",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualTokenReserve",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualEthReserve",
"type": "uint256"
},
{
"internalType": "address",
"name": "stocksVault",
"type": "address"
},
{
"internalType": "address",
"name": "stockToken",
"type": "address"
},
{
"internalType": "uint16",
"name": "vaultBps",
"type": "uint16"
}
],
"internalType": "struct LemonCurveFactory.TokenInfo",
"name": "info",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "keeper",
"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": "tokenURI",
"type": "string"
},
{
"internalType": "address",
"name": "dexFactory",
"type": "address"
},
{
"internalType": "address",
"name": "positionManager",
"type": "address"
},
{
"internalType": "uint8",
"name": "dexKind",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "graduationThreshold",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualTokenReserve",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualEthReserve",
"type": "uint256"
},
{
"internalType": "uint16",
"name": "creatorFeeBps",
"type": "uint16"
},
{
"internalType": "address",
"name": "creatorFeeReceiver",
"type": "address"
},
{
"internalType": "string",
"name": "logoURI",
"type": "string"
},
{
"internalType": "string",
"name": "description",
"type": "string"
},
{
"internalType": "string",
"name": "twitter",
"type": "string"
},
{
"internalType": "string",
"name": "telegram",
"type": "string"
},
{
"internalType": "string",
"name": "website",
"type": "string"
}
],
"internalType": "struct LemonCurveFactory.LaunchParams",
"name": "p",
"type": "tuple"
}
],
"name": "launch",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "curve",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "tokenURI",
"type": "string"
},
{
"internalType": "address",
"name": "dexFactory",
"type": "address"
},
{
"internalType": "address",
"name": "positionManager",
"type": "address"
},
{
"internalType": "uint8",
"name": "dexKind",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "graduationThreshold",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualTokenReserve",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "virtualEthReserve",
"type": "uint256"
},
{
"internalType": "uint16",
"name": "creatorFeeBps",
"type": "uint16"
},
{
"internalType": "address",
"name": "creatorFeeReceiver",
"type": "address"
},
{
"internalType": "string",
"name": "logoURI",
"type": "string"
},
{
"internalType": "string",
"name": "description",
"type": "string"
},
{
"internalType": "string",
"name": "twitter",
"type": "string"
},
{
"internalType": "string",
"name": "telegram",
"type": "string"
},
{
"internalType": "string",
"name": "website",
"type": "string"
}
],
"internalType": "struct LemonCurveFactory.LaunchParams",
"name": "p",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "stockToken",
"type": "address"
},
{
"internalType": "string",
"name": "stockSymbol",
"type": "string"
},
{
"internalType": "uint16",
"name": "vaultBps",
"type": "uint16"
}
],
"internalType": "struct LemonCurveFactory.TokenizedParams",
"name": "t",
"type": "tuple"
}
],
"name": "launchTokenized",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "curve",
"type": "address"
},
{
"internalType": "address",
"name": "vault",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "launches",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "curve",
"type": "address"
},
{
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"internalType": "uint256",
"name": "graduationThreshold",
"type": "uint256"
},
{
"internalType": "uint16",
"name": "creatorFeeBps",
"type": "uint16"
},
{
"internalType": "uint8",
"name": "dexKind",
"type": "uint8"
},
{
"internalType": "address",
"name": "stocksVault",
"type": "address"
},
{
"internalType": "address",
"name": "stockToken",
"type": "address"
},
{
"internalType": "uint16",
"name": "vaultBps",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "platformFeeReceiver",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_admin",
"type": "address"
}
],
"name": "setAdmin",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_keeper",
"type": "address"
}
],
"name": "setKeeper",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "contract IWETH",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {LemonToken} from "./LemonToken.sol";
import {BondingCurve} from "./BondingCurve.sol";
import {StocksVault} from "./StocksVault.sol";
import {IUniswapV3Factory, IUniswapV3Pool, INonfungiblePositionManager, IWETH} from "./interfaces/IUniswap.sol";
import {CurveDeployer} from "./deployers/CurveDeployer.sol";
/// @notice Factory that deploys LemonToken + BondingCurve pairs, with optional
/// StocksVault for tokenized launches that pay dividends in Robinhood Stock Tokens.
///
/// LemonToken is deployed directly by this factory so explorers/scanners see
/// the same one-hop internal contract-creation trace as the reference launchpad.
/// The large BondingCurve bytecode remains behind CurveDeployer to keep the
/// factory under EIP-170 without hiding the token deployment from indexers.
contract LemonCurveFactory {
uint256 public constant TOKEN_SUPPLY = 1_000_000_000e18;
uint24 public constant POOL_FEE = 10_000;
uint256 public constant ANTI_SNIPE_MAX_WALLET = 10_000_000e18; // 1% of supply, scanner parity
uint256 public constant ANTI_SNIPE_WINDOW_SECS = 900;
IWETH public immutable weth;
address public immutable platformFeeReceiver;
CurveDeployer public immutable curveDeployer;
address public keeper; // one keeper wallet powers every vault
address public admin; // can rotate keeper
struct Launched {
address token;
address curve;
address deployer;
uint256 graduationThreshold;
uint16 creatorFeeBps;
uint8 dexKind; // 0 = Uniswap, 1 = Sushi
address stocksVault; // 0 for regular launches
address stockToken; // 0 for regular launches
uint16 vaultBps; // 0 for regular launches
}
mapping(address token => Launched) public launches;
event CurveLaunched(
address indexed token,
address indexed curve,
address indexed deployer,
address dexFactory,
address positionManager,
uint8 dexKind,
uint256 graduationThreshold,
uint16 creatorFeeBps,
string name,
string symbol,
string tokenURI,
uint256 timestamp,
uint256 virtualTokenReserve,
uint256 virtualEthReserve,
uint256 totalSupply,
uint256 initialTokenReserve
);
/// @notice Fired only for tokenized launches so indexers can render the
/// "Pays $AAPL dividends" badge without decoding constructor args.
event TokenizedLaunched(
address indexed token,
address indexed curve,
address indexed vault,
address stockToken,
string stockSymbol,
uint16 vaultBps
);
event KeeperSet(address indexed keeper);
constructor(
IWETH _weth,
address _platformFeeReceiver,
CurveDeployer _curveDeployer
) {
weth = _weth;
platformFeeReceiver = _platformFeeReceiver;
curveDeployer = _curveDeployer;
admin = msg.sender;
}
function setKeeper(address _keeper) external {
require(msg.sender == admin, "admin");
require(_keeper != address(0), "zero");
keeper = _keeper;
emit KeeperSet(_keeper);
}
function setAdmin(address _admin) external {
require(msg.sender == admin, "admin");
require(_admin != address(0), "zero");
admin = _admin;
}
struct LaunchParams {
string name;
string symbol;
string tokenURI;
address dexFactory;
address positionManager;
uint8 dexKind;
uint256 graduationThreshold;
uint256 virtualTokenReserve;
uint256 virtualEthReserve;
uint16 creatorFeeBps;
address creatorFeeReceiver;
// On-chain metadata
string logoURI;
string description;
string twitter;
string telegram;
string website;
}
struct TokenizedParams {
address stockToken;
string stockSymbol;
uint16 vaultBps;
}
function launch(LaunchParams calldata p) external returns (address token, address curve) {
(token, curve, ) = _launchInternal(p, TokenizedParams({stockToken: address(0), stockSymbol: "", vaultBps: 0}), false);
}
function launchTokenized(LaunchParams calldata p, TokenizedParams calldata t)
external
returns (address token, address curve, address vault)
{
require(t.stockToken != address(0), "stock=0");
require(t.vaultBps > 0 && t.vaultBps <= 500, "vaultBps");
require(bytes(t.stockSymbol).length > 0 && bytes(t.stockSymbol).length <= 12, "sym");
(token, curve, vault) = _launchInternal(p, t, true);
emit TokenizedLaunched(token, curve, vault, t.stockToken, t.stockSymbol, t.vaultBps);
}
function _launchInternal(LaunchParams calldata p, TokenizedParams memory t, bool isTokenized)
internal
returns (address token, address curve, address vault)
{
require(bytes(p.name).length > 0 && bytes(p.symbol).length > 0, "meta");
require(p.dexFactory != address(0) && p.positionManager != address(0), "dex");
require(p.graduationThreshold > 0, "threshold=0");
require(p.virtualEthReserve > 0 && p.virtualTokenReserve > 0, "vreserves");
require(p.creatorFeeBps <= 1000, "creatorFee>10%");
require(p.dexKind <= 1, "dexKind");
token = address(new LemonToken(
p.name,
p.symbol,
TOKEN_SUPPLY,
address(this),
p.logoURI,
p.description,
p.twitter,
p.telegram,
p.website
));
if (isTokenized) {
StocksVault v = new StocksVault(token, t.stockToken, admin);
vault = address(v);
if (keeper != address(0)) v.setKeeper(keeper);
}
curve = curveDeployer.deployCurve(CurveDeployer.Args({
token: LemonToken(token),
weth: weth,
dexFactory: p.dexFactory,
positionManager: p.positionManager,
deployer: msg.sender,
creatorFeeReceiver: p.creatorFeeReceiver == address(0) ? msg.sender : p.creatorFeeReceiver,
platformFeeReceiver: platformFeeReceiver,
creatorFeeBps: p.creatorFeeBps,
graduationThreshold: p.graduationThreshold,
virtualTokenReserve: p.virtualTokenReserve,
virtualEthReserve: p.virtualEthReserve,
stocksVault: vault,
vaultBps: isTokenized ? t.vaultBps : uint16(0)
}));
_finalizeLaunch(token, curve);
launches[token] = Launched({
token: token,
curve: curve,
deployer: msg.sender,
graduationThreshold: p.graduationThreshold,
creatorFeeBps: p.creatorFeeBps,
dexKind: p.dexKind,
stocksVault: vault,
stockToken: isTokenized ? t.stockToken : address(0),
vaultBps: isTokenized ? t.vaultBps : uint16(0)
});
_emitCurveLaunched(token, curve, p);
}
/// @dev Seeds the curve with the full supply and locks in the launch
/// protections. Pool wiring happens later, inside BondingCurve._graduate().
function _finalizeLaunch(address token, address curve) internal {
LemonToken tok = LemonToken(token);
require(tok.transfer(curve, tok.totalSupply()), "seed");
tok.configureLaunch(ANTI_SNIPE_MAX_WALLET, 0, ANTI_SNIPE_WINDOW_SECS);
tok.initPoolSetter(curve);
}
function _emitCurveLaunched(address token, address curve, LaunchParams calldata p) internal {
uint256 supply = LemonToken(token).totalSupply();
emit CurveLaunched(
token, curve, msg.sender,
p.dexFactory, p.positionManager, p.dexKind,
p.graduationThreshold, p.creatorFeeBps,
p.name, p.symbol, p.tokenURI,
block.timestamp,
p.virtualTokenReserve, p.virtualEthReserve,
supply, supply
);
}
// --- Views ---
struct TokenInfo {
address token;
address curve;
address deployer;
uint256 graduationThreshold;
uint16 creatorFeeBps;
uint8 dexKind;
bool graduated;
address poolAfterGraduation;
address lpLocker;
uint256 tokenReserve;
uint256 ethReserve;
uint256 virtualTokenReserve;
uint256 virtualEthReserve;
address stocksVault;
address stockToken;
uint16 vaultBps;
}
function getTokenInfo(address token) external view returns (TokenInfo memory info) {
Launched memory L = launches[token];
info.token = L.token;
info.curve = L.curve;
info.deployer = L.deployer;
info.graduationThreshold = L.graduationThreshold;
info.creatorFeeBps = L.creatorFeeBps;
info.dexKind = L.dexKind;
info.stocksVault = L.stocksVault;
info.stockToken = L.stockToken;
info.vaultBps = L.vaultBps;
if (L.curve == address(0)) return info;
BondingCurve c = BondingCurve(payable(L.curve));
info.graduated = c.graduated();
info.poolAfterGraduation = c.poolAfterGraduation();
info.lpLocker = c.lpLockerAddress();
info.tokenReserve = c.tokenReserve();
info.ethReserve = c.ethReserve();
info.virtualTokenReserve = c.virtualTokenReserve();
info.virtualEthReserve = c.virtualEthReserve();
}
function _ensurePoolConfigured(
address token,
address wethAddr,
address factory,
uint256 virtualTokenReserve,
uint256 virtualEthReserve
) internal returns (address pool) {
bool tokenIsToken0 = token < wethAddr;
(address token0, address token1) = tokenIsToken0 ? (token, wethAddr) : (wethAddr, token);
pool = IUniswapV3Factory(factory).getPool(token0, token1, POOL_FEE);
if (pool == address(0)) {
pool = IUniswapV3Factory(factory).createPool(token0, token1, POOL_FEE);
}
uint256 reserve0 = tokenIsToken0 ? TOKEN_SUPPLY + virtualTokenReserve : virtualEthReserve;
uint256 reserve1 = tokenIsToken0 ? virtualEthReserve : TOKEN_SUPPLY + virtualTokenReserve;
uint160 sqrtPriceX96 = _sqrtPriceX96(reserve1, reserve0);
try IUniswapV3Pool(pool).initialize(sqrtPriceX96) {} catch {}
}
function _sqrtPriceX96(uint256 reserve1, uint256 reserve0) internal pure returns (uint160) {
require(reserve0 > 0, "r0=0");
uint256 ratioX96 = (reserve1 << 96) / reserve0;
uint256 sqrtRatioX48 = _sqrt(ratioX96);
uint256 result = sqrtRatioX48 << 48;
require(result <= type(uint160).max, "sqrtPrice ovf");
return uint160(result);
}
function _sqrt(uint256 x) internal pure returns (uint256) {
if (x == 0) return 0;
uint256 z = (x + 1) / 2;
uint256 y = x;
while (z < y) { y = z; z = (x / z + z) / 2; }
return y;
}
}
Chain explorer3270msChain node102ms