AaroLauncher
0xdb0fc3298fbf4a831fccd032741b271562b97af9
Verification
Verified
v0.8.23+commit.f704f362
Type
Contract
7,216 bytes
ABI entries
18
10 read · 2 write
License
none
Contract information
- Address
- 0xdb0fc3298fbf4a831fccd032741b271562b97af9
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.23+commit.f704f362
- Optimization
- Enabled
- Creator
- 0x62822Cc05D…da42c6c6f4
- Creation tx
- 0x8540bf4101…b4304a33e4
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (10)
CREATOR_BPS() → uint16
SUPPLY() → uint256
WETH() → address
creatorOf(address) → address
launchCount() → uint256
npm() → address
onERC721Received(address, address, uint256, bytes) → bytes4
poolOf(address) → address
positionOf(address) → uint256
treasury() → address
Events (2)
FeesCollectedTokenLaunched
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_weth",
"type": "address"
},
{
"internalType": "address",
"name": "_npm",
"type": "address"
},
{
"internalType": "address",
"name": "_treasury",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "UnknownToken",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "wethCreator",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "wethTreasury",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenCreator",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenTreasury",
"type": "uint256"
}
],
"name": "FeesCollected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "uri",
"type": "string"
}
],
"name": "TokenLaunched",
"type": "event"
},
{
"inputs": [],
"name": "CREATOR_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "WETH",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "collectFees",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "creatorOf",
"outputs": [
{
"internalType": "address",
"name": "creator",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "uri",
"type": "string"
}
],
"name": "launch",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "launchCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "npm",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "onERC721Received",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "poolOf",
"outputs": [
{
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "positionOf",
"outputs": [
{
"internalType": "uint256",
"name": "positionId",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "treasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {AaroToken} from "./AaroToken.sol";
import {INonfungiblePositionManager} from "../interfaces/INonfungiblePositionManager.sol";
/**
* AARO direct-to-Uniswap launcher.
*
* launch() deploys a fixed 1B-supply ERC20 and seeds the ENTIRE supply as a
* single-sided (all-token / zero-WETH) Uniswap V3 position at ~$2k FDV, so the
* token is tradeable on Uniswap from block one and its price walks up the range
* as buyers arrive. The LP position NFT is held here forever and its liquidity
* is NEVER decreased — there is no withdraw/decrease code path, so it cannot be
* rugged. Only the 1% trading fees are ever collectible, split 80% creator /
* 20% platform via collectFees().
*
* No bonding curve, no graduation: a token is live on Uniswap the moment it is
* launched.
*
* Single-sided seeding works by placing the LP range entirely on the token side
* of the initial price. In V3 a position is 100% token0 when currentTick <=
* tickLower and 100% token1 when currentTick >= tickUpper. We initialize the
* pool exactly at the launch tick and put the range above (token0 = TOKEN) or
* below (token0 = WETH) it, so the seed is all token and zero WETH. As buyers
* swap WETH in, price walks into the range and the memecoin's price rises.
*/
contract AaroLauncher is IERC721Receiver {
using SafeERC20 for IERC20;
// --- token economics ---
uint256 public constant SUPPLY = 1_000_000_000e18; // 1B, 100% into the pool
uint16 public constant CREATOR_BPS = 8000; // 80% of fees to creator, 20% platform
uint16 internal constant BPS = 10_000;
// --- Uniswap V3 pool params (1% fee tier => tickSpacing 200) ---
uint24 internal constant LP_FEE = 10000;
int24 internal constant MAX_TICK = 887200; // full-range max for spacing 200
// Launch price ~= $2,000 FDV. |tick| = 205800 (aligned to spacing 200).
// The token is always currency0 (address < WETH, forced via CREATE2 below),
// so the pool initializes at tick -205800 == tickLower: an INCLUSIVE, ACTIVE
// boundary. That makes the single-sided seed simultaneously 100% token AND
// live liquidity from block one, so Uniswap's router quotes it immediately.
// sqrtPriceX96 computed via TickMath.getSqrtRatioAtTick, validated on-chain.
int24 internal constant LAUNCH_TICK = 205800;
uint160 internal constant SQRT_PRICE_TOKEN0 = 2692853918866877851296459;
uint256 internal constant MAX_SALT = 1_000_000; // salt-search bound (~22 avg)
address public immutable WETH;
address public immutable npm; // Uniswap V3 NonfungiblePositionManager
address public immutable treasury;
mapping(address token => address creator) public creatorOf;
mapping(address token => uint256 positionId) public positionOf;
mapping(address token => address pool) public poolOf;
uint256 public launchCount; // per-launch nonce, keeps CREATE2 addresses unique
error ZeroAddress();
error UnknownToken();
event TokenLaunched(
address indexed token,
address indexed pool,
address indexed creator,
uint256 positionId,
string name,
string symbol,
string uri
);
event FeesCollected(
address indexed token,
address indexed creator,
uint256 wethCreator,
uint256 wethTreasury,
uint256 tokenCreator,
uint256 tokenTreasury
);
constructor(address _weth, address _npm, address _treasury) {
if (_weth == address(0) || _npm == address(0) || _treasury == address(0)) revert ZeroAddress();
WETH = _weth;
npm = _npm;
treasury = _treasury;
}
/**
* Launches a new token: deploys the ERC20, creates + initializes its
* TOKEN/WETH 1% pool at the fixed launch price, and seeds all 1B tokens as a
* single-sided, permanently-locked LP position held by this contract.
* msg.sender is recorded as the creator (earns 80% of the pool's fees).
*/
function launch(string calldata name, string calldata symbol, string calldata uri)
external
returns (address token, address pool)
{
// 1. Deploy the fixed-supply token to an address BELOW WETH (CREATE2 salt
// mining) so it is always Uniswap currency0. That ordering is the only
// one where a single-sided all-token position is BOTH 100% token AND
// active liquidity at launch — so the pool is routable on Uniswap's UI
// immediately, not just after the first trade drags the price in-range.
token = _deployTokenBelowWeth(name, symbol, uri);
// 2. Create + initialize the pool at the launch price. token is currency0.
pool = INonfungiblePositionManager(npm).createAndInitializePoolIfNecessary(token, WETH, LP_FEE, SQRT_PRICE_TOKEN0);
// 3. Seed ALL supply single-sided as a [tickLower=-LAUNCH_TICK, max]
// position. The pool inits exactly at tickLower (inclusive => active),
// so the deposit is 100% token / 0 WETH and liquidity is live at once.
IERC20(token).forceApprove(npm, SUPPLY);
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
token0: token,
token1: WETH,
fee: LP_FEE,
tickLower: -LAUNCH_TICK,
tickUpper: MAX_TICK,
amount0Desired: SUPPLY,
amount1Desired: 0,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
});
(uint256 positionId,,,) = INonfungiblePositionManager(npm).mint(params);
creatorOf[token] = msg.sender;
positionOf[token] = positionId;
poolOf[token] = pool;
emit TokenLaunched(token, pool, msg.sender, positionId, name, symbol, uri);
}
/// Deploys AaroToken via CREATE2, mining the salt so the resulting address
/// sorts below WETH (making the token Uniswap currency0). ~4.6% of salts
/// qualify, so this finds one in ~22 tries on average.
function _deployTokenBelowWeth(string calldata name, string calldata symbol, string calldata uri)
internal
returns (address token)
{
bytes memory initCode =
abi.encodePacked(type(AaroToken).creationCode, abi.encode(name, symbol, uri, SUPPLY, address(this)));
bytes32 initHash = keccak256(initCode);
// Seed the salt search with a per-launch nonce so two launches with the
// SAME name/symbol/uri (identical initHash) still explore disjoint salts
// and never collide on the same CREATE2 address.
uint256 nonce = launchCount++;
bytes32 salt;
bool found;
for (uint256 i = 0; i < MAX_SALT; ++i) {
salt = keccak256(abi.encodePacked(nonce, i));
address predicted =
address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initHash)))));
if (predicted < WETH) {
found = true;
break;
}
}
require(found, "no salt below WETH");
token = address(new AaroToken{salt: salt}(name, symbol, uri, SUPPLY, address(this)));
require(token < WETH, "token not currency0"); // CREATE2 determinism guard
}
/**
* Collects accrued 1% LP trading fees (in WETH + the token) from the locked
* position and splits them 80% creator / 20% platform. Permissionless — the
* split is fixed and funds only ever reach the creator + treasury. Uses
* uint128-max so it collects all owed fees and can NEVER touch the locked
* principal liquidity, preserving the "locked forever" guarantee.
*/
function collectFees(address token) external returns (uint256 amount0, uint256 amount1) {
uint256 positionId = positionOf[token];
if (positionId == 0) revert UnknownToken();
address creator = creatorOf[token];
(amount0, amount1) = INonfungiblePositionManager(npm).collect(
INonfungiblePositionManager.CollectParams({
tokenId: positionId,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
bool tokenIsToken0 = token < WETH;
uint256 wethAmt = tokenIsToken0 ? amount1 : amount0;
uint256 tokenAmt = tokenIsToken0 ? amount0 : amount1;
(uint256 wc, uint256 wt) = _split(WETH, creator, wethAmt);
(uint256 tc, uint256 tt) = _split(token, creator, tokenAmt);
emit FeesCollected(token, creator, wc, wt, tc, tt);
}
/// Sends `amount` of `asset` 80/20 to creator/treasury via SafeERC20.
function _split(address asset, address creator, uint256 amount)
internal
returns (uint256 toCreator, uint256 toTreasury)
{
if (amount == 0) return (0, 0);
toCreator = (amount * CREATOR_BPS) / BPS;
toTreasury = amount - toCreator;
IERC20(asset).safeTransfer(creator, toCreator);
IERC20(asset).safeTransfer(treasury, toTreasury);
}
/// Accept the LP position NFT. Uniswap's V3 NPM uses _mint (not _safeMint),
/// so this is only defensive, but it lets the launcher hold the position.
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
return this.onERC721Received.selector;
}
}
Chain explorer932msChain node97ms