HissTokenV33
0xf7c03755572ea0da005709de5774bd7449358888
Verification
Verified
v0.8.34+commit.80d5c536
Type
Contract
4,390 bytes
ABI entries
28
20 read · 5 write
License
none
Contract information
- Address
- 0xf7c03755572ea0da005709de5774bd7449358888
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.34+commit.80d5c536
- Optimization
- Enabled
- Creator
- 0x217d579B29…e5E073C3b6
- Creation tx
- 0x951685f4f6…49cfb8aab5
Token
- Name
- HoodShield
- Symbol
- HOODS
- Decimals
- 18
- Holders
- 11
- Market
- View token page →
Read contract (20)
DOMAIN_SEPARATOR() → bytes32
PERMIT_TYPEHASH() → bytes32
allowance(address, address) → uint256
balanceOf(address) → uint256
decimals() → uint8
description() → string
launchpad() → address
limitEndBlock() → uint256
limitExempt(address) → bool
logo() → string
maxWallet() → uint256
metadataURI() → string
name() → string
nonces(address) → uint256
socials() → string, string, string
symbol() → string
telegram() → string
totalSupply() → uint256
twitter() → string
website() → string
Events (2)
ApprovalTransfer
ABI
[
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "metadataURI",
"type": "string"
},
{
"internalType": "string",
"name": "logo",
"type": "string"
},
{
"internalType": "string",
"name": "description",
"type": "string"
},
{
"internalType": "string",
"name": "website",
"type": "string"
},
{
"internalType": "string",
"name": "twitter",
"type": "string"
},
{
"internalType": "string",
"name": "telegram",
"type": "string"
}
],
"internalType": "struct TokenMeta",
"name": "meta_",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "supply_",
"type": "uint256"
},
{
"internalType": "address",
"name": "holder_",
"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": [],
"name": "DOMAIN_SEPARATOR",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PERMIT_TYPEHASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"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": "pool_",
"type": "address"
},
{
"internalType": "address",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "address",
"name": "creator_",
"type": "address"
},
{
"internalType": "address",
"name": "burn_",
"type": "address"
},
{
"internalType": "uint256",
"name": "windowBlocks_",
"type": "uint256"
}
],
"name": "armLimits",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"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": [],
"name": "description",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launchpad",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "limitEndBlock",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "limitExempt",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "logo",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxWallet",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "metadataURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "nonces",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "permit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "socials",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
},
{
"internalType": "string",
"name": "",
"type": "string"
},
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "telegram",
"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"
},
{
"inputs": [],
"name": "twitter",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "website",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// ---------------------------------------------------------------------------
/// HissLPLocker — immutable, ownerless liquidity vault for a single Uniswap V3
/// position NFT.
/// ---------------------------------------------------------------------------
///
/// Design goals (GMGN "Burnt/Locked LP" scanner parity):
/// • Bytecode-provably rug-proof — the LP NFT can NEVER leave this contract.
/// • No owner. No admin. No upgradability. No selfdestruct.
/// • No path that calls `approve` / `setApprovalForAll` / `transferFrom` /
/// `safeTransferFrom` / `decreaseLiquidity` / `burn` on the position
/// manager. GMGN's LP-lock heuristic verifies this by reading bytecode.
/// • ONLY function that touches the NFT is `collect()`, which pulls the
/// accrued swap-fee balances (never the underlying liquidity) and forwards
/// them to the launchpad, where the creator/platform fee split happens.
///
/// One locker is deployed per launched token. `positionManager`, `tokenId`
/// and `launchpad` are all `immutable` and baked into the runtime code —
/// nothing can be reconfigured after deployment.
/// ---------------------------------------------------------------------------
interface INonfungiblePositionManagerLite {
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
function collect(CollectParams calldata params)
external
payable
returns (uint256 amount0, uint256 amount1);
}
contract HissLPLocker {
address public immutable positionManager;
address public immutable launchpad;
uint256 public immutable tokenId;
event FeesCollected(uint256 amount0, uint256 amount1);
constructor(address positionManager_, address launchpad_, uint256 tokenId_) {
positionManager = positionManager_;
launchpad = launchpad_;
tokenId = tokenId_;
}
/// ERC721Receiver hook — required so `safeTransferFrom` from the launchpad
/// (which mints the LP position) can hand the NFT to this locker. The
/// hook only accepts one specific tokenId — any accidental transfer of a
/// different position NFT reverts, so this locker can hold ONE and only
/// ONE position for its entire lifetime.
function onERC721Received(
address, /* operator */
address, /* from */
uint256 receivedId,
bytes calldata /* data */
) external view returns (bytes4) {
require(msg.sender == positionManager, "only position manager");
require(receivedId == tokenId, "wrong tokenId");
return this.onERC721Received.selector;
}
/// Collect accrued swap fees from the locked position and send them to
/// the launchpad for the creator/platform fee split. Permissionless —
/// anyone may call. Cannot pull the underlying liquidity.
function collect() external returns (uint256 amount0, uint256 amount1) {
(amount0, amount1) = INonfungiblePositionManagerLite(positionManager).collect(
INonfungiblePositionManagerLite.CollectParams({
tokenId: tokenId,
recipient: launchpad,
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
emit FeesCollected(amount0, amount1);
}
}
/// ---------------------------------------------------------------------------
/// SirHissLaunchpadV33 — pool-native launchpad, trustless edition
/// ---------------------------------------------------------------------------
/// Everything V3.2 does (immutable ERC-20 with on-chain branding, full supply
/// into a single-sided Uniswap V3 position, per-token immutable HissLPLocker,
/// optional atomic dev buy, pull-pattern ETH fee claims) with a 70/30
/// creator/platform fee split, PLUS:
///
/// 1. ANTI-SNIPE — for LIMIT_WINDOW blocks after launch no wallet can hold
/// more than 2% of supply (pool / launchpad / position manager / creator
/// / burn address exempt). Enforced inside the token, bytecode-provable,
/// auto-expires. Snipers can no longer vacuum a fresh launch.
///
/// 2. CREATE2 DETERMINISTIC + VANITY ADDRESSES — tokens deploy via CREATE2
/// so the address is known BEFORE the launch tx lands. `predictToken()`
/// + `tokenInitCodeHash()` let the frontend mine a salt off-chain so
/// every HISS token address ends in the vanity suffix 0x…8888. The salt
/// is namespaced by creator, so nobody can front-run your address.
///
/// 3. ON-CHAIN GRADUATION — permissionless `checkGraduation(token)` latches
/// `graduated[token]` and emits `Graduated` once 800M tokens (80% of
/// supply) have been bought out of the pool — the exact metric the UI
/// progress bar tracks (~6 ETH of depth). Note this is a MILESTONE, not
/// a migration: the token is already in its final Uniswap V3 pool from
/// block one, so there is no liquidity move, no downtime, no wrapper.
/// Third parties (GMGN, DexScreener) can detect the milestone from the
/// chain instead of trusting the app.
///
/// 4. ZERO ADMIN — no owner, no transferOwnership. The only mutable knob is
/// feeRecipient rotating ITSELF (key rotation without centralization).
///
/// 5. launchFee() view (constant 0) — integrators can query it today, so a
/// future paid version keeps the same ABI.
///
/// 6. EIP-2612 permit() on the token — enables true one-transaction sells
/// through HissZap (no separate approval tx, ever).
///
/// The TokenLaunched event is byte-compatible with V3.2 so existing indexers
/// keep working unchanged.
///
/// Deploy (Robinhood Chain mainnet, chain id 4663):
/// feeRecipient_: platform fee wallet (0x0 = deployer)
/// positionManager_: 0x73991a25c818bf1f1128deaab1492d45638de0d3
/// swapRouter_: 0xcaf681a66d020601342297493863e78c959e5cb2
/// Compile with solc 0.8.24+, optimization enabled (200 runs).
/// ---------------------------------------------------------------------------
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 createAndInitializePoolIfNecessary(
address token0,
address token1,
uint24 fee,
uint160 sqrtPriceX96
) external payable returns (address pool);
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 safeTransferFrom(address from, address to, uint256 tokenId) external;
function WETH9() external view returns (address);
}
interface ISwapRouter02 {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata params)
external
payable
returns (uint256 amountOut);
}
interface IWETH9 {
function withdraw(uint256 amount) external;
function balanceOf(address account) external view returns (uint256);
}
struct TokenMeta {
string name;
string symbol;
string metadataURI;
string logo;
string description;
string website;
string twitter;
string telegram;
}
/// Immutable ERC-20 with on-chain branding, EIP-2612 permit and a
/// self-expiring anti-snipe max-wallet cap. No owner, no mint, no tax,
/// no blacklist, no pause.
contract HissTokenV33 {
string public name;
string public symbol;
string public metadataURI;
string public logo;
string public description;
string public website;
string public twitter;
string public telegram;
uint8 public constant decimals = 18;
uint256 public immutable totalSupply;
// ------------------------- anti-snipe state ---------------------------
/// 2% of supply — the most any non-exempt wallet can hold inside the
/// limit window.
uint256 public immutable maxWallet;
/// Last block (inclusive) on which the max-wallet cap applies. Zero until
/// the launchpad arms it in the launch transaction; permanently past
/// after the window, so post-window transfers cost one cold SLOAD + CMP.
uint256 public limitEndBlock;
/// Addresses allowed to exceed the cap during the window: launchpad,
/// pool, position manager, creator, burn address.
mapping(address => bool) public limitExempt;
/// The factory that deployed this token — the only address that may arm
/// the anti-snipe window, exactly once, during the launch transaction.
address public immutable launchpad;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
// --------------------------- EIP-2612 ---------------------------------
mapping(address => uint256) public nonces;
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 private immutable _domainSeparator;
uint256 private immutable _cachedChainId;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(TokenMeta memory meta_, uint256 supply_, address holder_) {
name = meta_.name;
symbol = meta_.symbol;
metadataURI = meta_.metadataURI;
logo = meta_.logo;
description = meta_.description;
website = meta_.website;
twitter = meta_.twitter;
telegram = meta_.telegram;
totalSupply = supply_;
maxWallet = (supply_ * 200) / 10_000; // 2%
launchpad = msg.sender;
limitExempt[msg.sender] = true;
balanceOf[holder_] = supply_;
emit Transfer(address(0), holder_, supply_);
_cachedChainId = block.chainid;
_domainSeparator = _computeDomainSeparator();
}
/// One-time arm of the anti-snipe window, called by the launchpad inside
/// the launch transaction (after the pool exists). Not callable again and
/// not callable by anyone else — bytecode-provably not an admin lever.
function armLimits(
address pool_,
address positionManager_,
address creator_,
address burn_,
uint256 windowBlocks_
) external {
require(msg.sender == launchpad, "only launchpad");
require(limitEndBlock == 0, "armed");
limitExempt[pool_] = true;
limitExempt[positionManager_] = true;
limitExempt[creator_] = true;
limitExempt[burn_] = true;
limitEndBlock = block.number + windowBlocks_;
}
function socials()
external
view
returns (string memory, string memory, string memory)
{
return (website, twitter, telegram);
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) external returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
uint256 allowed = allowance[from][msg.sender];
require(allowed >= amount, "allowance");
if (allowed != type(uint256).max) {
allowance[from][msg.sender] = allowed - amount;
}
_transfer(from, to, amount);
return true;
}
// --------------------------- EIP-2612 ---------------------------------
function DOMAIN_SEPARATOR() public view returns (bytes32) {
// Recompute if the chain forked so signatures stay domain-bound.
return block.chainid == _cachedChainId ? _domainSeparator : _computeDomainSeparator();
}
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(block.timestamp <= deadline, "permit expired");
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)
)
)
);
address recovered = ecrecover(digest, v, r, s);
require(recovered != address(0) && recovered == owner, "invalid signature");
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _computeDomainSeparator() private view returns (bytes32) {
return keccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
),
keccak256(bytes(name)),
keccak256(bytes("1")),
block.chainid,
address(this)
)
);
}
// ------------------------------ internal ------------------------------
function _transfer(address from, address to, uint256 amount) internal {
require(to != address(0), "zero address");
uint256 bal = balanceOf[from];
require(bal >= amount, "balance");
balanceOf[from] = bal - amount;
uint256 newBal = balanceOf[to] + amount;
// Anti-snipe: inside the limit window non-exempt wallets are capped
// at 2% of supply. After limitEndBlock this is a single comparison.
if (block.number <= limitEndBlock && !limitExempt[to]) {
require(newBal <= maxWallet, "max wallet");
}
balanceOf[to] = newBal;
emit Transfer(from, to, amount);
}
}
contract SirHissLaunchpadV33 {
// ----------------------------- constants ------------------------------
uint256 public constant TOTAL_SUPPLY = 1_000_000_000e18;
uint24 public constant POOL_FEE = 10_000; // 1% fee tier (tick spacing 200)
uint256 public constant CREATOR_FEE_SHARE_BPS = 7_000; // 70% creator / 30% platform
uint256 internal constant BPS = 10_000;
/// Launch is free. The view exists so integrators query it today and a
/// future paid deployment keeps an identical ABI.
uint256 public constant launchFee = 0;
/// Anti-snipe window: max-wallet cap applies for this many blocks after
/// launch, then expires automatically.
uint256 public constant LIMIT_WINDOW_BLOCKS = 100;
/// Anti-snipe cap, mirrored from the token for easy off-chain reads.
uint256 public constant MAX_WALLET_BPS = 200; // 2%
/// Graduation milestone: once this many tokens (80% of supply) have been
/// bought out of the locked pool the token is considered graduated
/// (latched on-chain via checkGraduation). Matches the UI progress bar's
/// 800M semantics — NOT a migration; the pool never moves.
uint256 public constant GRADUATION_TOKENS_SOLD = 800_000_000e18;
int24 internal constant BOUNDARY_TICK = 203_200;
int24 internal constant EXTREME_TICK = 887_200;
uint160 internal constant SQRT_PRICE_TOKEN0 = 3051384182559527864182507;
uint160 internal constant SQRT_PRICE_TOKEN1 = 2057132553568326054787615825908002;
address public constant DEAD = 0x000000000000000000000000000000000000dEaD;
// ------------------------------- state --------------------------------
/// Platform fee wallet. NOT an owner: the only thing it can do is rotate
/// itself (key hygiene). There is no other admin surface on this contract.
address public feeRecipient;
INonfungiblePositionManager public immutable positionManager;
ISwapRouter02 public immutable swapRouter;
address public immutable weth;
struct Launch {
address creator;
address pool;
address lpLocker; // per-token immutable locker holding the LP NFT
uint256 lpTokenId;
bool tokenIsToken0;
bool exists;
}
mapping(address => Launch) public launches;
address[] public allTokens;
mapping(address => uint256) public pendingEth;
/// Latched to true by checkGraduation() once GRADUATION_TOKENS_SOLD
/// tokens have been bought out of the pool. Never un-latches.
mapping(address => bool) public graduated;
uint256 private locked = 1;
// ------------------------------- events -------------------------------
/// Byte-compatible with V3.2 — same signature, same topic0 — so existing
/// indexers decode V3.3 launches without changes.
event TokenLaunched(
address indexed token,
address indexed creator,
address pool,
address lpLocker,
string name,
string symbol,
string metadataURI,
uint256 devBuyEth
);
event FeesClaimed(
address indexed token,
uint256 ethToCreator,
uint256 ethToPlatform,
uint256 tokensToCreator,
uint256 tokensToPlatform
);
event Claimed(address indexed account, uint256 amount);
event Graduated(address indexed token, address indexed pool, uint256 tokensSold);
event FeeRecipientRotated(address indexed oldRecipient, address indexed newRecipient);
modifier nonReentrant() {
require(locked == 1, "reentrancy");
locked = 2;
_;
locked = 1;
}
constructor(address feeRecipient_, address positionManager_, address swapRouter_) {
feeRecipient = feeRecipient_ == address(0) ? msg.sender : feeRecipient_;
positionManager = INonfungiblePositionManager(positionManager_);
swapRouter = ISwapRouter02(swapRouter_);
weth = INonfungiblePositionManager(positionManager_).WETH9();
}
// ------------------------------ launch --------------------------------
/// Launch a token straight into a Uniswap V3 pool whose LP NFT is then
/// transferred to a per-token immutable HissLPLocker.
///
/// `salt` — CREATE2 salt, typically mined off-chain (see predictToken)
/// so the token address ends in the HISS vanity suffix. The effective
/// salt is namespaced by msg.sender, so a mined address cannot be
/// front-run by anyone else.
///
/// Any ETH sent executes an atomic dev buy in the same transaction —
/// before any sniper can act. Tokens go to `msg.sender`.
function createToken(TokenMeta calldata meta, bytes32 salt, uint256 minDevBuyTokens)
external
payable
nonReentrant
returns (address token)
{
return _createToken(meta, salt, minDevBuyTokens, msg.sender);
}
/// Same as createToken, but the atomic dev buy lands on `devBuyRecipient`
/// (pass address(0) to keep msg.sender). Gift recipients are still subject
/// to the 2% anti-snipe max-wallet during the limit window.
function createToken(
TokenMeta calldata meta,
bytes32 salt,
uint256 minDevBuyTokens,
address devBuyRecipient
) external payable nonReentrant returns (address token) {
address to = devBuyRecipient == address(0) ? msg.sender : devBuyRecipient;
return _createToken(meta, salt, minDevBuyTokens, to);
}
function _createToken(
TokenMeta calldata meta,
bytes32 salt,
uint256 minDevBuyTokens,
address devBuyRecipient
) internal returns (address token) {
require(bytes(meta.name).length >= 2 && bytes(meta.name).length <= 32, "bad name");
require(bytes(meta.symbol).length >= 2 && bytes(meta.symbol).length <= 8, "bad symbol");
token = address(
new HissTokenV33{salt: _effectiveSalt(msg.sender, salt)}(
meta,
TOTAL_SUPPLY,
address(this)
)
);
(address pool, address locker) = _poolAndLock(token);
_devBuy(token, minDevBuyTokens, devBuyRecipient);
emit TokenLaunched(
token,
msg.sender,
pool,
locker,
meta.name,
meta.symbol,
meta.metadataURI,
msg.value - launchFee
);
}
/// Create + initialize the pool, arm the token's anti-snipe window,
/// deposit the full supply as a single-sided position, and lock the LP
/// NFT in a fresh per-token immutable HissLPLocker.
function _poolAndLock(address token) internal returns (address pool, address locker) {
bool tokenIsToken0 = token < weth;
(address token0, address token1) = tokenIsToken0 ? (token, weth) : (weth, token);
pool = positionManager.createAndInitializePoolIfNecessary(
token0,
token1,
POOL_FEE,
tokenIsToken0 ? SQRT_PRICE_TOKEN0 : SQRT_PRICE_TOKEN1
);
// Arm the anti-snipe window now that the pool address is known.
HissTokenV33(token).armLimits(
pool,
address(positionManager),
msg.sender,
DEAD,
LIMIT_WINDOW_BLOCKS
);
HissTokenV33(token).approve(address(positionManager), TOTAL_SUPPLY);
(uint256 lpTokenId, , uint256 amount0, uint256 amount1) = positionManager.mint(
INonfungiblePositionManager.MintParams({
token0: token0,
token1: token1,
fee: POOL_FEE,
tickLower: tokenIsToken0 ? -BOUNDARY_TICK : -EXTREME_TICK,
tickUpper: tokenIsToken0 ? EXTREME_TICK : BOUNDARY_TICK,
amount0Desired: tokenIsToken0 ? TOTAL_SUPPLY : 0,
amount1Desired: tokenIsToken0 ? 0 : TOTAL_SUPPLY,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
})
);
uint256 pooled = tokenIsToken0 ? amount0 : amount1;
if (TOTAL_SUPPLY > pooled) {
HissTokenV33(token).transfer(DEAD, TOTAL_SUPPLY - pooled);
}
// Deploy per-token locker and hand it the LP NFT. The locker is
// immutable, ownerless and can never transfer the NFT out —
// GMGN's scanner treats this as fully "Locked LP".
locker = address(new HissLPLocker(address(positionManager), address(this), lpTokenId));
positionManager.safeTransferFrom(address(this), locker, lpTokenId);
launches[token] = Launch({
creator: msg.sender,
pool: pool,
lpLocker: locker,
lpTokenId: lpTokenId,
tokenIsToken0: tokenIsToken0,
exists: true
});
allTokens.push(token);
}
/// Atomic dev buy: swap the sent ETH (minus launch fee) for the new token
/// in the launch transaction, before any sniper can act.
function _devBuy(address token, uint256 minDevBuyTokens, address recipient) internal {
uint256 devBuyEth = msg.value - launchFee;
if (devBuyEth == 0) return;
swapRouter.exactInputSingle{value: devBuyEth}(
ISwapRouter02.ExactInputSingleParams({
tokenIn: weth,
tokenOut: token,
fee: POOL_FEE,
recipient: recipient,
amountIn: devBuyEth,
amountOutMinimum: minDevBuyTokens,
sqrtPriceLimitX96: 0
})
);
}
// ------------------------- address prediction -------------------------
/// The CREATE2 init code hash for a token with the given metadata.
/// Frontends hash this once, then mine salts locally (one keccak per
/// candidate) until predictToken ends in the desired vanity suffix.
function tokenInitCodeHash(TokenMeta calldata meta) public view returns (bytes32) {
return keccak256(
abi.encodePacked(
type(HissTokenV33).creationCode,
abi.encode(meta, TOTAL_SUPPLY, address(this))
)
);
}
/// Predict the token address `creator` would get for (salt, meta) —
/// exact match for what createToken deploys.
function predictToken(address creator, bytes32 salt, TokenMeta calldata meta)
external
view
returns (address)
{
return address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
_effectiveSalt(creator, salt),
tokenInitCodeHash(meta)
)
)
)
)
);
}
function _effectiveSalt(address creator, bytes32 salt) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(creator, salt));
}
// ----------------------------- graduation -----------------------------
/// Permissionless: latch the graduation flag and emit the milestone event
/// once 800M tokens have been bought out of the locked pool. Also invoked
/// opportunistically by claimFees so most tokens graduate without anyone
/// calling this. Purely a milestone — the pool and liquidity never move.
function checkGraduation(address token) public {
Launch storage l = launches[token];
require(l.exists, "unknown token");
_maybeGraduate(token, l.pool);
}
function _maybeGraduate(address token, address pool) internal {
if (graduated[token]) return;
// Tokens sold = total supply minus what's still sitting in the pool
// (the same semantics the app's progress bar uses).
uint256 inPool = HissTokenV33(token).balanceOf(pool);
uint256 sold = TOTAL_SUPPLY > inPool ? TOTAL_SUPPLY - inPool : 0;
if (sold >= GRADUATION_TOKENS_SOLD) {
graduated[token] = true;
emit Graduated(token, pool, sold);
}
}
// ---------------------------- fee claiming ----------------------------
/// Route the locked position's accrued 1% swap fees through the locker
/// (which forwards them to this launchpad) and split them 70/30 between
/// the creator and the platform. Anyone can call.
function claimFees(address token) external nonReentrant {
Launch storage l = launches[token];
require(l.exists, "unknown token");
(uint256 amount0, uint256 amount1) = HissLPLocker(l.lpLocker).collect();
(uint256 tokenAmt, uint256 wethAmt) =
l.tokenIsToken0 ? (amount0, amount1) : (amount1, amount0);
uint256 ethCreator;
uint256 ethPlatform;
if (wethAmt > 0) {
IWETH9(weth).withdraw(wethAmt);
ethCreator = (wethAmt * CREATOR_FEE_SHARE_BPS) / BPS;
ethPlatform = wethAmt - ethCreator;
pendingEth[l.creator] += ethCreator;
pendingEth[feeRecipient] += ethPlatform;
}
uint256 tokCreator;
uint256 tokPlatform;
if (tokenAmt > 0) {
tokCreator = (tokenAmt * CREATOR_FEE_SHARE_BPS) / BPS;
tokPlatform = tokenAmt - tokCreator;
require(HissTokenV33(token).transfer(l.creator, tokCreator), "token transfer");
require(HissTokenV33(token).transfer(feeRecipient, tokPlatform), "token transfer");
}
emit FeesClaimed(token, ethCreator, ethPlatform, tokCreator, tokPlatform);
_maybeGraduate(token, l.pool);
}
/// Withdraw accumulated ETH fees (pull pattern).
function claim() external nonReentrant {
uint256 amount = pendingEth[msg.sender];
require(amount > 0, "nothing to claim");
pendingEth[msg.sender] = 0;
_send(msg.sender, amount);
emit Claimed(msg.sender, amount);
}
// ------------------------------- views --------------------------------
function tokenCount() external view returns (uint256) {
return allTokens.length;
}
function poolOf(address token) external view returns (address) {
return launches[token].pool;
}
function lockerOf(address token) external view returns (address) {
return launches[token].lpLocker;
}
// --------------------------- fee recipient -----------------------------
/// Key rotation only — the fee wallet may point future platform fees at a
/// new address. It cannot touch launches, lockers, tokens, creator fees
/// or anything else. There is no owner on this contract.
function rotateFeeRecipient(address newRecipient) external {
require(msg.sender == feeRecipient, "not fee recipient");
require(newRecipient != address(0), "zero address");
emit FeeRecipientRotated(feeRecipient, newRecipient);
feeRecipient = newRecipient;
}
// ------------------------------ internal ------------------------------
function _send(address to, uint256 amount) internal {
if (amount == 0) return;
(bool ok, ) = to.call{value: amount}("");
require(ok, "eth send failed");
}
receive() external payable {}
}Chain explorer3714msChain node72ms