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
- Creator
- 0xb668382cF4…A809787CDa
- Creation tx
- 0x35c32b43ce…88af90efd4
Token
- Name
- StonkBrokers
- Symbol
- STONK
- Decimals
- —
- Holders
- 519
- Market
- View token page →
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