GenesisTokenV3
0x60e906a5c15f7517845f00fa8f61ce22b40e14ed
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
8,256 bytes
ABI entries
59
21 read · 7 write
License
none
Contract information
- Address
- 0x60e906a5c15f7517845f00fa8f61ce22b40e14ed
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0xf39D42ac7B…25506Ca859
- Creation tx
- 0xb52a100ed0…828e726931
Token
- Name
- Crash Cat
- Symbol
- CRASHCAT
- Decimals
- 18
- Holders
- 52
- Market
- View token page →
Read contract (21)
BURN_ADDRESS() → address
CLOCK_MODE() → string
DOMAIN_SEPARATOR() → bytes32
EXEMPT_INITIAL_HOLDER() → address
MAX_WALLET_BPS() → uint16
allowance(address, address) → uint256
balanceOf(address) → uint256
checkpoints(address, uint32) → tuple
clock() → uint48
decimals() → uint8
delegates(address) → address
eip712Domain() → bytes1, string, string, uint256, address, bytes32, uint256[]
exemptPool() → address
getPastTotalSupply(uint256) → uint256
getPastVotes(address, uint256) → uint256
getVotes(address) → uint256
name() → string
nonces(address) → uint256
numCheckpoints(address) → uint32
symbol() → string
totalSupply() → uint256
Events (5)
ApprovalDelegateChangedDelegateVotesChangedEIP712DomainChangedTransfer
ABI
[
{
"inputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
},
{
"internalType": "string",
"name": "symbol_",
"type": "string"
},
{
"internalType": "uint256",
"name": "totalSupply_",
"type": "uint256"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint16",
"name": "maxWalletBps_",
"type": "uint16"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "CheckpointUnorderedInsertion",
"type": "error"
},
{
"inputs": [],
"name": "ECDSAInvalidSignature",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "length",
"type": "uint256"
}
],
"name": "ECDSAInvalidSignatureLength",
"type": "error"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "ECDSAInvalidSignatureS",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "increasedSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "cap",
"type": "uint256"
}
],
"name": "ERC20ExceededSafeSupply",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "allowance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "ERC20InsufficientAllowance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "ERC20InsufficientBalance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "approver",
"type": "address"
}
],
"name": "ERC20InvalidApprover",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "ERC20InvalidReceiver",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "ERC20InvalidSender",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "ERC20InvalidSpender",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "ERC2612ExpiredSignature",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "ERC2612InvalidSigner",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "timepoint",
"type": "uint256"
},
{
"internalType": "uint48",
"name": "clock",
"type": "uint48"
}
],
"name": "ERC5805FutureLookup",
"type": "error"
},
{
"inputs": [],
"name": "ERC6372InconsistentClock",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "currentNonce",
"type": "uint256"
}
],
"name": "InvalidAccountNonce",
"type": "error"
},
{
"inputs": [],
"name": "InvalidShortString",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "requested",
"type": "uint16"
}
],
"name": "MaxWalletBpsTooHigh",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "cap",
"type": "uint256"
}
],
"name": "MaxWalletExceeded",
"type": "error"
},
{
"inputs": [],
"name": "NotFactory",
"type": "error"
},
{
"inputs": [],
"name": "PoolExemptionAlreadySet",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "bits",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "SafeCastOverflowedUintDowncast",
"type": "error"
},
{
"inputs": [
{
"internalType": "string",
"name": "str",
"type": "string"
}
],
"name": "StringTooLong",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "expiry",
"type": "uint256"
}
],
"name": "VotesExpiredSignature",
"type": "error"
},
{
"inputs": [],
"name": "ZeroPoolAddress",
"type": "error"
},
{
"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": "delegator",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "fromDelegate",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "toDelegate",
"type": "address"
}
],
"name": "DelegateChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "delegate",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "previousVotes",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newVotes",
"type": "uint256"
}
],
"name": "DelegateVotesChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "EIP712DomainChanged",
"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": "BURN_ADDRESS",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "CLOCK_MODE",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DOMAIN_SEPARATOR",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "EXEMPT_INITIAL_HOLDER",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_WALLET_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"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": "value",
"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": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint32",
"name": "pos",
"type": "uint32"
}
],
"name": "checkpoints",
"outputs": [
{
"components": [
{
"internalType": "uint48",
"name": "_key",
"type": "uint48"
},
{
"internalType": "uint208",
"name": "_value",
"type": "uint208"
}
],
"internalType": "struct Checkpoints.Checkpoint208",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "clock",
"outputs": [
{
"internalType": "uint48",
"name": "",
"type": "uint48"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "delegatee",
"type": "address"
}
],
"name": "delegate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "delegatee",
"type": "address"
},
{
"internalType": "uint256",
"name": "nonce",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "expiry",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "delegateBySig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "delegates",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "eip712Domain",
"outputs": [
{
"internalType": "bytes1",
"name": "fields",
"type": "bytes1"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "version",
"type": "string"
},
{
"internalType": "uint256",
"name": "chainId",
"type": "uint256"
},
{
"internalType": "address",
"name": "verifyingContract",
"type": "address"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "uint256[]",
"name": "extensions",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "exemptPool",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "timepoint",
"type": "uint256"
}
],
"name": "getPastTotalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "timepoint",
"type": "uint256"
}
],
"name": "getPastVotes",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "getVotes",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "nonces",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "numCheckpoints",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"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": [
{
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"name": "setPoolExemption",
"outputs": [],
"stateMutability": "nonpayable",
"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": "value",
"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": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";
/// @title GenesisTokenV3
/// @notice The one consistent ERC20 template for every direct-Uniswap-V3 GenesisPad launch.
///
/// Named `GenesisTokenV3` (not `GenesisToken`) to avoid any collision with the legacy
/// bonding-curve token template at contracts/src/GenesisToken.sol, which remains
/// untouched and continues to serve existing live tokens - see docs/MIGRATION_PLAN.md.
/// This is an intentional, documented naming deviation from the spec's literal
/// "GenesisToken" name, per Requirement 3's own allowance: "Equivalent names are
/// acceptable when justified."
///
/// By design this contract has NO privileged role whatsoever after construction:
/// no owner, no Ownable, no AccessControl, no mint function, no pause, no blacklist,
/// no whitelist, no tax, no adjustable fee, no trading switch, no proxy/upgradeability/
/// delegatecall, and no external calls from `_update`. The entire fixed supply is minted
/// exactly once, to `recipient`, inside the constructor.
///
/// The one exception is an OPTIONAL max-wallet cap, added per explicit product request:
/// creators may choose one at deploy time. It is set once and permanently locked - see
/// `setPoolExemption` below for why the pool-exemption address specifically is set via a
/// dedicated function instead of the constructor, and why that doesn't weaken the
/// "no ongoing admin control" guarantee.
///
/// Deliberately excludes any on-chain metadata URI/social-link storage - per
/// Requirement 3's "Token metadata" guidance, that information belongs in
/// GenesisPad backend metadata (which is attached off-chain per launch),
/// not duplicated into every token's storage.
contract GenesisTokenV3 is ERC20, ERC20Permit, ERC20Votes {
error MaxWalletBpsTooHigh(uint16 requested);
error MaxWalletExceeded(address account, uint256 balance, uint256 cap);
error NotFactory();
error PoolExemptionAlreadySet();
error ZeroPoolAddress();
address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD;
/// @notice Max wallet cap, in basis points of total supply. 0 disables the check entirely.
/// Immutable - set once at deployment, never adjustable by anyone afterward.
uint16 public immutable MAX_WALLET_BPS;
/// @notice The address the full supply was minted to (GenesisLaunchFactory in the standard
/// flow), exempt from the cap - it transiently holds the whole supply before supplying it
/// to the V3 position. Immutable, set in the constructor.
address public immutable EXEMPT_INITIAL_HOLDER;
/// @notice The Uniswap V3 pool address, exempt from the cap once set (a V3 pool contract
/// holds its own reserves as a real ERC20 balance - for a single-sided launch that's
/// effectively the entire supply, which must never be blocked by a wallet cap meant for
/// individual holders).
///
/// This is NOT a constructor parameter, deliberately: the pool's address depends on
/// comparing this token's own address against the paired asset's address (Uniswap V3 sorts
/// pool assets by address) - but this token's address would itself change if the pool
/// address were baked into its constructor's init code, since CREATE2 addresses depend on
/// the full constructor argument encoding. Including the pool address as a constructor arg
/// would make the token's own address depend on the pool address, which depends on the
/// token's address - an unresolvable circular dependency. Instead, `setPoolExemption` is
/// called by the launch factory exactly once, atomically within the same launch
/// transaction, immediately after the factory creates the pool (which only needs to know
/// this token's already-fixed address, not the other way around) and before minting any
/// liquidity into it. Restricted to `EXEMPT_INITIAL_HOLDER` (the factory) and settable only
/// once - functionally equivalent to a constructor-time immutable, without the circularity.
address public exemptPool;
/// @param name_ Token name.
/// @param symbol_ Token symbol.
/// @param totalSupply_ Fixed total supply, minted entirely to `recipient` in this constructor.
/// @param recipient Address receiving the entire minted supply. In the standard GenesisPad
/// launch flow this is GenesisLaunchFactory, which immediately supplies it to the Uniswap V3
/// position in the same transaction (see GenesisLaunchFactory.sol) - the factory is never
/// meant to retain a meaningful balance afterward.
/// @param maxWalletBps_ Optional max-wallet cap in basis points of total supply (0-10000).
/// 0 disables the check entirely. Immutable once set - see contract-level documentation.
constructor(
string memory name_,
string memory symbol_,
uint256 totalSupply_,
address recipient,
uint16 maxWalletBps_
) ERC20(name_, symbol_) ERC20Permit(name_) {
if (maxWalletBps_ > 10_000) revert MaxWalletBpsTooHigh(maxWalletBps_);
MAX_WALLET_BPS = maxWalletBps_;
EXEMPT_INITIAL_HOLDER = recipient;
_mint(recipient, totalSupply_);
}
/// @notice Sets the pool-exemption address exactly once. Callable only by
/// `EXEMPT_INITIAL_HOLDER` (the launch factory), which calls this atomically within the
/// same launch transaction that deploys this token - there is no window in which any other
/// party could call it first, and it can never be called again afterward by anyone,
/// including the factory itself.
function setPoolExemption(address pool) external {
if (msg.sender != EXEMPT_INITIAL_HOLDER) revert NotFactory();
if (exemptPool != address(0)) revert PoolExemptionAlreadySet();
if (pool == address(0)) revert ZeroPoolAddress();
exemptPool = pool;
}
function _isExemptFromMaxWallet(address account) internal view returns (bool) {
return account == exemptPool || account == EXEMPT_INITIAL_HOLDER || account == BURN_ADDRESS;
}
// --- Required overrides: ERC20Votes and ERC20Permit both touch _update/nonces in OZ v5 ---
function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Votes) {
super._update(from, to, value);
if (MAX_WALLET_BPS > 0 && to != address(0) && !_isExemptFromMaxWallet(to)) {
uint256 cap = (totalSupply() * MAX_WALLET_BPS) / 10_000;
uint256 balance = balanceOf(to);
if (balance > cap) revert MaxWalletExceeded(to, balance, cap);
}
}
function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
}
Chain explorer4859msChain node69ms