HoodMaps
0x36373fff6312e2fc72aab77667d6db5db62526ab
Verification
Verified
v0.8.33+commit.64118f21
Type
Contract
16,043 bytes
ABI entries
84
30 read · 22 write
License
none
Contract information
- Address
- 0x36373fff6312e2fc72aab77667d6db5db62526ab
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.33+commit.64118f21
- Optimization
- Enabled
- Creator
- 0x31E4713664…CB4460c7C5
- Creation tx
- 0xf4b682b0d2…12e231fead
Token
- Name
- HoodMaps
- Symbol
- HOOD
- Decimals
- —
- Holders
- 1,090
- Market
- View token page →
Read contract (30)
DEFAULT_TRANSFER_VALIDATOR() → address
MAX_SUPPLY() → uint256
ROYALTY_BPS() → uint96
autoApproveTransfersFromValidator() → bool
balanceOf(address) → uint256
dataChunks(uint256) → address
description() → string
genesisSupply() → uint256
getApproved(uint256) → address
getTransferValidationFunction() → bytes4, bool
getTransferValidator() → address
isApprovedForAll(address, address) → bool
isMinted(uint256) → bool
merkleRoot() → bytes32
mintedBy(address) → uint256
name() → string
owner() → address
ownerOf(uint256) → address
paused() → bool
phase() → uint8
plotsStored() → uint256
publicMintsPerWallet() → uint256
publicStart() → uint64
royaltyInfo(uint256, uint256) → address, uint256
supportsInterface(bytes4) → bool
symbol() → string
tokenURI(uint256) → string
totalSupply() → uint256
wlMintsPerWallet() → uint256
wlStart() → uint64
Events (15)
AirdroppedApprovalApprovalForAllAutomaticApprovalOfTransferValidatorSetDataChunkAddedDefaultRoyaltySetMerkleRootSetOwnershipTransferredPausedSetPhasesSetPlotMintedTokenRoyaltySetTransferTransferValidatorUpdatedWithdrawn
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "royaltyReceiver_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "BadChunk",
"type": "error"
},
{
"inputs": [],
"name": "CreatorTokenBase__InvalidTransferValidatorContract",
"type": "error"
},
{
"inputs": [],
"name": "DataMissing",
"type": "error"
},
{
"inputs": [],
"name": "MintPaused",
"type": "error"
},
{
"inputs": [],
"name": "NonexistentToken",
"type": "error"
},
{
"inputs": [],
"name": "NotWhitelisted",
"type": "error"
},
{
"inputs": [],
"name": "NothingToWithdraw",
"type": "error"
},
{
"inputs": [],
"name": "OutOfRange",
"type": "error"
},
{
"inputs": [],
"name": "PublicNotStarted",
"type": "error"
},
{
"inputs": [],
"name": "ShouldNotMintToBurnAddress",
"type": "error"
},
{
"inputs": [],
"name": "SoldOut",
"type": "error"
},
{
"inputs": [],
"name": "WLNotStarted",
"type": "error"
},
{
"inputs": [],
"name": "WalletLimitReached",
"type": "error"
},
{
"inputs": [],
"name": "WithdrawFailed",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "count",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newTotal",
"type": "uint256"
}
],
"name": "Airdropped",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "approved",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "autoApproved",
"type": "bool"
}
],
"name": "AutomaticApprovalOfTransferValidatorSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "chunkIndex",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "plotsInChunk",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "plotsStored",
"type": "uint256"
}
],
"name": "DataChunkAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"indexed": false,
"internalType": "uint96",
"name": "feeNumerator",
"type": "uint96"
}
],
"name": "DefaultRoyaltySet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bytes32",
"name": "root",
"type": "bytes32"
}
],
"name": "MerkleRootSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "paused",
"type": "bool"
}
],
"name": "PausedSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint64",
"name": "wlStart",
"type": "uint64"
},
{
"indexed": false,
"internalType": "uint64",
"name": "publicStart",
"type": "uint64"
}
],
"name": "PhasesSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "PlotMinted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"indexed": false,
"internalType": "uint96",
"name": "feeNumerator",
"type": "uint96"
}
],
"name": "TokenRoyaltySet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldValidator",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newValidator",
"type": "address"
}
],
"name": "TransferValidatorUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Withdrawn",
"type": "event"
},
{
"inputs": [],
"name": "DEFAULT_TRANSFER_VALIDATOR",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ROYALTY_BPS",
"outputs": [
{
"internalType": "uint96",
"name": "",
"type": "uint96"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "packedPlots",
"type": "bytes"
}
],
"name": "addDataChunk",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "recipients",
"type": "address[]"
}
],
"name": "airdrop",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "autoApproveTransfersFromValidator",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "dataChunks",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "description",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "genesisSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getApproved",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getTransferValidationFunction",
"outputs": [
{
"internalType": "bytes4",
"name": "functionSignature",
"type": "bytes4"
},
{
"internalType": "bool",
"name": "isViewFunction",
"type": "bool"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "getTransferValidator",
"outputs": [
{
"internalType": "address",
"name": "validator",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "isApprovedForAll",
"outputs": [
{
"internalType": "bool",
"name": "isApproved",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "isMinted",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "merkleRoot",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "quantity",
"type": "uint256"
}
],
"name": "mint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "mintedBy",
"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": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "phase",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "plotsStored",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "publicMintsPerWallet",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "publicStart",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_tokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_salePrice",
"type": "uint256"
}
],
"name": "royaltyInfo",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "autoApprove",
"type": "bool"
}
],
"name": "setAutomaticApprovalOfTransfersFromValidator",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "d",
"type": "string"
}
],
"name": "setDescription",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "n",
"type": "uint256"
}
],
"name": "setGenesisSupply",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "root",
"type": "bytes32"
}
],
"name": "setMerkleRoot",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "p",
"type": "bool"
}
],
"name": "setPaused",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint64",
"name": "wlStart_",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "publicStart_",
"type": "uint64"
}
],
"name": "setPhases",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "r",
"type": "address"
},
{
"internalType": "uint96",
"name": "fee",
"type": "uint96"
}
],
"name": "setRoyaltyInfo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "transferValidator_",
"type": "address"
}
],
"name": "setTransferValidator",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "wlCap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "publicCap",
"type": "uint256"
}
],
"name": "setWalletCaps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"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": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "quantity",
"type": "uint256"
},
{
"internalType": "bytes32[]",
"name": "proof",
"type": "bytes32[]"
}
],
"name": "whitelistMint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "withdrawTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "wlMintsPerWallet",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "wlStart",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.33;
// ============================================================
// HOODMAPS — ERC-721C + SSTORE2 (WL/Public relaunch)
// 21,000 cap | genesis 3,323 | sequential mint | on-chain pixel SVG
// Phase 1: whitelist (merkle-gated, free, 1hr) -> Phase 2: public (free)
// owner airdrop(address[]) | 5% royalties | data via SSTORE2
// ============================================================
// Compiler: 0.8.33, optimizer ON (runs 50), EVM: paris
// ============================================================
import { OwnableBasic } from "@limitbreak/creator-token-standards/src/access/OwnableBasic.sol";
import { ERC721C } from "@limitbreak/creator-token-standards/src/erc721c/ERC721C.sol";
import { ERC721OpenZeppelin } from "@limitbreak/creator-token-standards/src/token/erc721/ERC721OpenZeppelin.sol";
import { BasicRoyalties, ERC2981 } from "@limitbreak/creator-token-standards/src/programmable-royalties/BasicRoyalties.sol";
import "@openzeppelin/contracts@4.8.3/utils/Strings.sol";
import "@openzeppelin/contracts@4.8.3/utils/Base64.sol";
import "@openzeppelin/contracts@4.8.3/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts@4.8.3/utils/cryptography/MerkleProof.sol";
/// @dev Minimal SSTORE2 (based on the well-audited Solmate/0xSequence pattern).
/// Stores `data` as the runtime code of a tiny contract, prefixed with a STOP (0x00)
/// byte so the stored data can never be executed. Read back via extcodecopy.
library SSTORE2 {
// Creation code that returns (STOP ++ data) as runtime bytecode:
// 0x60_0B PUSH1 11 (offset where runtime begins in this creation code)
// 0x59 MSIZE -> 0
// 0x81 DUP2
// 0x38 CODESIZE
// 0x03 SUB -> runtime length
// 0x80 DUP1
// 0x92 SWAP3
// 0x59 MSIZE -> 0
// 0x39 CODECOPY
// 0x59 MSIZE -> 0
// 0xF3 RETURN
// 0x00 STOP (first runtime byte; guarantees data is non-executable)
function write(bytes memory data) internal returns (address ptr) {
bytes memory code = abi.encodePacked(hex"600B5981380380925939F300", data);
assembly { ptr := create(0, add(code, 0x20), mload(code)) }
require(ptr != address(0), "SSTORE2: write failed");
}
function read(address ptr, uint256 start, uint256 end) internal view returns (bytes memory data) {
uint256 len = end - start;
data = new bytes(len);
assembly { extcodecopy(ptr, add(data, 0x20), add(start, 1), len) } // +1 skips STOP byte
}
function codeLen(address ptr) internal view returns (uint256) {
return ptr.code.length - 1; // minus the STOP byte
}
}
contract HoodMaps is ERC721C, BasicRoyalties, OwnableBasic, ReentrancyGuard {
using Strings for uint256;
// ===================== CONSTANTS =====================
uint256 public constant MAX_SUPPLY = 21000;
uint96 public constant ROYALTY_BPS = 500;
uint256 internal constant PLOT_BYTES = 20; // packed bytes per plot (see _pack)
// ===================== SUPPLY =====================
uint256 public genesisSupply = 3323; // mintable genesis pool (<= MAX_SUPPLY)
uint256 public totalSupply; // number minted so far (== last tokenId)
// SSTORE2 data chunks, appended in order. Each holds N plots' packed bytes.
address[] public dataChunks;
uint256 public plotsStored; // how many plots have data uploaded
// ===================== PHASES / MINT CONTROLS =====================
// Phase gating by timestamp: wlStart <= now < publicStart => WL only
// now >= publicStart => public
uint64 public wlStart; // whitelist phase start (unix)
uint64 public publicStart; // public phase start (unix)
bool public paused = true; // hard kill-switch over everything
bytes32 public merkleRoot; // whitelist allowlist root
uint256 public wlMintsPerWallet = 1; // cap during WL (1 per wallet)
uint256 public publicMintsPerWallet = 2; // cap during public
mapping(address => uint256) public mintedBy; // lifetime minted per wallet
string public description =
"HoodMaps is a fully on-chain collection mapping the genesis activity of Robinhood Chain. Each plot is a real slice of the chain, rendered on-chain from its own transaction data.";
// ===================== EVENTS =====================
event PlotMinted(address indexed to, uint256 indexed tokenId);
event DataChunkAdded(uint256 chunkIndex, uint256 plotsInChunk, uint256 plotsStored);
event PhasesSet(uint64 wlStart, uint64 publicStart);
event MerkleRootSet(bytes32 root);
event PausedSet(bool paused);
event Airdropped(uint256 count, uint256 newTotal);
event Withdrawn(address indexed to, uint256 amount);
// ===================== ERRORS =====================
error MintPaused();
error OutOfRange();
error SoldOut();
error WalletLimitReached();
error NotWhitelisted();
error WLNotStarted();
error PublicNotStarted();
error ZeroAddress();
error NothingToWithdraw();
error WithdrawFailed();
error NonexistentToken();
error DataMissing();
error BadChunk();
constructor(address royaltyReceiver_)
ERC721OpenZeppelin("HoodMaps", "HOOD")
OwnableBasic()
BasicRoyalties(royaltyReceiver_, ROYALTY_BPS)
{}
// ================================================================
// PUBLIC MINT
// ================================================================
/// @dev current phase: 0 = closed, 1 = whitelist, 2 = public
function phase() public view returns (uint8) {
if (paused) return 0;
if (wlStart == 0) return 0; // phases not configured yet
if (publicStart != 0 && block.timestamp >= publicStart) return 2;
if (block.timestamp >= wlStart) return 1;
return 0;
}
/// @notice Whitelist mint (free). Requires a valid merkle proof for msg.sender.
function whitelistMint(uint256 quantity, bytes32[] calldata proof) external nonReentrant {
if (phase() != 1) revert WLNotStarted();
bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
if (!MerkleProof.verifyCalldata(proof, merkleRoot, leaf)) revert NotWhitelisted();
if (mintedBy[msg.sender] + quantity > wlMintsPerWallet) revert WalletLimitReached();
_mintMany(msg.sender, quantity);
}
/// @notice Public mint (free).
function mint(uint256 quantity) external nonReentrant {
if (phase() != 2) revert PublicNotStarted();
if (mintedBy[msg.sender] + quantity > publicMintsPerWallet) revert WalletLimitReached();
_mintMany(msg.sender, quantity);
}
function _mintMany(address to, uint256 quantity) internal {
if (quantity == 0) revert OutOfRange();
if (totalSupply + quantity > genesisSupply) revert SoldOut();
mintedBy[to] += quantity;
for (uint256 i = 0; i < quantity; i++) {
uint256 id = ++totalSupply; // sequential 1,2,3...
_mint(to, id);
emit PlotMinted(to, id);
}
}
// ================================================================
// DATA READ (SSTORE2)
// ================================================================
// Plot i (0-indexed = tokenId-1) is stored at global byte offset i*PLOT_BYTES,
// spread across dataChunks in order. Each chunk holds a whole number of plots.
function _readPlot(uint256 tokenId) internal view returns (bytes memory) {
uint256 gi = tokenId - 1; // global plot index
if (gi >= plotsStored) revert DataMissing();
uint256 seen = 0;
for (uint256 c = 0; c < dataChunks.length; c++) {
uint256 plotsHere = SSTORE2.codeLen(dataChunks[c]) / PLOT_BYTES;
if (gi < seen + plotsHere) {
uint256 local = gi - seen;
return SSTORE2.read(dataChunks[c], local * PLOT_BYTES, local * PLOT_BYTES + PLOT_BYTES);
}
seen += plotsHere;
}
revert DataMissing();
}
// unpack 20-byte record: tx(4) blocks(2) maxTx(2) gasK(4) firstBlock(4) lastBlock(4)
function _fields(uint256 tokenId) internal view returns (
uint256 tx_, uint256 blocks_, uint256 maxTx_, uint256 gasK, uint256 fb, uint256 lb
) {
bytes memory b = _readPlot(tokenId);
tx_ = _u(b, 0, 4);
blocks_ = _u(b, 4, 2);
maxTx_ = _u(b, 6, 2);
gasK = _u(b, 8, 4);
fb = _u(b, 12, 4);
lb = _u(b, 16, 4);
}
function _u(bytes memory b, uint256 off, uint256 n) internal pure returns (uint256 v) {
for (uint256 i = 0; i < n; i++) { v = (v << 8) | uint8(b[off + i]); }
}
// ================================================================
// METADATA
// ================================================================
function tokenURI(uint256 tokenId) public view override returns (string memory) {
if (!_exists(tokenId)) revert NonexistentToken();
(uint256 tx_, uint256 blocks_, uint256 maxTx_, , , ) = _fields(tokenId);
string memory image = string(abi.encodePacked(
"data:image/svg+xml;base64,", Base64.encode(bytes(_renderSVG(tokenId, tx_, blocks_, maxTx_)))
));
string memory json = string(abi.encodePacked(
'{"name":"HoodMap #', tokenId.toString(),
'","description":"', description,
'","attributes":[', _attributes(tokenId),
'],"image":"', image, '"}'
));
return string(abi.encodePacked("data:application/json;base64,", Base64.encode(bytes(json))));
}
function _attributes(uint256 tokenId) internal view returns (string memory) {
(uint256 tx_, uint256 blocks_, uint256 maxTx_, uint256 gasK, uint256 fb, uint256 lb) = _fields(tokenId);
return string(abi.encodePacked(
'{"trait_type":"Transactions","value":', tx_.toString(), '},',
'{"trait_type":"Blocks","value":', blocks_.toString(), '},',
'{"trait_type":"Busiest Block","value":', maxTx_.toString(), '},',
_attributes2(gasK, fb, lb)
));
}
function _attributes2(uint256 gasK, uint256 fb, uint256 lb) internal pure returns (string memory) {
return string(abi.encodePacked(
'{"trait_type":"Gas (k)","value":', gasK.toString(), '},',
'{"trait_type":"First Block","value":', fb.toString(), '},',
'{"trait_type":"Last Block","value":', lb.toString(), '}'
));
}
// ================================================================
// ON-CHAIN PIXEL RENDER
// ================================================================
function _renderSVG(uint256 id, uint256 tx_, uint256 blocks_, uint256 maxTx_)
internal pure returns (string memory)
{
blocks_; maxTx_;
uint256 density = tx_ >= 380 ? 100 : (tx_ * 100) / 380;
uint256 bigChance = (density * 60) / 100;
bool[256] memory covered;
string memory cells = "";
for (uint256 gy = 0; gy < 16; gy++) {
for (uint256 gx = 0; gx < 16; gx++) {
if (covered[gy * 16 + gx]) continue;
uint256 hsh = uint256(keccak256(abi.encodePacked(id, gy * 16 + gx)));
if (hsh % 100 > (density * 85) / 100) continue;
uint256 span = _pickSpan(hsh, gx, gy, bigChance);
if (!_spanFree(covered, gx, gy, span)) span = 1;
_mark(covered, gx, gy, span);
cells = string(abi.encodePacked(cells, _rect(hsh, gx, gy, span)));
}
}
return string(abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 500 500">',
'<rect width="500" height="500" rx="26" fill="#000000"/>', cells, '</svg>'
));
}
function _pickSpan(uint256 hsh, uint256 gx, uint256 gy, uint256 bigChance) internal pure returns (uint256) {
uint256 r3 = (hsh >> 16) % 100;
if (r3 < bigChance && gx < 14 && gy < 14) return 3;
if (r3 < bigChance + 22 && gx < 15 && gy < 15) return 2;
return 1;
}
function _spanFree(bool[256] memory covered, uint256 gx, uint256 gy, uint256 span) internal pure returns (bool) {
for (uint256 dy = 0; dy < span; dy++)
for (uint256 dx = 0; dx < span; dx++)
if (covered[(gy + dy) * 16 + (gx + dx)]) return false;
return true;
}
function _mark(bool[256] memory covered, uint256 gx, uint256 gy, uint256 span) internal pure {
for (uint256 dy = 0; dy < span; dy++)
for (uint256 dx = 0; dx < span; dx++)
covered[(gy + dy) * 16 + (gx + dx)] = true;
}
function _rect(uint256 hsh, uint256 gx, uint256 gy, uint256 span) internal pure returns (string memory) {
uint256 r2 = (hsh >> 8) % 100;
string memory fill; string memory op;
if (r2 > 90 && (hsh % 9 == 0)) { fill = "#faffe8"; op = "0.95"; }
else {
fill = "#ccff00";
if (span >= 3) op = "0.92";
else if (span >= 2) op = (r2 > 50) ? "0.8" : "0.55";
else op = (r2 > 60) ? "0.7" : ((r2 > 30) ? "0.45" : "0.28");
}
return string(abi.encodePacked(
'<rect x="', (10 + gx * 30).toString(), '" y="', (10 + gy * 30).toString(),
'" width="', (span * 30 - 2).toString(), '" height="', (span * 30 - 2).toString(),
'" rx="', (span >= 2 ? "4" : "2"), '" fill="', fill, '" fill-opacity="', op, '"/>'
));
}
// ================================================================
// INTERFACE
// ================================================================
function supportsInterface(bytes4 interfaceId)
public view virtual override(ERC721C, ERC2981) returns (bool)
{
return ERC721C.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId);
}
// ================================================================
// VIEWS
// ================================================================
function isMinted(uint256 tokenId) external view returns (bool) { return _exists(tokenId); }
// ================================================================
// OWNER — DATA
// ================================================================
/// @notice Append one SSTORE2 chunk of packed plot bytes (multiple of PLOT_BYTES).
function addDataChunk(bytes calldata packedPlots) external onlyOwner {
if (packedPlots.length == 0 || packedPlots.length % PLOT_BYTES != 0) revert BadChunk();
address ptr = SSTORE2.write(packedPlots);
dataChunks.push(ptr);
plotsStored += packedPlots.length / PLOT_BYTES;
emit DataChunkAdded(dataChunks.length - 1, packedPlots.length / PLOT_BYTES, plotsStored);
}
// ================================================================
// OWNER — AIRDROP
// ================================================================
/// @notice Airdrop one token each to `recipients`, sequentially. Call in batches
/// (e.g. ~80 per tx) to stay under the block gas limit. Counts toward supply.
function airdrop(address[] calldata recipients) external onlyOwner {
uint256 n = recipients.length;
if (totalSupply + n > genesisSupply) revert SoldOut();
for (uint256 i = 0; i < n; i++) {
address to = recipients[i];
if (to == address(0)) revert ZeroAddress();
uint256 id = ++totalSupply;
_mint(to, id);
emit PlotMinted(to, id);
}
emit Airdropped(n, totalSupply);
}
// ================================================================
// OWNER — PHASES / SETTINGS
// ================================================================
function setPhases(uint64 wlStart_, uint64 publicStart_) external onlyOwner {
wlStart = wlStart_;
publicStart = publicStart_;
emit PhasesSet(wlStart_, publicStart_);
}
function setMerkleRoot(bytes32 root) external onlyOwner {
merkleRoot = root;
emit MerkleRootSet(root);
}
function setWalletCaps(uint256 wlCap, uint256 publicCap) external onlyOwner {
wlMintsPerWallet = wlCap;
publicMintsPerWallet = publicCap;
}
function setGenesisSupply(uint256 n) external onlyOwner {
require(n >= totalSupply && n <= MAX_SUPPLY, "bad supply");
genesisSupply = n;
}
function setPaused(bool p) external onlyOwner { paused = p; emit PausedSet(p); }
function setDescription(string calldata d) external onlyOwner { description = d; }
function setRoyaltyInfo(address r, uint96 fee) external onlyOwner { _setDefaultRoyalty(r, fee); }
function withdraw() external onlyOwner nonReentrant {
uint256 bal = address(this).balance;
if (bal == 0) revert NothingToWithdraw();
(bool ok, ) = payable(owner()).call{ value: bal }("");
if (!ok) revert WithdrawFailed();
emit Withdrawn(owner(), bal);
}
function withdrawTo(address to) external onlyOwner nonReentrant {
if (to == address(0)) revert ZeroAddress();
uint256 bal = address(this).balance;
if (bal == 0) revert NothingToWithdraw();
(bool ok, ) = payable(to).call{ value: bal }("");
if (!ok) revert WithdrawFailed();
emit Withdrawn(to, bal);
}
receive() external payable {}
}Chain explorer1875msChain node106ms