ZxoarToken
0x72a2221ed269f2046b97d5b0fa1e616773b01a9b
Verification
Unverified
v0.8.26+commit.8a97fa7a
Type
Contract
2,074 bytes
ABI entries
14
6 read · 5 write
License
none
Contract information
- Address
- 0x72a2221ed269f2046b97d5b0fa1e616773b01a9b
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x2c42840bd9…3A9f9Cc66F
- Creation tx
- 0xe2a21613a5…43d5684111
Token
- Name
- ZXOAR Genesis
- Symbol
- ZGENESIS
- Decimals
- 18
- Holders
- 4
- Market
- View token page →
Read contract (6)
allowance(address, address) → uint256
balanceOf(address) → uint256
decimals() → uint8
name() → string
symbol() → string
totalSupply() → uint256
Events (2)
ApprovalTransfer
ABI
[
{
"inputs": [
{
"internalType": "string",
"name": "n",
"type": "string"
},
{
"internalType": "string",
"name": "s",
"type": "string"
},
{
"internalType": "uint256",
"name": "supply",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "subtractedValue",
"type": "uint256"
}
],
"name": "decreaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "addedValue",
"type": "uint256"
}
],
"name": "increaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {IWETH} from "./interfaces/IWETH.sol";
import {IUnlockCallback} from "v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {Currency} from "v4-core/src/types/Currency.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "v4-core/src/libraries/FixedPoint96.sol";
import {LPFeeLibrary} from "v4-core/src/libraries/LPFeeLibrary.sol";
import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol";
/// @dev Fixed-supply token; the entire supply is minted to the launcher and
/// immediately deposited into the locked pool position. No owner, no
/// mint, no hooks — nothing a creator can rug with.
contract ZxoarToken is ERC20 {
constructor(string memory n, string memory s, uint256 supply, address to) ERC20(n, s) {
_mint(to, supply);
}
}
/// @title ZxoarLauncher
/// @notice Agent-first fair-launch factory for Uniswap V4.
///
/// launch() in one transaction:
/// 1. deploys a fixed-supply token,
/// 2. initializes a native-ETH/token V4 pool wired to SnipeGuardHook
/// (dynamic fee: 50% -> 1% over 3 minutes),
/// 3. deposits 95% of supply as a single-sided position spanning
/// [TICK_LOWER, TICK_UPPER] — the pool's own math is the bonding
/// curve — and escrows the remaining 5% as the graduation
/// reserve, deployed by permissionless graduate() as locked
/// open-market liquidity the moment the curve fills. There is
/// no migration, ever: 100% of supply is locked pool liquidity.
///
/// The position is owned by this contract and there is deliberately
/// no code path that removes liquidity: it is locked forever. Swap
/// fees accrued to the position are claimable via collectFees(),
/// split 50/50 between the token's creator and the protocol.
contract ZxoarLauncher is IUnlockCallback, ReentrancyGuard {
using SafeERC20 for IERC20;
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
// ---------------------------------------------------------------- config
uint256 public constant TOTAL_SUPPLY = 1_000_000_000e18;
int24 public constant TICK_SPACING = 200;
/// @notice Max bytes for a token's metadataURI. Enforced on-chain so the
/// 32KB cap the off-chain builder advertises is actually guaranteed,
/// and to bound the SSTORE cost / event size of a launch.
uint256 public constant MAX_METADATA_URI_BYTES = 32_768;
/// Price band, expressed in currency1-per-currency0 ticks (token per ETH).
/// TICK_UPPER is the *launch* price (~1.4e-9 ETH per token, ~1.4 Ξ market
/// cap); TICK_LOWER is the top of the ladder (~2.05e-8 ETH per token,
/// ~20.5 Ξ market cap). ETH buys push the tick downward through the band.
int24 public constant TICK_LOWER = 177_000;
int24 public constant TICK_UPPER = 203_800;
/// Graduation: 95% of supply forms the curve; 5% is escrowed here and
/// deployed by permissionless graduate() as locked liquidity spanning
/// [GRAD_TICK_LOWER, TICK_LOWER) — the same band width again (~14.6x more
/// price runway, curve top ~20.6 ETH FDV -> graduated top ~300 ETH FDV).
uint256 public constant CURVE_SUPPLY = 950_000_000e18;
uint256 public constant GRAD_RESERVE = 50_000_000e18;
int24 public constant GRAD_TICK_LOWER = 150_200;
IPoolManager public immutable poolManager;
IHooks public immutable hook;
address public immutable protocolFeeRecipient;
IWETH public immutable weth;
address public immutable router;
// -------------------------------------------------------------- registry
struct Launch {
address token;
address creator;
string metadataURI;
uint64 createdAt;
uint128 liquidity;
}
mapping(address => Launch) public launches; // token => Launch
address[] public allTokens;
mapping(address => bool) public graduated; // token => curve filled & seed deployed
event TokenLaunched(
address indexed token,
address indexed creator,
PoolId indexed poolId,
string name,
string symbol,
string metadataURI,
uint128 liquidity
);
event FeesCollected(
address indexed token,
address indexed creator,
uint256 ethAmount,
uint256 tokenAmount,
bool creatorWethFallback,
bool protocolWethFallback
);
event Graduated(address indexed token, uint128 gradLiquidity);
error UnknownToken();
error NotPoolManager();
error NotRouter();
error UnexpectedEthOwed();
error ZeroAddress();
error MetadataUriTooLong();
error AlreadyGraduated();
error CurveNotFilled();
uint8 private constant ACTION_ADD = 1;
uint8 private constant ACTION_COLLECT = 2;
uint8 private constant ACTION_GRADUATE = 3;
constructor(IPoolManager poolManager_, IHooks hook_, address protocolFeeRecipient_, IWETH weth_, address router_) {
if (
address(poolManager_) == address(0) || address(hook_) == address(0)
|| protocolFeeRecipient_ == address(0) || address(weth_) == address(0) || router_ == address(0)
) revert ZeroAddress();
poolManager = poolManager_;
hook = hook_;
protocolFeeRecipient = protocolFeeRecipient_;
weth = weth_;
router = router_;
}
// -------------------------------------------------------------- launching
/// @notice Launch a new token. Caller becomes the creator.
function launch(string calldata name, string calldata symbol, string calldata metadataURI)
external
returns (address token)
{
return _launch(msg.sender, name, symbol, metadataURI);
}
/// @notice Launch a new token on behalf of a creator. Router-only.
/// @dev Used by launchAndBuy to preserve creator attribution when the router
/// is msg.sender. Without this, all fee revenue would route to the router.
function launchFor(address creator, string calldata name, string calldata symbol, string calldata metadataURI)
external
returns (address token)
{
if (msg.sender != router) revert NotRouter();
return _launch(creator, name, symbol, metadataURI);
}
function _launch(address creator, string calldata name, string calldata symbol, string calldata metadataURI)
internal
returns (address token)
{
if (bytes(metadataURI).length > MAX_METADATA_URI_BYTES) revert MetadataUriTooLong();
token = address(new ZxoarToken(name, symbol, TOTAL_SUPPLY, address(this)));
PoolKey memory key = poolKeyFor(token);
// Initialize exactly at the top tick of the band: the position is then
// 100% token (currency1); the first ETH buy moves price into the band.
uint160 sqrtStart = TickMath.getSqrtPriceAtTick(TICK_UPPER);
poolManager.initialize(key, sqrtStart);
// Liquidity such that the position holds the curve supply (95%); the
// graduation reserve stays escrowed here until graduate().
// amount1 = L * (sqrtU - sqrtL) / Q96 => L = supply * Q96 / (sqrtU - sqrtL)
uint160 sqrtL = TickMath.getSqrtPriceAtTick(TICK_LOWER);
uint160 sqrtU = sqrtStart;
uint128 liquidity =
uint128(FullMath.mulDiv(CURVE_SUPPLY, FixedPoint96.Q96, uint256(sqrtU - sqrtL)));
poolManager.unlock(abi.encode(ACTION_ADD, token, liquidity));
launches[token] = Launch({
token: token,
creator: creator,
metadataURI: metadataURI,
createdAt: uint64(block.timestamp),
liquidity: liquidity
});
allTokens.push(token);
emit TokenLaunched(token, creator, key.toId(), name, symbol, metadataURI, liquidity);
}
// ----------------------------------------------------------- fee stream
/// @notice Claim swap fees accrued to the locked position for `token`.
/// Anyone can poke this; proceeds always split 50/50 between the
/// token's creator and the protocol. Principal cannot be touched.
function collectFees(address token) external nonReentrant returns (uint256 ethAmount, uint256 tokenAmount) {
Launch memory l = launches[token];
if (l.token == address(0)) revert UnknownToken();
bytes memory result = poolManager.unlock(abi.encode(ACTION_COLLECT, token, uint128(0)));
(ethAmount, tokenAmount) = abi.decode(result, (uint256, uint256));
uint256 ethCreator = ethAmount / 2;
uint256 tokCreator = tokenAmount / 2;
bool creatorFallback;
bool protocolFallback;
if (ethCreator > 0) creatorFallback = _sendEth(l.creator, ethCreator);
if (ethAmount - ethCreator > 0) protocolFallback = _sendEth(protocolFeeRecipient, ethAmount - ethCreator);
if (tokCreator > 0) IERC20(token).safeTransfer(l.creator, tokCreator);
if (tokenAmount - tokCreator > 0) {
IERC20(token).safeTransfer(protocolFeeRecipient, tokenAmount - tokCreator);
}
emit FeesCollected(token, l.creator, ethAmount, tokenAmount, creatorFallback, protocolFallback);
}
// ------------------------------------------------------------ graduation
/// @notice Deploy the graduation reserve as locked open-market liquidity in
/// [GRAD_TICK_LOWER, TICK_LOWER). Permissionless — callable by
/// anyone once the curve is bought through its floor. One-way and
/// once per token; the seed is locked exactly like the curve.
function graduate(address token) external nonReentrant returns (uint128 gradLiquidity) {
Launch memory l = launches[token];
if (l.token == address(0)) revert UnknownToken();
if (graduated[token]) revert AlreadyGraduated();
(, int24 tick,,) = poolManager.getSlot0(poolKeyFor(token).toId());
if (tick > TICK_LOWER) revert CurveNotFilled();
graduated[token] = true;
uint160 sqrtGL = TickMath.getSqrtPriceAtTick(GRAD_TICK_LOWER);
uint160 sqrtGU = TickMath.getSqrtPriceAtTick(TICK_LOWER);
gradLiquidity = uint128(FullMath.mulDiv(GRAD_RESERVE, FixedPoint96.Q96, uint256(sqrtGU - sqrtGL)));
poolManager.unlock(abi.encode(ACTION_GRADUATE, token, gradLiquidity));
emit Graduated(token, gradLiquidity);
}
/// @notice Lowest tick routers may let buys reach: the curve floor before
/// graduation, the graduated-band floor after. Single source of
/// truth so no caller can walk price into empty tick space.
function buyTickLimit(address token) public view returns (int24) {
return graduated[token] ? GRAD_TICK_LOWER : TICK_LOWER;
}
// --------------------------------------------------------- v4 callback
function unlockCallback(bytes calldata data) external returns (bytes memory) {
if (msg.sender != address(poolManager)) revert NotPoolManager();
(uint8 action, address token, uint128 liquidity) = abi.decode(data, (uint8, address, uint128));
PoolKey memory key = poolKeyFor(token);
if (action == ACTION_ADD || action == ACTION_GRADUATE) {
bool grad = action == ACTION_GRADUATE;
(BalanceDelta delta,) = poolManager.modifyLiquidity(
key,
IPoolManager.ModifyLiquidityParams({
tickLower: grad ? GRAD_TICK_LOWER : TICK_LOWER,
tickUpper: grad ? TICK_LOWER : TICK_UPPER,
liquidityDelta: int256(uint256(liquidity)),
salt: bytes32(0)
}),
""
);
// Both mints owe only token (currency1): launch initializes at the
// band top, and graduation mints at/above its range top. Any ETH
// owed means price sits mid-band — revert; graduate() is retryable.
if (delta.amount0() != 0) revert UnexpectedEthOwed();
uint256 owed = uint256(uint128(-delta.amount1()));
poolManager.sync(key.currency1);
IERC20(token).safeTransfer(address(poolManager), owed);
poolManager.settle();
return "";
}
// ACTION_COLLECT: modifyLiquidity with zero delta realizes accrued fees.
(BalanceDelta feeDelta,) = poolManager.modifyLiquidity(
key,
IPoolManager.ModifyLiquidityParams({
tickLower: TICK_LOWER,
tickUpper: TICK_UPPER,
liquidityDelta: 0,
salt: bytes32(0)
}),
""
);
uint256 ethAmt = feeDelta.amount0() > 0 ? uint256(uint128(feeDelta.amount0())) : 0;
uint256 tokAmt = feeDelta.amount1() > 0 ? uint256(uint128(feeDelta.amount1())) : 0;
if (graduated[token]) {
(BalanceDelta g,) = poolManager.modifyLiquidity(
key,
IPoolManager.ModifyLiquidityParams({
tickLower: GRAD_TICK_LOWER,
tickUpper: TICK_LOWER,
liquidityDelta: 0,
salt: bytes32(0)
}),
""
);
if (g.amount0() > 0) ethAmt += uint256(uint128(g.amount0()));
if (g.amount1() > 0) tokAmt += uint256(uint128(g.amount1()));
}
if (ethAmt > 0) poolManager.take(key.currency0, address(this), ethAmt);
if (tokAmt > 0) poolManager.take(key.currency1, address(this), tokAmt);
return abi.encode(ethAmt, tokAmt);
}
// ---------------------------------------------------------------- views
function poolKeyFor(address token) public view returns (PoolKey memory) {
return PoolKey({
currency0: Currency.wrap(address(0)), // native ETH
currency1: Currency.wrap(token),
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
tickSpacing: TICK_SPACING,
hooks: hook
});
}
function tokenCount() external view returns (uint256) {
return allTokens.length;
}
/// @dev Send ETH to `to`. If raw send fails (e.g. recipient is a contract
/// without payable receive), wrap to WETH and transfer instead.
/// WETH transfer cannot be recipient-reverted, so this never fails
/// for reasons outside our control.
/// @return usedWethFallback True if raw send failed and WETH was used.
function _sendEth(address to, uint256 amount) internal returns (bool usedWethFallback) {
(bool ok,) = to.call{value: amount}("");
if (!ok) {
// Wrap to WETH and transfer — cannot be reverted by recipient
weth.deposit{value: amount}();
require(weth.transfer(to, amount), "weth transfer failed");
return true;
}
return false;
}
receive() external payable {}
}
Chain explorer5225msChain node76ms