StonkBrokers

0x539cdd042c2f3d93ebc5be7dfff0c79f3b4fabf0

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
15,176 bytes
ABI entries
104
45 read · 19 write
License
none

Contract information

Address
0x539cdd042c2f3d93ebc5be7dfff0c79f3b4fabf0
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x35c32b43ce…88af90efd4

Token

Name
StonkBrokers
Symbol
STONK
Decimals
Holders
519

Read contract (45)

ACCOUNT_SALT()bytes32
DEFAULT_ROYALTY_BPS()uint96
MAX_PER_TX()uint256
MAX_SUPPLY()uint256
MINT_PRICE()uint256
REVEAL_DELAY()uint256
STAGE_COUNT()uint8
accountImplementation()address
activationManager()address
allocation(address)uint256
balanceOf(address)uint256
fundedToken(uint256)address
fundedTokenMintCount(address)uint256
getApproved(uint256)address
getTransferValidationFunction()bytes4, bool
getTransferValidator()address
initialWalletGrant(uint256)uint256
isApprovedForAll(address, address)bool
minted(address)uint256
name()string
owner()address
ownerOf(uint256)address
placeholderRenderer()address
predictWallet(uint256)address
previewSvg(uint256, uint256)string
registry()address
remainingAllocation(address)uint256
renderer()address
revealCommitBlock()uint256
revealSupply()uint256
revealed()bool
royaltyInfo(uint256, uint256)address, uint256
stageActive(uint8)bool
startingIndex()uint256
stockTokenAt(uint256)address
stockTokenCount()uint256
supportsInterface(bytes4)bool
symbol()string
tokenTraitSeed(uint256)uint256
tokenURI(uint256)string
tokenWallet(uint256)address
totalSupply()uint256
traitSeedOf(uint256)uint256
traitSignatureOf(uint256)bytes32
transfersEnabled()bool

Events (15)

ActivationManagerUpdatedApprovalApprovalForAllBatchMetadataUpdateOwnershipTransferredPlaceholderRendererUpdatedRevealArmedRevealedStageStatusUpdatedTransferTransferValidatorUpdatedTransfersEnabledUpdatedWalletCreatedWalletFundedWhitelistAllocationAdded

ABI

