The token contract
HoodToken: fixed supply, no mint, no pause, no owner — plus on-chain metadata and the opt-in sniper guard.
Every hood.dev launch deploys a HoodToken: a fixed-supply ERC-20 with ERC20Burnable, and nothing else. No mint function. No pause. No blacklist. No transfer tax. No proxy and no upgrade path. The entire supply is created in the constructor and handed to the launcher, which puts all of it into the pool in the same transaction.
#What is guaranteed, and by what
| Property | Enforced by |
|---|---|
| Supply can never increase | _mint is called once, in the constructor. The contract exposes no minting function at all. |
| Nobody can freeze or seize your balance | There is no pause, no blacklist, and no privileged transfer path in the contract. |
| Transfers are never taxed | _update only ever checks the launch-window rules; it never diverts value. |
| The code can never change | Not a proxy. The bytecode at the address is the bytecode forever. |
| The pool address is set exactly once | setPool is launcher-only and reverts on the second call — it runs inside launch(), before any third party can touch the token. |
The only mutable state a person can reach after launch is metadata, and only the token’s registered owner can reach it.
#Metadata
Token metadata is written three ways so that every indexer, wallet and explorer finds something it understands: on-chain getters, an ERC-7572 contract URI, and the launch event.
function image() external view returns (string memory);
function logo() external view returns (string memory); // alias for image()
function description() external view returns (string memory);
function socials() external view returns (string memory); // one JSON string
function contractURI() external view returns (string memory); // ERC-7572
// owner-gated (TokenOwnerRegistry.ownerOf(token)); each emits an event
function setImage(string calldata) external;
function setDescription(string calldata) external;
function setSocials(string calldata) external;
function setContractURI(string calldata) external;socials is a single JSON string (for example {"x":"…","telegram":"…","website":"…"}) so new link types never need a contract change. If ownership is renounced, metadata freezes exactly as it stands — permanently.
#Editing it after launch
You do not need to call the setters by hand. My Tokens shows an Edit button on every token whose registered owner is the wallet you are signed in with — artwork, description and the four link fields, saved straight to the contract. Name and ticker are not editable by anyone: they are ERC-20 constructor state.
- New artwork is stored permanently on Arweave before anything is written on chain, and so is a fresh ERC-7572 metadata JSON — re-pinned on every save, because it embeds the image, description and socials together and goes stale the moment any of them moves. Both are paid for by the platform; there is no gateway that can quietly drop them later.
- Only changed fields are written. One transaction per changed setter, plus the
setContractURIrefresh — so an edit that only touches the description costs two transactions, not four. - Each one prompts you. The target is the token contract, a different address per launch, so it can never ride the popup-free delegated key — your own credential signs.
- Every surface catches up on its own. The setters emit events the indexer picks up, so the terminal, the launch board and anything else reading the chain update within about a minute. Nothing is stored in a private database that could disagree with the contract.
#The sniper guard
A per-launch, creator-chosen protection whose constants are immutable from the constructor and which expires on its own. It has two independent parts.
#Launch-block buy block — always on
In the launch block itself, buying straight from the pool reverts with LaunchBlockBuy for everyone except the creator’s own atomic dev buy. This applies whether or not you opt into the sniper guard: with the guard off the restriction window collapses to the launch block, and that check still runs inside it.
block.number on this chain tracks the parent chain, “the launch block” spans roughly 12 seconds of wall clock — about 120 L2 blocks. That is a meaningful window of protection for the creator’s first fill, and it costs nothing.#Wallet cap — opt-in
Set sniperGuard: true and the token additionally caps every non-exempt wallet’s balance for a fixed number of blocks. A transfer that would push a recipient over the cap reverts with MaxWalletExceeded.
maxWalletBps = 200 at launch time, snapshotted)Exempt addresses are exactly the infrastructure that must keep working: the launcher, the pool, the position manager, the FeeLocker, the venue router, and any fee venue registered at launch (a staking pool or vesting vault). Without those exemptions the cap would brick fee collection and staking deposits during the window.
maxWalletAmount() and restrictionEndBlock() on the token itself rather than assuming.#Burning
HoodToken extends ERC20Burnable, so burn(amount) and burnFrom(account, amount) both work and genuinely reduce totalSupply. That is what the Burn tool calls, and what the burn fee policy uses to destroy the token side of LP fees on every collect.
#Immutables you can read
address public immutable launcher; // the factory that deployed this token
address public immutable creator; // original launch creator (historical identity)
address public immutable ownerRegistry; // canonical ownership; 0 => creator owns forever
address public immutable positionManager; // the venue's NFT position manager
address public immutable feeLocker; // where the launch LP lives, permanently
address public immutable swapRouter; // the venue's router
uint256 public immutable launchBlock;
uint256 public immutable restrictionEndBlock; // == launchBlock when the guard is off
uint256 public immutable maxWalletAmount; // 0 when the guard is off
address public pool; // set once, by the launcher, inside launch()
mapping(address => bool) public isFeeVenue; // fee policy venues, exempt from the cap
function tokenOwner() public view returns (address); // resolves through the registrycreatoris historical. Control is not keyed on it — it lives in the TokenOwnerRegistry, so transferring ownership moves every creator power at once.tokenOwner()returns the zero address after a renounce. Every owner-gated function then reverts, forever.
