HoodToken
0x920d04541d7029c50e753bfd2f7b1b3f4bd6c0de
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
6,721 bytes
ABI entries
56
24 read · 11 write
License
mit
Contract information
- Address
- 0x920d04541d7029c50e753bfd2f7b1b3f4bd6c0de
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0x3A1be08E3a…8Cc910ab85
- Creation tx
- 0xb37a29dbcd…cf49ac79ec
Token
- Name
- Testoaster
- Symbol
- TOASTER
- Decimals
- 18
- Holders
- 126
- Market
- View token page →
Read contract (24)
allowance(address, address) → uint256
balanceOf(address) → uint256
contractURI() → string
creator() → address
decimals() → uint8
description() → string
feeLocker() → address
image() → string
isExempt(address) → bool
isFeeVenue(address) → bool
launchBlock() → uint256
launcher() → address
logo() → string
maxWalletAmount() → uint256
name() → string
ownerRegistry() → address
pool() → address
positionManager() → address
restrictionEndBlock() → uint256
socials() → string
swapRouter() → address
symbol() → string
tokenOwner() → address
totalSupply() → uint256
Events (8)
ApprovalContractURIUpdatedDescriptionUpdatedFeeVenueAddedImageUpdatedPoolSetSocialsUpdatedTransfer
ABI
[
{
"inputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
},
{
"internalType": "string",
"name": "symbol_",
"type": "string"
},
{
"internalType": "uint256",
"name": "supply_",
"type": "uint256"
},
{
"internalType": "address",
"name": "creator_",
"type": "address"
},
{
"components": [
{
"internalType": "string",
"name": "image",
"type": "string"
},
{
"internalType": "string",
"name": "description",
"type": "string"
},
{
"internalType": "string",
"name": "socials",
"type": "string"
},
{
"internalType": "string",
"name": "contractURI",
"type": "string"
}
],
"internalType": "struct HoodToken.Metadata",
"name": "metadata_",
"type": "tuple"
},
{
"internalType": "uint16",
"name": "maxWalletBps_",
"type": "uint16"
},
{
"internalType": "uint32",
"name": "restrictionBlocks_",
"type": "uint32"
},
{
"internalType": "address",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "address",
"name": "feeLocker_",
"type": "address"
},
{
"internalType": "address",
"name": "swapRouter_",
"type": "address"
},
{
"internalType": "address",
"name": "ownerRegistry_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"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": [],
"name": "LaunchBlockBuy",
"type": "error"
},
{
"inputs": [],
"name": "MaxWalletExceeded",
"type": "error"
},
{
"inputs": [],
"name": "OnlyLauncher",
"type": "error"
},
{
"inputs": [],
"name": "OnlyTokenOwner",
"type": "error"
},
{
"inputs": [],
"name": "PoolAlreadySet",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"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": [],
"name": "ContractURIUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "string",
"name": "description",
"type": "string"
}
],
"name": "DescriptionUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "feeVenue",
"type": "address"
}
],
"name": "FeeVenueAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "string",
"name": "image",
"type": "string"
}
],
"name": "ImageUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"name": "PoolSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "string",
"name": "socials",
"type": "string"
}
],
"name": "SocialsUpdated",
"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": "venue",
"type": "address"
}
],
"name": "addFeeVenue",
"outputs": [],
"stateMutability": "nonpayable",
"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": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "burnFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "contractURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "creator",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"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": "feeLocker",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "image",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "isExempt",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "venue",
"type": "address"
}
],
"name": "isFeeVenue",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launchBlock",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launcher",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "logo",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxWalletAmount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ownerRegistry",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pool",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "positionManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "restrictionEndBlock",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "uri",
"type": "string"
}
],
"name": "setContractURI",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "description_",
"type": "string"
}
],
"name": "setDescription",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "image_",
"type": "string"
}
],
"name": "setImage",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "pool_",
"type": "address"
}
],
"name": "setPool",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "socials_",
"type": "string"
}
],
"name": "setSocials",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "socials",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "swapRouter",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"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.28;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {ITokenOwnerRegistry} from "./interfaces/ITokenOwnerRegistry.sol";
/**
* @title HoodToken
* @notice The ERC-20 deployed for every hood.dev launch. Fixed supply, minted
* in full to the launcher (which immediately LPs it single-sided into
* Uniswap V3 and locks the position in the FeeLocker). No owner, no
* mint, no pause — the only post-launch mutability is creator-editable
* metadata and the launcher's one-time `setPool` inside the launch tx.
*
* Metadata is belt-and-suspenders so every indexer finds something it
* understands: on-chain getters (`image`/`logo`/`description`/
* `socials`) à la NOXA/Clanker, plus ERC-7572 `contractURI()`, plus
* the launcher's rich TokenLaunched event.
*
* Optional launch protection (chosen by the creator at launch,
* constants immutable afterwards, auto-expiring):
* - buys straight from the pool are blocked in the launch block
* (except the creator's own atomic dev buy);
* - wallets are capped at `maxWalletAmount` until
* `restrictionEndBlock`.
*/
contract HoodToken is ERC20, ERC20Burnable {
// ------------------------------------------------------------- immutables
address public immutable launcher; // the factory; deployed this token
/// @notice The ORIGINAL launch creator (historical identity; launch-block
/// dev-buy exemption). Control is NOT keyed on this — see
/// `tokenOwner()` / the TokenOwnerRegistry.
address public immutable creator;
/// @notice Canonical ownership registry; zero => `creator` is the owner
/// forever (standalone deployments only — the launcher always
/// wires a registry).
address public immutable ownerRegistry;
address public immutable positionManager;
address public immutable feeLocker;
address public immutable swapRouter;
uint256 public immutable launchBlock;
/// @notice Last block the transfer restrictions apply in. == launchBlock when disabled.
uint256 public immutable restrictionEndBlock;
/// @notice Max balance per non-exempt wallet during the restriction window. 0 = no cap.
uint256 public immutable maxWalletAmount;
// ---------------------------------------------------------------- storage
/// @notice The token's Uniswap V3 pool. Set once by the launcher inside the
/// launch tx, before any third party can interact — de-facto immutable.
address public pool;
/// @notice Fee-policy venues (staking pool, vesting vault, burn/buyback
/// modules) registered by the launcher inside the launch tx —
/// exempt from the wallet cap so fee routing can never be bricked.
mapping(address venue => bool) public isFeeVenue;
string private _image;
string private _description;
string private _socials; // single JSON string, e.g. {"x":"...","telegram":"...","website":"..."}
string private _contractURI; // ERC-7572: ipfs:// JSON with the full metadata
// ----------------------------------------------------------------- events
/// @dev ERC-7572: carries no args; indexers re-read contractURI().
event ContractURIUpdated();
event ImageUpdated(string image);
event DescriptionUpdated(string description);
event SocialsUpdated(string socials);
event PoolSet(address indexed pool);
event FeeVenueAdded(address indexed feeVenue);
// ----------------------------------------------------------------- errors
error OnlyLauncher();
error OnlyTokenOwner();
error PoolAlreadySet();
error ZeroAddress();
error LaunchBlockBuy();
error MaxWalletExceeded();
// ------------------------------------------------------------ constructor
struct Metadata {
string image;
string description;
string socials;
string contractURI;
}
constructor(
string memory name_,
string memory symbol_,
uint256 supply_,
address creator_,
Metadata memory metadata_,
uint16 maxWalletBps_, // 0 disables the wallet cap
uint32 restrictionBlocks_, // 0 limits restrictions to the launch block itself
address positionManager_,
address feeLocker_,
address swapRouter_,
address ownerRegistry_ // 0 => creator owns forever (standalone use)
) ERC20(name_, symbol_) {
launcher = msg.sender;
creator = creator_;
positionManager = positionManager_;
feeLocker = feeLocker_;
swapRouter = swapRouter_;
ownerRegistry = ownerRegistry_;
launchBlock = block.number;
restrictionEndBlock = block.number + restrictionBlocks_;
maxWalletAmount = maxWalletBps_ == 0 ? 0 : (supply_ * maxWalletBps_) / 10_000;
_image = metadata_.image;
_description = metadata_.description;
_socials = metadata_.socials;
_contractURI = metadata_.contractURI;
_mint(msg.sender, supply_);
}
// --------------------------------------------------------------- metadata
/// @notice ERC-7572 contract-level metadata URI (ipfs:// JSON).
function contractURI() external view returns (string memory) {
return _contractURI;
}
/// @notice Token artwork URI. `logo()` is an alias for NOXA-style readers.
function image() external view returns (string memory) {
return _image;
}
function logo() external view returns (string memory) {
return _image;
}
function description() external view returns (string memory) {
return _description;
}
/// @notice Social links as a single JSON string.
function socials() external view returns (string memory) {
return _socials;
}
function setContractURI(string calldata uri) external onlyTokenOwner {
_contractURI = uri;
emit ContractURIUpdated();
}
function setImage(string calldata image_) external onlyTokenOwner {
_image = image_;
emit ImageUpdated(image_);
}
function setDescription(string calldata description_) external onlyTokenOwner {
_description = description_;
emit DescriptionUpdated(description_);
}
function setSocials(string calldata socials_) external onlyTokenOwner {
_socials = socials_;
emit SocialsUpdated(socials_);
}
/// @notice Canonical owner of this token (metadata + fee-recipient
/// control). Resolves through the TokenOwnerRegistry; zero after
/// a renounce (metadata frozen).
function tokenOwner() public view returns (address) {
return ownerRegistry == address(0) ? creator : ITokenOwnerRegistry(ownerRegistry).ownerOf(address(this));
}
// ----------------------------------------------------------------- launch
/// @notice One-time pool registration by the launcher, called inside launch().
function setPool(address pool_) external {
if (msg.sender != launcher) revert OnlyLauncher();
if (pool != address(0)) revert PoolAlreadySet();
if (pool_ == address(0)) revert ZeroAddress();
pool = pool_;
emit PoolSet(pool_);
}
/// @notice Fee-venue registration by the launcher inside launch(), once
/// per chosen fee policy (token-side and/or creator-side).
/// Idempotent: both policies may share one venue (staking pool).
function addFeeVenue(address venue) external {
if (msg.sender != launcher) revert OnlyLauncher();
if (venue == address(0)) revert ZeroAddress();
if (isFeeVenue[venue]) return;
isFeeVenue[venue] = true;
emit FeeVenueAdded(venue);
}
// ----------------------------------------------------------- restrictions
/// @notice Infrastructure addresses the restrictions never apply to.
/// The pool must be exempt as a recipient or sells would trip the
/// wallet cap; the FeeLocker must be exempt so fee collection can
/// never be bricked by the cap; the fee venue (staking pool /
/// vesting vault) must be exempt so fee routing and staking
/// deposits work during the restriction window.
function isExempt(address account) public view returns (bool) {
return account == launcher || account == pool || account == positionManager || account == feeLocker
|| account == swapRouter || isFeeVenue[account];
}
function _update(address from, address to, uint256 value) internal override {
// Mint/burn always passes; after the restriction window this is two
// immutable reads and nothing else.
if (from != address(0) && to != address(0) && block.number <= restrictionEndBlock && !isExempt(to)) {
// Launch-block buys are blocked for everyone except the creator's
// own atomic dev buy (pool -> creator, still wallet-capped below).
if (block.number == launchBlock && from == pool && to != creator) revert LaunchBlockBuy();
if (maxWalletAmount != 0 && balanceOf(to) + value > maxWalletAmount) revert MaxWalletExceeded();
}
super._update(from, to, value);
}
// ---------------------------------------------------------------- helpers
modifier onlyTokenOwner() {
if (msg.sender != tokenOwner()) revert OnlyTokenOwner();
_;
}
}
Chain explorer4006msChain node73ms