[
  {
    "inputs": [
      {
        "internalType": "address[]",
        "name": "stockTokenAddresses",
        "type": "address[]"
      },
      {
        "internalType": "address",
        "name": "registryAddress",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "accountImplementationAddress",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "rendererAddress",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "initialOwner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "royaltyReceiver",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "numerator",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "denominator",
        "type": "uint256"
      }
    ],
    "name": "ERC2981InvalidDefaultRoyalty",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "ERC2981InvalidDefaultRoyaltyReceiver",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "numerator",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "denominator",
        "type": "uint256"
      }
    ],
    "name": "ERC2981InvalidTokenRoyalty",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "ERC2981InvalidTokenRoyaltyReceiver",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "ERC721IncorrectOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "ERC721InsufficientApproval",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "approver",
        "type": "address"
      }
    ],
    "name": "ERC721InvalidApprover",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      }
    ],
    "name": "ERC721InvalidOperator",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "ERC721InvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "ERC721InvalidReceiver",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "ERC721InvalidSender",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "ERC721NonexistentToken",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StonkBrokers__AlreadyRevealed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StonkBrokers__InvalidStage",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StonkBrokers__InvalidTransferValidatorContract",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StonkBrokers__NothingMinted",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StonkBrokers__RevealCommitExpired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StonkBrokers__RevealNotArmed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StonkBrokers__RevealTooEarly",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StonkBrokers__TransfersDisabled",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "length",
        "type": "uint256"
      }
    ],
    "name": "StringsInsufficientHexLength",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldManager",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newManager",
        "type": "address"
      }
    ],
    "name": "ActivationManagerUpdated",
    "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": "uint256",
        "name": "_fromTokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "_toTokenId",
        "type": "uint256"
      }
    ],
    "name": "BatchMetadataUpdate",
    "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": true,
        "internalType": "address",
        "name": "oldRenderer",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newRenderer",
        "type": "address"
      }
    ],
    "name": "PlaceholderRendererUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "commitBlock",
        "type": "uint256"
      }
    ],
    "name": "RevealArmed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "startingIndex",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "supply",
        "type": "uint256"
      }
    ],
    "name": "Revealed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint8",
        "name": "stage",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "active",
        "type": "bool"
      }
    ],
    "name": "StageStatusUpdated",
    "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": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "TransfersEnabledUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "wallet",
        "type": "address"
      }
    ],
    "name": "WalletCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "stockToken",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "WalletFunded",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint8",
        "name": "stage",
        "type": "uint8"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "wallet",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amountAdded",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newTotalAllocation",
        "type": "uint256"
      }
    ],
    "name": "WhitelistAllocationAdded",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "ACCOUNT_SALT",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DEFAULT_ROYALTY_BPS",
    "outputs": [
      {
        "internalType": "uint96",
        "name": "",
        "type": "uint96"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_PER_TX",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MINT_PRICE",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "REVEAL_DELAY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "STAGE_COUNT",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "accountImplementation",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "activationManager",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "allocation",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "approve",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "commitReveal",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "fundedToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "fundedTokenMintCount",
    "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": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "initialWalletGrant",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      }
    ],
    "name": "isApprovedForAll",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "stage",
        "type": "uint8"
      },
      {
        "internalType": "uint256",
        "name": "quantity",
        "type": "uint256"
      }
    ],
    "name": "mint",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "minted",
    "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": "placeholderRenderer",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "predictWallet",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "traitSeed",
        "type": "uint256"
      }
    ],
    "name": "previewSvg",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "registry",
    "outputs": [
      {
        "internalType": "contract ERC6551Registry",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "wallet",
        "type": "address"
      }
    ],
    "name": "remainingAllocation",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renderer",
    "outputs": [
      {
        "internalType": "contract StonkBrokerSvgRendererV6Proposal",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "reveal",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "revealCommitBlock",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "revealSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "revealed",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "salePrice",
        "type": "uint256"
      }
    ],
    "name": "royaltyInfo",
    "outputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "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": "activationManager_",
        "type": "address"
      }
    ],
    "name": "setActivationManager",
    "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": "address",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "uint96",
        "name": "feeNumerator",
        "type": "uint96"
      }
    ],
    "name": "setDefaultRoyalty",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "placeholderRenderer_",
        "type": "address"
      }
    ],
    "name": "setPlaceholderRenderer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "stage",
        "type": "uint8"
      },
      {
        "internalType": "bool",
        "name": "active",
        "type": "bool"
      }
    ],
    "name": "setStageActive",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "uint96",
        "name": "feeNumerator",
        "type": "uint96"
      }
    ],
    "name": "setTokenRoyalty",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "transferValidator_",
        "type": "address"
      }
    ],
    "name": "setTransferValidator",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setTransfersEnabled",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "stage",
        "type": "uint8"
      },
      {
        "internalType": "address[]",
        "name": "wallets",
        "type": "address[]"
      },
      {
        "internalType": "uint256[]",
        "name": "allocations",
        "type": "uint256[]"
      }
    ],
    "name": "setWhitelist",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "name": "stageActive",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "startingIndex",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "stockTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "stockTokenCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "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": "",
        "type": "uint256"
      }
    ],
    "name": "tokenTraitSeed",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "tokenURI",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "tokenWallet",
    "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": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "traitSeedOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "traitSignatureOf",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "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": [],
    "name": "transfersEnabled",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address payable",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "withdrawMintProceeds",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@limitbreak/creator-token-standards/src/interfaces/ICreatorToken.sol";
import "@limitbreak/creator-token-standards/src/interfaces/ICreatorTokenLegacy.sol" as Legacy;
import "@limitbreak/creator-token-standards/src/interfaces/ITransferValidator.sol";
import "./ERC6551Registry.sol";
import "./StonkBrokerSvgRendererV6Proposal.sol";
import "./libraries/StonkBrokerTraitsV6Proposal.sol";

/// @notice Minimal interface for the separately-deployed pre-reveal placeholder contract (see
/// StonkBrokerPlaceholderV6) — kept as an interface here rather than importing the full contract
/// so StonkBrokers doesn't need to know anything about it beyond this one function.
interface IStonkBrokerPlaceholder {
    function placeholderSvg(uint256 tokenId) external pure returns (string memory);
}

/// @notice Minimal interface for the Anvil-market ActivationManager (see
/// stonkbrokers/anvil-nftfi). Activation must reset on every true ownership transfer — with no
/// custody exceptions — so the transfer hook below notifies the manager on each one.
interface IActivationClearer {
    function clearActivation(uint256 tokenId) external;
}

/// @notice V6 mint contract — V4 art with Gold Tooth (face accessory #7). ERC-721C + EIP-2981.
contract StonkBrokers is ERC721, ERC2981, Ownable, ReentrancyGuard, ICreatorToken {
    using Strings for uint256;

    uint256 public constant MAX_SUPPLY = 4444;
    /// @notice Whitelist mint is free — allocations are earned by burning Pup Cup / Clutch
    /// Puppies NFTs, not purchased. Kept as a named constant (rather than hardcoding 0
    /// throughout) so the value-check logic below reads the same regardless of price.
    uint256 public constant MINT_PRICE = 0;
    uint256 public constant MAX_PER_TX = 10;
    uint96 public constant DEFAULT_ROYALTY_BPS = 666; // 6.66%
    uint8 public constant STAGE_COUNT = 3;

    /// @notice Number of blocks between arming the reveal (`commitReveal`) and the earliest block
    /// whose hash can be consumed by `reveal()`. The committing party cannot know this future
    /// blockhash at commit time, which is what makes the resulting shuffle unmanipulable — see
    /// the reveal section below for the full rationale.
    uint256 public constant REVEAL_DELAY = 5;

    error StonkBrokers__InvalidTransferValidatorContract();
    error StonkBrokers__TransfersDisabled();
    error StonkBrokers__InvalidStage();
    error StonkBrokers__AlreadyRevealed();
    error StonkBrokers__RevealNotArmed();
    error StonkBrokers__RevealTooEarly();
    error StonkBrokers__RevealCommitExpired();
    error StonkBrokers__NothingMinted();

    /// @notice ERC-721C transfer validator. Defaults to `address(0)` (no validator policy —
    /// every operator/marketplace is effectively whitelisted) until the owner explicitly opts
    /// into one via `setTransferValidator`. AUDIT NOTE: earlier drafts of this contract fell
    /// back to Limit Break's real, constant mainnet validator address
    /// (0x721C008f...) whenever no validator had been set. That address has no deployed code
    /// on Robinhood Chain, so any transfer attempted before an explicit `setTransferValidator`
    /// call would revert unexpectedly (a decode failure on the empty returndata) the moment
    /// `transfersEnabled` was flipped on — a self-inflicted denial of service, and the exact
    /// opposite of "transfer validator = 0 so everyone is whitelisted at launch." Defaulting
    /// to `address(0)` here removes that footgun entirely: unset == unrestricted, matching the
    /// global `transfersEnabled` lock as the sole gate until the owner deliberately layers on
    /// stricter ERC-721C policy (e.g. via `StonkBrokerTransferValidator`) later.
    address private transferValidator;

    /// @notice Hard global lock — while false, ERC721 transfers (mint/burn excluded) and
    /// approvals are blocked outright, regardless of the ERC-721C transfer validator policy.
    /// This is a stronger guarantee than the validator's operator whitelist: it also blocks
    /// direct wallet-to-wallet transfers and stops anyone from pre-arming an approval for a
    /// marketplace conduit (e.g. OpenSea Seaport) that would otherwise fire the instant this
    /// flips on. Off-chain listings can still be *created* (marketplaces don't touch our
    /// contract for that), but no listing can settle — the underlying transfer always reverts
    /// while locked, and no approval can exist to allow one to be readied in advance.
    bool public transfersEnabled;

    /// @notice Per-stage manual on/off switch, set by the owner. No timers — stages are
    /// opened and closed by explicit owner action only.
    mapping(uint8 => bool) public stageActive;
    /// @notice wallet => cumulative allocation ever granted, across all stages. Allocation is
    /// a single shared pool rather than per-stage buckets: unused allocation from an earlier
    /// stage automatically carries into later stages (it is never reset or re-granted), and
    /// `setWhitelist` can be called repeatedly over time (e.g. as more wallets earn slots via
    /// burns) without clobbering amounts already earned or already minted.
    mapping(address => uint256) public allocation;
    /// @notice wallet => cumulative amount minted so far, across all stages.
    mapping(address => uint256) public minted;

    ERC6551Registry public immutable registry;
    address public immutable accountImplementation;
    StonkBrokerSvgRendererV6Proposal public immutable renderer;
    bytes32 public constant ACCOUNT_SALT = bytes32(0);

    /// @notice Optional separately-deployed contract supplying the pre-reveal placeholder SVG
    /// (see StonkBrokerPlaceholderV6). Settable rather than constructor-immutable specifically so
    /// it can be deployed and wired up without changing this contract's constructor signature —
    /// the main renderer's bytecode is already close to the EIP-3860 initcode ceiling, so this
    /// placeholder logic intentionally lives in its own small deployment. Falls back to a minimal
    /// inline "?" placeholder if left unset (address(0)), so tokenURI() never breaks either way.
    address public placeholderRenderer;
    event PlaceholderRendererUpdated(address indexed oldRenderer, address indexed newRenderer);

    function setPlaceholderRenderer(address placeholderRenderer_) external onlyOwner {
        emit PlaceholderRendererUpdated(placeholderRenderer, placeholderRenderer_);
        placeholderRenderer = placeholderRenderer_;
    }

    /// @notice ActivationManager of the StonkBrokers Anvil market. When set, every true
    /// ownership transfer clears the token's paid activation tier (stock already in the
    /// token-bound wallet stays with the NFT). address(0) = feature off.
    address public activationManager;
    event ActivationManagerUpdated(address indexed oldManager, address indexed newManager);

    function setActivationManager(address activationManager_) external onlyOwner {
        emit ActivationManagerUpdated(activationManager, activationManager_);
        activationManager = activationManager_;
    }

    uint256 private nextTokenId = 1;
    uint256 private mintedCount;
    address[] private stockTokens;

    mapping(uint256 => address) public tokenWallet;
    mapping(uint256 => address) public fundedToken;
    mapping(uint256 => uint256) public initialWalletGrant;
    mapping(address => uint256) public fundedTokenMintCount;
    mapping(uint256 => uint256) public tokenTraitSeed;
    mapping(bytes32 => bool) private usedTraitSignatures;

    /// @notice DELAYED REVEAL (anti trait-sniping).
    ///
    /// Trait seeds are still assigned per-token at mint time exactly as before — the mint-time
    /// dedup loop is untouched, so uniqueness and the "contract can never block itself" guarantee
    /// both hold. What changes is WHICH stored seed each `tokenId` actually *displays*: until the
    /// collection is revealed, every token shows placeholder metadata/art, and at reveal a single
    /// collection-wide rotation offset (`startingIndex`) permutes the mapping of tokenId -> stored
    /// seed. Because the whole assignment is a rotation of the already-dedup'd seed set, the
    /// revealed collection is still perfectly unique.
    ///
    /// WHY THIS DEFEATS SNIPING: the pre-reveal problem is that `candidateSeed`/`decode` are pure
    /// functions of `tokenId`, so anyone could compute the entire rarity map in advance and mint
    /// into a known-rare `tokenId` (proven exploitable on testnet). After this change, the traits
    /// a `tokenId` ends up with depend on `startingIndex`, which is derived from the hash of a
    /// block that does not exist yet when the reveal is armed. No minter — and no one else — can
    /// know or influence it while minting is open, so there is no `tokenId` to target.
    ///
    /// WHY IT'S UNMANIPULABLE EVEN BY THE OWNER: `reveal()` consumes `blockhash(revealCommitBlock)`
    /// where `revealCommitBlock` was fixed `REVEAL_DELAY` blocks in the future by an earlier
    /// `commitReveal()` call. The owner commits before that block is mined, so they cannot grind
    /// the outcome; and `reveal()` itself is permissionless, so the owner also cannot withhold it.
    bool public revealed;
    /// @notice Block whose hash will seed the shuffle; 0 until `commitReveal` arms it.
    uint256 public revealCommitBlock;
    /// @notice Rotation offset applied to the tokenId -> stored-seed mapping; set at reveal.
    uint256 public startingIndex;
    /// @notice Supply frozen at reveal time; the rotation is taken modulo this value.
    uint256 public revealSupply;

    event WalletCreated(uint256 indexed tokenId, address indexed wallet);
    event WalletFunded(uint256 indexed tokenId, address indexed stockToken, uint256 amount);
    event StageStatusUpdated(uint8 indexed stage, bool active);
    /// @param stage which stage batch this addition was made under (for bookkeeping only —
    /// the granted amount is added to the wallet's single shared allocation pool).
    /// @param amountAdded the incremental allocation granted by this call.
    /// @param newTotalAllocation the wallet's cumulative allocation after this addition.
    event WhitelistAllocationAdded(uint8 indexed stage, address indexed wallet, uint256 amountAdded, uint256 newTotalAllocation);
    event TransfersEnabledUpdated(bool enabled);
    /// @param commitBlock the future block whose hash `reveal()` will consume (>= this + 1).
    event RevealArmed(uint256 commitBlock);
    event Revealed(uint256 startingIndex, uint256 supply);

    /// @notice ERC-4906 metadata-refresh signal. Explorers (Blockscout) and marketplaces
    /// (OpenSea, etc.) cache each token's metadata the first time they index it — which for
    /// this collection is the PRE-reveal placeholder. Without this event, `reveal()` flips
    /// `tokenURI` on-chain but every indexer keeps showing the cached placeholder until
    /// someone manually requests a per-token refresh (exactly the "explorer doesn't show
    /// these NFTs as revealed" failure observed on testnet). Emitting the standard
    /// BatchMetadataUpdate over the full ID range at reveal time tells all of them to
    /// re-fetch automatically.
    event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);

    constructor(
        address[] memory stockTokenAddresses,
        address registryAddress,
        address accountImplementationAddress,
        address rendererAddress,
        address initialOwner,
        address royaltyReceiver
    ) ERC721("StonkBrokers", "STONK") Ownable(initialOwner) {
        require(stockTokenAddresses.length > 0, "no stock tokens");
        require(registryAddress != address(0), "registry=0");
        require(accountImplementationAddress != address(0), "account impl=0");
        require(rendererAddress != address(0), "renderer=0");
        registry = ERC6551Registry(registryAddress);
        accountImplementation = accountImplementationAddress;
        renderer = StonkBrokerSvgRendererV6Proposal(rendererAddress);
        for (uint256 i = 0; i < stockTokenAddresses.length; i++) {
            require(stockTokenAddresses[i] != address(0), "stock token=0");
            stockTokens.push(stockTokenAddresses[i]);
        }

        _setDefaultRoyalty(royaltyReceiver, DEFAULT_ROYALTY_BPS);
        // transferValidator starts at address(0) (Solidity's default) — no event needed since
        // nothing changes from that default; see the field's doc comment above.
    }

    /// @notice Mint using your whitelist allocation while the given stage is open. Every mint
    /// on this collection is gated by an explicit per-wallet allocation set by the owner via
    /// `setWhitelist` — there is no open/public mint path. `stage` only selects which mint
    /// window must currently be active; the allocation itself is a single pool shared across
    /// all stages (unused allocation from an earlier stage remains spendable in a later one).
    function mint(uint8 stage, uint256 quantity) external payable nonReentrant {
        // Once revealed, the tokenId -> traits mapping is public knowledge, so any further mint
        // would be trivially snipeable. Minting must be fully complete before reveal.
        if (revealed) revert StonkBrokers__AlreadyRevealed();
        if (stage >= STAGE_COUNT) revert StonkBrokers__InvalidStage();
        require(stageActive[stage], "stage not active");
        require(quantity > 0, "qty=0");
        require(quantity <= MAX_PER_TX, "qty too high");
        require(mintedCount + quantity <= MAX_SUPPLY, "sold out");
        require(msg.value == MINT_PRICE * quantity, "wrong value");

        uint256 alreadyMinted = minted[msg.sender];
        require(alreadyMinted + quantity <= allocation[msg.sender], "exceeds allocation");
        minted[msg.sender] = alreadyMinted + quantity;

        for (uint256 i = 0; i < quantity; i++) {
            uint256 tokenId = nextTokenId;
            nextTokenId += 1;
            mintedCount += 1;

            _assignUniqueTraitSeed(tokenId);
            _safeMint(msg.sender, tokenId);

            address wallet = registry.createAccount(
                accountImplementation,
                ACCOUNT_SALT,
                block.chainid,
                address(this),
                tokenId
            );
            tokenWallet[tokenId] = wallet;
            emit WalletCreated(tokenId, wallet);

            (address tokenAddress, uint256 tokenBalance) = _pickFundableStockToken(tokenId);
            uint256 grant = _stockGrantForToken(tokenId, tokenAddress, tokenBalance);
            require(grant > 0, "grant=0");

            fundedToken[tokenId] = tokenAddress;
            initialWalletGrant[tokenId] = grant;
            require(IERC20(tokenAddress).transfer(wallet, grant), "token transfer failed");
            fundedTokenMintCount[tokenAddress] += 1;

            emit WalletFunded(tokenId, tokenAddress, grant);
        }
    }

    function withdrawMintProceeds(address payable to) external onlyOwner {
        require(to != address(0), "to=0");
        (bool ok, ) = to.call{value: address(this).balance}("");
        require(ok, "withdraw failed");
    }

    /// @notice Turn a whitelist stage on or off. Manual only — no timers or auto-expiry.
    function setStageActive(uint8 stage, bool active) external onlyOwner {
        if (stage >= STAGE_COUNT) revert StonkBrokers__InvalidStage();
        stageActive[stage] = active;
        emit StageStatusUpdated(stage, active);
    }

    /// @notice Batch-grant additional whitelist allocation, tagged under `stage` for
    /// bookkeeping. Amounts are ADDITIVE — calling this again for a wallet (e.g. because more
    /// wallets earned slots via burns after the first snapshot) tops up its allocation rather
    /// than overwriting it, so previously-granted or already-minted amounts are never lost.
    /// Emits one event per wallet so the frontend/an indexer can reconstruct the full
    /// whitelist history without needing an on-chain enumerable array.
    function setWhitelist(uint8 stage, address[] calldata wallets, uint256[] calldata allocations) external onlyOwner {
        if (stage >= STAGE_COUNT) revert StonkBrokers__InvalidStage();
        require(wallets.length == allocations.length, "length mismatch");
        for (uint256 i = 0; i < wallets.length; i++) {
            uint256 newTotal = allocation[wallets[i]] + allocations[i];
            allocation[wallets[i]] = newTotal;
            emit WhitelistAllocationAdded(stage, wallets[i], allocations[i], newTotal);
        }
    }

    /// @notice Global switch for secondary transfers. Defaults to false at deploy — no
    /// transfer of any kind (including direct wallet-to-wallet) succeeds until the owner
    /// calls this with `true`. Independent of, and stronger than, the ERC-721C validator.
    function setTransfersEnabled(bool enabled) external onlyOwner {
        transfersEnabled = enabled;
        emit TransfersEnabledUpdated(enabled);
    }

    /// @notice Step 1 of 2 — arm the reveal. Records a block `REVEAL_DELAY` in the future whose
    /// (not-yet-known) hash `reveal()` will later consume as the shuffle seed. Call this once the
    /// mint is finished. Safe to call again to re-arm (e.g. if `reveal()` was left unfinalized
    /// long enough that the committed blockhash aged out of the EVM's 256-block window). Owner-
    /// only so only the team decides *when* the reveal window opens; the actual randomness comes
    /// from the future blockhash, which the owner cannot know or choose at this point.
    function commitReveal() external onlyOwner {
        if (revealed) revert StonkBrokers__AlreadyRevealed();
        if (mintedCount == 0) revert StonkBrokers__NothingMinted();
        revealCommitBlock = block.number + REVEAL_DELAY;
        emit RevealArmed(revealCommitBlock);
    }

    /// @notice Step 2 of 2 — finalize the reveal. Permissionless on purpose: once armed, anyone
    /// can trigger it (the frontend does so automatically after the delay), so the owner can
    /// neither stall the reveal nor cherry-pick the block it lands in. Derives the collection-wide
    /// rotation `startingIndex` from `blockhash(revealCommitBlock)`. If that block has aged past
    /// the EVM's 256-block hash window (`blockhash` returns 0), it reverts and the owner must
    /// re-arm via `commitReveal()` — the shuffle is never seeded from a zero hash.
    function reveal() external {
        if (revealed) revert StonkBrokers__AlreadyRevealed();
        if (revealCommitBlock == 0) revert StonkBrokers__RevealNotArmed();
        if (block.number <= revealCommitBlock) revert StonkBrokers__RevealTooEarly();

        bytes32 bh = blockhash(revealCommitBlock);
        if (bh == bytes32(0)) revert StonkBrokers__RevealCommitExpired();

        uint256 n = mintedCount;
        uint256 idx = uint256(keccak256(abi.encodePacked(bh, address(this), n))) % n;
        // Avoid the degenerate identity rotation (0), which would leave the as-minted order —
        // i.e. the fully predictable mapping — in place. Any other value is an equally-random
        // rotation of the unique seed set.
        if (idx == 0) idx = 1;

        startingIndex = idx;
        revealSupply = n;
        revealed = true;
        emit Revealed(idx, n);
        // ERC-4906: every token's metadata just changed (placeholder -> real art), so tell
        // indexers to re-fetch the whole range in one event.
        emit BatchMetadataUpdate(1, n);
    }

    /// @dev The stored seed that `tokenId` actually renders with, after applying the reveal
    /// rotation. Reverts if called before reveal (callers gate on `revealed` and show a
    /// placeholder instead). Because this is a pure rotation over positions `1..revealSupply`,
    /// it is a bijection: the revealed collection displays exactly the same unique set of seeds
    /// that mint-time dedup produced, just remapped to different tokenIds.
    function _effectiveSeed(uint256 tokenId) internal view returns (uint256) {
        uint256 n = revealSupply;
        uint256 position = ((tokenId - 1 + startingIndex) % n) + 1;
        return tokenTraitSeed[position];
    }

    /// @notice Convenience view for the frontend: how many more this wallet can still mint,
    /// total, right now (shared across whichever stage(s) happen to be active).
    function remainingAllocation(address wallet) external view returns (uint256) {
        uint256 total = allocation[wallet];
        uint256 used = minted[wallet];
        return total > used ? total - used : 0;
    }

    function totalSupply() external view returns (uint256) {
        return mintedCount;
    }

    /// @notice The revealed trait seed for `tokenId`. Returns 0 before reveal — the mapping is
    /// intentionally hidden until then so nobody can front-run the shuffle (the raw stored seeds
    /// remain readable via `tokenTraitSeed`, but without `startingIndex` they can't be tied to a
    /// tokenId, so exposing nothing here keeps the reveal airtight).
    function traitSeedOf(uint256 tokenId) external view returns (uint256) {
        _requireOwned(tokenId);
        if (!revealed) return 0;
        return _effectiveSeed(tokenId);
    }

    /// @notice The revealed trait signature for `tokenId`; `bytes32(0)` before reveal. Used by
    /// the dedupe audit — post-reveal every token's signature is still unique (rotation is a
    /// bijection over the dedup'd seed set).
    function traitSignatureOf(uint256 tokenId) external view returns (bytes32) {
        _requireOwned(tokenId);
        if (!revealed) return bytes32(0);
        return renderer.traitSignatureFromSeed(_effectiveSeed(tokenId));
    }

    function previewSvg(uint256 tokenId, uint256 traitSeed) external view returns (string memory) {
        return renderer.renderSvg(tokenId, traitSeed);
    }

    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        _requireOwned(tokenId);

        // Pre-reveal: every token shows the same placeholder so traits can't be read (or
        // inferred) before the collection-wide shuffle is locked in. Only the wallet address is
        // shown since it is assigned at mint and isn't part of the shuffled art.
        if (!revealed) {
            return _placeholderTokenURI(tokenId);
        }

        uint256 seed = _effectiveSeed(tokenId);
        string memory image = renderer.renderSvg(tokenId, seed);
        address wallet = tokenWallet[tokenId];

        // Wallet balances are deliberately NOT surfaced as traits: broker wallets accumulate
        // multiple stock tokens over time (StockBooster airdrops AAPL/AMZN/NVDA and more), so
        // any single "stock" attribute would misrepresent the portfolio. The wallet address is
        // the only account attribute — holders inspect balances directly.
        string memory visualAttrs = renderer.buildVisualAttributes(tokenId, seed);
        string memory json = string(
            abi.encodePacked(
                '{"name":"Stonk Broker #',
                tokenId.toString(),
                '","description":"4444 pixel stock-broker NFTs with dedicated onchain wallets funded at mint. ERC-721C with enforceable royalties.","attributes":[',
                visualAttrs,
                ',{"trait_type":"Wallet Address","value":"',
                Strings.toHexString(uint256(uint160(wallet)), 20),
                '"}],"image":"data:image/svg+xml;base64,',
                Base64.encode(bytes(image)),
                '"}'
            )
        );

        return string(abi.encodePacked("data:application/json;base64,", Base64.encode(bytes(json))));
    }

    /// @dev Placeholder metadata served for every token until `reveal()` runs. Deliberately
    /// carries NO visual trait attributes so nothing about the shuffled art can be inferred early.
    function _placeholderTokenURI(uint256 tokenId) internal view returns (string memory) {
        address wallet = tokenWallet[tokenId];

        string memory image = placeholderRenderer == address(0)
            ? '<svg xmlns="http://www.w3.org/2000/svg" width="480" height="480" viewBox="0 0 480 480">'
              '<rect width="480" height="480" fill="#0a0c10"/>'
              '<rect x="150" y="120" width="180" height="180" rx="14" fill="#151a24" stroke="#2a3142" stroke-width="4"/>'
              '<text x="240" y="235" font-family="monospace" font-size="120" fill="#f5d36b" text-anchor="middle">?</text>'
              '<text x="240" y="360" font-family="monospace" font-size="30" fill="#e8ecf4" text-anchor="middle">STONK BROKER</text>'
              '<text x="240" y="398" font-family="monospace" font-size="22" fill="#8b95a8" text-anchor="middle">UNREVEALED</text>'
              "</svg>"
            : IStonkBrokerPlaceholder(placeholderRenderer).placeholderSvg(tokenId);

        string memory json = string(
            abi.encodePacked(
                '{"name":"Stonk Broker #',
                tokenId.toString(),
                ' (Unrevealed)","description":"4444 pixel stock-broker NFTs with dedicated onchain wallets funded at mint. Traits are hidden until the collection-wide reveal to prevent trait sniping.","attributes":[',
                '{"trait_type":"Status","value":"Unrevealed"}',
                ',{"trait_type":"Wallet Address","value":"',
                Strings.toHexString(uint256(uint160(wallet)), 20),
                '"}],"image":"data:image/svg+xml;base64,',
                Base64.encode(bytes(image)),
                '"}'
            )
        );

        return string(abi.encodePacked("data:application/json;base64,", Base64.encode(bytes(json))));
    }

    function predictWallet(uint256 tokenId) external view returns (address) {
        return registry.account(accountImplementation, ACCOUNT_SALT, block.chainid, address(this), tokenId);
    }

    function stockTokenCount() external view returns (uint256) {
        return stockTokens.length;
    }

    function stockTokenAt(uint256 index) external view returns (address) {
        require(index < stockTokens.length, "index out of bounds");
        return stockTokens[index];
    }

    function setDefaultRoyalty(address receiver, uint96 feeNumerator) external onlyOwner {
        _setDefaultRoyalty(receiver, feeNumerator);
    }

    function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) external onlyOwner {
        _setTokenRoyalty(tokenId, receiver, feeNumerator);
    }

    function setTransferValidator(address transferValidator_) public onlyOwner {
        bool isValidTransferValidator = transferValidator_.code.length > 0;

        if (transferValidator_ != address(0) && !isValidTransferValidator) {
            revert StonkBrokers__InvalidTransferValidatorContract();
        }

        emit TransferValidatorUpdated(transferValidator, transferValidator_);
        transferValidator = transferValidator_;
    }

    /// @notice `address(0)` until the owner explicitly sets one — see the field's doc comment.
    function getTransferValidator() public view returns (address validator) {
        return transferValidator;
    }

    function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) {
        functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)"));
        isViewFunction = true;
    }

    function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC2981) returns (bool) {
        return
            interfaceId == type(ICreatorToken).interfaceId ||
            interfaceId == type(Legacy.ICreatorTokenLegacy).interfaceId ||
            // ERC-4906 (metadata update events). The spec fixes this ID at 0x49064906 rather
            // than deriving it from a function-selector XOR, because the standard defines only
            // events. Advertising it is how OpenSea/Blockscout know to listen for the
            // BatchMetadataUpdate we emit in reveal().
            interfaceId == bytes4(0x49064906) ||
            super.supportsInterface(interfaceId);
    }

    /// @notice Blocks approvals while the global transfer lock is on, so no marketplace
    /// conduit (e.g. OpenSea's Seaport) can be pre-approved to fire the instant transfers
    /// are enabled. Approvals work normally once `transfersEnabled` is true.
    function approve(address to, uint256 tokenId) public virtual override {
        if (!transfersEnabled) revert StonkBrokers__TransfersDisabled();
        super.approve(to, tokenId);
    }

    /// @notice Same rationale as `approve` — blocked while the global transfer lock is on.
    function setApprovalForAll(address operator, bool approved) public virtual override {
        if (!transfersEnabled) revert StonkBrokers__TransfersDisabled();
        super.setApprovalForAll(operator, approved);
    }

    function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
        address from = _ownerOf(tokenId);
        if (from != address(0) && to != address(0)) {
            if (!transfersEnabled) revert StonkBrokers__TransfersDisabled();
            address validator = getTransferValidator();
            if (validator != address(0) && msg.sender != validator) {
                ITransferValidator(validator).validateTransfer(_msgSender(), from, to, tokenId);
            }
            // Clear the Anvil-market activation tier on every true ownership change (mints are
            // excluded — from == 0 — and burns don't exist here). try/catch because a transfer
            // hook must never be able to brick transfers, even if the manager misbehaves.
            // AUDIT NOTE (gas-griefing): deliberately starving this subcall so the clear OOGs
            // while the transfer completes is infeasible — under EIP-150 the subcall receives
            // 63/64 of available gas, so for the remaining 1/64 to still cover the transfer's
            // own storage writes (~30k), the subcall would have to receive ~1.9M gas, orders of
            // magnitude more than clearActivation's ~50k worst case. It cannot OOG first.
            address manager = activationManager;
            if (manager != address(0)) {
                try IActivationClearer(manager).clearActivation(tokenId) {} catch {}
            }
        }
        return super._update(to, tokenId, auth);
    }

    function _assignUniqueTraitSeed(uint256 tokenId) internal {
        for (uint256 attempt = 0; attempt < 256; attempt++) {
            uint256 seed = StonkBrokerTraitsV6Proposal.candidateSeed(tokenId, attempt);
            bytes32 sig = renderer.traitSignatureFromSeed(seed);
            if (!usedTraitSignatures[sig]) {
                usedTraitSignatures[sig] = true;
                tokenTraitSeed[tokenId] = seed;
                return;
            }
        }
        revert("trait space exhausted");
    }

    function _stockGrantForToken(uint256 tokenId, address tokenAddress, uint256 tokenBalance)
        internal
        view
        returns (uint256)
    {
        uint256 remainingMints = (MAX_SUPPLY - tokenId) + 1;
        uint256 expectedMintsForToken = (remainingMints / stockTokens.length) + 1;
        uint256 perMintBudget = tokenBalance / expectedMintsForToken;
        if (perMintBudget == 0) {
            return tokenBalance > 0 ? 1 : 0;
        }

        uint256 seed = uint256(keccak256(abi.encodePacked("STONK_BROKERS_V6", tokenId, tokenAddress)));
        uint256 grantBps = 6500 + (seed % 3501);
        uint256 grant = (perMintBudget * grantBps) / 10_000;
        if (grant == 0) return 1;
        if (grant > tokenBalance) return tokenBalance;
        return grant;
    }

    function _pickFundableStockToken(uint256 tokenId) internal view returns (address tokenAddress, uint256 tokenBalance) {
        uint256 minAssigned = type(uint256).max;
        uint256 eligibleCount = 0;

        for (uint256 i = 0; i < stockTokens.length; i++) {
            address candidate = stockTokens[i];
            uint256 balance = IERC20(candidate).balanceOf(address(this));
            if (balance == 0) continue;
            if (_stockGrantForToken(tokenId, candidate, balance) == 0) continue;

            uint256 assigned = fundedTokenMintCount[candidate];
            if (assigned < minAssigned) {
                minAssigned = assigned;
                eligibleCount = 1;
            } else if (assigned == minAssigned) {
                eligibleCount += 1;
            }
        }

        if (eligibleCount == 0) {
            revert("insufficient token inventory");
        }

        uint256 pick = uint256(keccak256(abi.encodePacked("STONK_STOCK_PICK_V6", tokenId, mintedCount))) % eligibleCount;
        uint256 cursor = 0;
        for (uint256 i = 0; i < stockTokens.length; i++) {
            address candidate = stockTokens[i];
            uint256 balance = IERC20(candidate).balanceOf(address(this));
            if (balance > 0 && _stockGrantForToken(tokenId, candidate, balance) > 0 && fundedTokenMintCount[candidate] == minAssigned) {
                if (cursor == pick) return (candidate, balance);
                cursor += 1;
            }
        }

        revert("insufficient token inventory");
    }
}
Chain explorer1692msChain node88ms