Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"WantedCyborgs.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.29;\r
\r
// _ __ __ __ \r
// | | / /___ _____ / /____ ____/ / \r
// | | /| / / __ `/ __ \/ __/ _ \/ __ / \r
// | |/ |/ / /_/ / / / / /_/ __/ /_/ / \r
// |__/|__/\__,_/_/ /_/\__/\___/\__,_/ \r
// ______ __ \r
// / ____/_ __/ /_ ____ _________ ______ \r
// / / / / / / __ \/ __ \/ ___/ __ `/ ___/ \r
// / /___/ /_/ / /_/ / /_/ / / / /_/ (__ ) \r
// \____/\__, /_.___/\____/_/ \__, /____/ \r
// /____/ /____/ \r
//\r
// Creator: MadV0x\r
// Deployer: Syntetik Labs\r
\r
import { ERC721A } from "lib/ERC721A/contracts/ERC721A.sol";\r
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";\r
import { ReentrancyGuard } from "lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol";\r
import { ERC721TransferValidator } from "src/lib/ERC721TransferValidator.sol";\r
import { ICreatorToken, ILegacyCreatorToken } from "src/interfaces/ICreatorToken.sol";\r
import { ITransferValidator721 } from "src/interfaces/ITransferValidator.sol";\r
import { ERC2981 } from "@openzeppelin/contracts/token/common/ERC2981.sol";\r
import { IERC2981 } from "lib/openzeppelin-contracts/contracts/interfaces/IERC2981.sol";\r
import { IERC165 } from "lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol";\r
import { WANTEDErrorsAndEvents } from "WANTED/lib/WANTEDErrorsAndEvents.sol";\r
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";\r
\r
/**\r
* @title WANTED Cyborgs (WANTED).\r
* @notice Implements Token holding time tracking.\r
* @notice Implements marketsPaused : Override approvals to restrict \r
* listings on marketplaces until Hunt out.\r
* @notice Implements PreHunt (HuntFor) function (onlyOwner).\r
* @notice Implements Multistages Hunt with Dutch Auctions and standard HuntListing.\r
* @notice Implements Sell/transfer Ownership2steps.\r
* @notice Implements Freeze Metadata (contractURI and BaseURI).\r
* @notice Implements Limit Break's Creator Token Standards transfer.\r
* validation for royalty enforcement.\r
* @notice ADDED Admin role & MasterHunter Position.\r
* @notice REMOVED from ERC721A: burn() => no burn of WANTEDs !!!\r
* @notice REMOVED from ERC721A: unused mintERC2309\r
*/\r
\r
contract WantedCyborgs is ERC721A, ERC721TransferValidator, Ownable, ReentrancyGuard, ERC2981, WANTEDErrorsAndEvents {\r
\r
using EnumerableSet for EnumerableSet.AddressSet;\r
EnumerableSet.AddressSet private HuntList;\r
\r
/// INITIALIZE HUNT PARAMETERS\r
bool public marketsPaused = true;\r
bool public HuntPaused = true;\r
bool public HuntListActive = true;\r
uint256 public HuntPrice = 0.05 ether;\r
uint256 public stageMaxSupply = 1250;\r
uint256 public immutable maxSupply = 5000;\r
uint256 public walletHuntLimit = 1;\r
\r
// Dutch Auction parameters.\r
uint256 public auctionStartTime = 0; // Timestamp when the auction begins. Defaults to 0 (inactive).\r
uint256 public auctionDuration; // Total duration of the auction (in seconds).\r
uint256 public auctionStartPrice; // Starting auction price (in wei).\r
uint256 public auctionEndPrice; // Final (minimum) auction price (in wei).\r
uint256 public auctionDropInterval; // Time interval (in seconds) between each price drop.\r
uint256 public auctionDropPerStep; // The amount (in wei) that the price drops at each interval.\r
\r
// @notice Price set for a contract Ownership transfer. \r
uint256 public ownershipPrice;\r
\r
// @notice Address currently holding the MasterHunter position.\r
address public MasterHunter;\r
\r
// @notice ADMIN address.\r
address public Admin;\r
\r
// @notice Pending Owner address (if ownership transfer).\r
address public pendingOwner;\r
\r
// @notice Track the URI for contract metadata.\r
string public contractURI;\r
\r
// @notice Track if contract metadata is frozen (baseURI + contractURI).\r
// @dev emit an event when frozen.\r
bool public isMetadataFrozen = false;\r
\r
// @dev Mapping from tokenId to tokenData.\r
mapping(uint256 => uint96) private tokenHoldingStart;\r
\r
constructor(\r
string memory _name,\r
string memory _symbol,\r
string memory _baseURI\r
) ERC721A(_name, _symbol, _baseURI) ERC2981() Ownable(msg.sender) {\r
}\r
\r
/**\r
* @notice Returns whether the interface is supported.\r
*\r
* @param interfaceId The interface id to check against.\r
*/\r
function supportsInterface(bytes4 interfaceId)\r
public\r
view\r
override(ERC721A, ERC2981)\r
returns (bool)\r
{\r
return\r
interfaceId == type(IERC165).interfaceId ||\r
interfaceId == type(IERC2981).interfaceId ||\r
interfaceId == type(ICreatorToken).interfaceId ||\r
interfaceId == type(ILegacyCreatorToken).interfaceId ||\r
interfaceId == 0x49064906 || // ERC-4906\r
super.supportsInterface(interfaceId);\r
}\r
\r
/**\r
* @notice Public hunting function supporting multiple types of sale stages.\r
* @dev Handles simple HuntList, Dutch Auction, and public Hunt phases.\r
* Keeps `HuntListActive` enabled until the public Hunt stage(s) starts.\r
* Reverts if quantity is zero, Hunting is paused, or limits are exceeded.\r
* Refunds excess payment if overpaid.\r
*\r
* @param quantity Number of WANTED tokens to Hunt.\r
*/\r
function hunt(uint256 quantity) nonReentrant external payable {\r
// Perform initial verifications before proceeding.\r
_checkBeforeHunt(quantity);\r
\r
uint256 totalPrice = HuntPrice * quantity;\r
\r
// Adjust price if a Dutch Auction is active. \r
if (auctionStartTime != 0) {\r
totalPrice = getDutchAuctionCurrentPrice() * quantity;\r
} \r
\r
// Revert if insufficient payment. \r
if (msg.value < totalPrice) {\r
revert InvalidPaidPrice();\r
}\r
// Revert if quantity Hunted exceeds maxSupply:\r
if (totalSupply() + quantity > maxSupply) {\r
revert QuantityExceedMaxSupply();\r
}\r
\r
_safeMint(msg.sender, quantity);\r
emit Hunted(msg.sender, quantity);\r
\r
// Refund any excess payment.\r
if (msg.value > totalPrice) {\r
payable(msg.sender).transfer(msg.value - totalPrice);\r
}\r
\r
// Trigger Hunt out logic if max supply reached.\r
if(totalSupply() == maxSupply) {\r
_isHuntOut();\r
}\r
}\r
\r
/**\r
* @dev Marks the Hunting process as complete and emits a `HuntOutAchieved` event.\r
* Unlocks Marketplaces by setting `tradePaused` to false (unlock approvals).\r
*\r
* Note:\r
* This function assumes that the condition for "Hunt out" has already been met \r
* and is simply used to trigger related state changes and notifications.\r
*/\r
function _isHuntOut() private {\r
emit HuntOutAchieved(block.timestamp);\r
marketsPaused = false;\r
} \r
\r
/**\r
* @dev Internal validation function that runs multiple checks before Hunting.\r
*\r
* Reverts with:\r
* - `ZeroQuantityOrPaused` if quantity is zero or Hunting is currently paused.\r
* - `WalletHuntLimit` if Hunting this quantity exceeds the caller's wallet limit.\r
* - `NotHuntListedOrPaused` if HuntList is active and caller is not HuntListed.\r
* - `QuantityExceedsStageMaxSupply` if Hunting this quantity would exceed the current stage's max supply.\r
*\r
* @param quantity The number of tokens the caller intends to Hunt.\r
*/\r
function _checkBeforeHunt(uint256 quantity) private view {\r
// Check for zero quantity or paused Hunt.\r
if (quantity == 0 || HuntPaused) {\r
revert ZeroQuantityOrPaused();\r
}\r
// Check if the Hunted quantity exceeds the wallet's Hunt limit.\r
if (_numberMinted(msg.sender) + quantity > walletHuntLimit) {\r
revert WalletHuntLimit();\r
}\r
// If HuntList is active, verify caller is HuntListed.\r
if (HuntListActive) {\r
if(!HuntList.contains(msg.sender)){\r
revert NotHuntListed();\r
}\r
}\r
// Ensure Hunted quantity does not exceed current stage max supply.\r
if (totalSupply() + quantity > stageMaxSupply) {\r
revert QuantityExceedsStageMaxSupply();\r
}\r
}\r
\r
/**\r
* @notice Calculates and returns the current price of the token during the Dutch Auction.\r
* @dev Reverts if the auction hasn't started (`auctionStartTime == 0` or is in the future) \r
* or if the auction duration has already elapsed.\r
* \r
* Note: \r
* Price decreases over time in steps defined by `auctionDropInterval`, \r
* with each step reducing the price by `auctionDropPerStep`. \r
* The price will never drop below `auctionEndPrice`.\r
*\r
* @return The current Dutch Auction price in wei.\r
*/\r
function getDutchAuctionCurrentPrice() public view returns (uint256) {\r
if (auctionStartTime == 0 || block.timestamp < auctionStartTime) {\r
revert DutchAuctionNotStarted();\r
}\r
\r
uint256 elapsed = block.timestamp - auctionStartTime;\r
\r
if (elapsed >= auctionDuration) {\r
revert DutchAuctionEnded();\r
}\r
\r
// Calculate number of steps passed\r
uint256 steps = elapsed / auctionDropInterval;\r
\r
// Calculate price drop based on steps\r
uint256 discount = steps * auctionDropPerStep;\r
\r
uint256 price = auctionStartPrice > discount\r
? auctionStartPrice - discount\r
: auctionEndPrice;\r
\r
// Ensure price doesn't go below auctionEndPrice\r
if (price < auctionEndPrice) {\r
price = auctionEndPrice;\r
}\r
return price;\r
}\r
\r
/**\r
* @notice Returns the remaining time in the Dutch Auction.\r
* @dev Return 0 if the auction hasn't started (`auctionStartTime == 0` or is in the future) \r
*\r
* @return The Dutch Auction remaining time in seconds.\r
*/\r
function getAuctionRemainingTime() public view returns (uint256) {\r
if (auctionStartTime == 0 || block.timestamp < auctionStartTime \r
|| block.timestamp > auctionStartTime + auctionDuration) {\r
return 0;\r
}\r
return auctionStartTime + auctionDuration - block.timestamp;\r
}\r
\r
/**\r
* @notice Allows the owner to Hunt new WANTED to a specified account.\r
* @dev Intended for preHunting or forcing Hunt out for opening sales.\r
* Reverts if quantity is zero or if Hunting exceeds max supply.\r
*\r
* @param account The address receiving the Hunted tokens.\r
* @param quantity The number of WANTED tokens to Hunt.\r
*/\r
function HuntFor(address account, uint256 quantity) external { \r
_isOwnerOrAdmin();\r
\r
if (quantity == 0) {\r
revert ZeroQuantity();\r
} \r
\r
if (totalSupply() + quantity > stageMaxSupply) {\r
revert QuantityExceedsStageMaxSupply();\r
}\r
\r
if (totalSupply() + quantity > maxSupply) {\r
revert QuantityExceedMaxSupply();\r
} \r
\r
_safeMint(account, quantity);\r
emit Hunted(account, quantity);\r
\r
// Check if Hunt Out and call _isHuntOut() if true\r
if(totalSupply() == maxSupply) {\r
_isHuntOut();\r
}\r
}\r
\r
/**\r
* @notice Configure Hunting parameters and stages (Admin only).\r
* @dev Allows toggling huntList activation, Hunt price, and max supply for the current stage.\r
*\r
* @param _HuntListActive Whether HuntList is active for the current Hunt stage.\r
* @param _HuntPrice Price per token Hunt (in wei).\r
* @param _stageMaxSupply Maximum number of tokens allowed to be Hunted in this stage.\r
*/\r
function setHunt(\r
bool _HuntListActive, \r
uint256 _HuntPrice, \r
uint256 _stageMaxSupply\r
) external {\r
_isOwnerOrAdmin();\r
HuntPrice = _HuntPrice;\r
stageMaxSupply = _stageMaxSupply;\r
HuntListActive = _HuntListActive;\r
}\r
\r
/**\r
* @notice Pause/Unpause the Hunt.\r
*\r
* @param _HuntPaused set 'true' to pause Hunting.\r
*/\r
function pauseHunt(bool _HuntPaused) external {\r
_isOwnerOrAdmin();\r
HuntPaused = _HuntPaused;\r
}\r
\r
/**\r
* @notice Configure Dutch Auction parameters (Admin only).\r
* @dev Sets auction pricing and timing details in wei and seconds.\r
*\r
* @param _startPrice Initial price at auction start (in wei).\r
* @param _endPrice Minimum price at auction end (in wei).\r
* @param _duration Total duration of the auction (in seconds).\r
* @param _dropInterval Time interval between each price drop (in seconds).\r
* @param _dropPerStep Amount by which the price decreases at each interval (in wei).\r
*/\r
function setDutchAuction(\r
uint256 _startPrice, \r
uint256 _endPrice, \r
uint256 _duration, \r
uint256 _dropInterval, \r
uint256 _dropPerStep\r
) external {\r
_isOwnerOrAdmin();\r
auctionStartPrice = _startPrice;\r
auctionEndPrice = _endPrice;\r
auctionDuration = _duration;\r
auctionDropInterval = _dropInterval;\r
auctionDropPerStep = _dropPerStep;\r
}\r
\r
/**\r
* @notice Launches the Dutch Auction (Admin only).\r
* @dev Unpauses the auction by setting the start time to the current block timestamp.\r
*/\r
function startDutchAuction() external {\r
_isOwnerOrAdmin();\r
HuntPaused = false;\r
auctionStartTime = block.timestamp;\r
}\r
\r
/**\r
* @notice Stops and resets the Dutch Auction (Admin only).\r
* @dev Pauses the auction by setting the start time to zero.\r
* Also pauses the Hunting function by setting `HuntPaused = true`.\r
* To temporarily pause Hunting without resetting the auction,\r
* use `setHunt()` instead.\r
*/\r
function stopDutchAuction() external {\r
_isOwnerOrAdmin();\r
HuntPaused = true;\r
auctionStartTime == 0;\r
}\r
\r
/**\r
* @notice Returns the total number of NFTs Hunted by a given account.\r
* @param account The address to query.\r
* @return The number of tokens Hunted by the account.\r
*/\r
function amountHunted(address account) external view returns (uint256) {\r
return _numberMinted(account);\r
}\r
\r
/**\r
* @notice Updates the maximum number of tokens that a single wallet can Hunt.\r
* @dev Callable only by the contract owner or admin.\r
* @param _limit The new wallet Hunting limit.\r
*/\r
function setWalletHuntLimit(uint256 _limit) external {\r
_isOwnerOrAdmin();\r
walletHuntLimit = _limit;\r
}\r
\r
/**\r
* @notice Checks if a given account is on the HuntList.\r
* @param account The address to check.\r
* @return 'true' if the account is HuntListed, 'false' otherwise.\r
*/\r
function isHuntListed(address account) external view returns (bool) {\r
return HuntList.contains(account);\r
}\r
\r
/**\r
* @notice Add or Remove accounts to/from the HuntList (Admin only). \r
* @param accounts The list of accounts to add or remove.\r
* @param add : Set 'true' to add account(s) ('false" to remove).\r
*/\r
function addToHuntList(address[] calldata accounts, bool add) external {\r
_isOwnerOrAdmin();\r
\r
for (uint256 i = 0; i < accounts.length; i++) {\r
if(add) {\r
HuntList.add(accounts[i]);\r
} else {\r
HuntList.remove(accounts[i]);\r
}\r
}\r
emit HuntListUpdated(accounts, add);\r
}\r
\r
/**\r
* @notice Returns the number of entries in the HuntList.\r
* @dev This function returns the current length of the HuntList array.\r
* @return uint256 The total count of HuntListed addresses.\r
*/\r
function HuntListSize() external view returns (uint256) {\r
return HuntList.length();\r
}\r
\r
/**\r
* @notice Batch removes a specified number of accounts from the HuntList. (Admin only)\r
* @dev Removes up to `batchSize` entries from the HuntList, iterating backwards to avoid\r
* index shifting issues during removal. Requires caller to be owner or admin.\r
* Emits `HuntListCleared` of the `batchSize` event upon completion.\r
*\r
* @param batchSize The number of HuntList accounts to remove in this batch.\r
* Must not exceed the current HuntList length.\r
*/\r
function clearHuntList(uint256 batchSize) external {\r
_isOwnerOrAdmin();\r
uint256 length = HuntList.length();\r
require(length > 0, "HuntList already empty");\r
require(batchSize <= length, "Batchsize exceeds WL");\r
\r
for (uint256 i = batchSize; i > 0; i--) {\r
address account = HuntList.at(length - i);\r
HuntList.remove(account);\r
}\r
emit HuntListCleared(batchSize);\r
}\r
\r
/**\r
* @dev Overrides ERC721 `approve` to restrict approvals while `marketsPaused` is true \r
* (i.e., before Hunting is complete). Only the owner or admin can approve operators \r
* before _isHuntOut() is triggered to prevent secondary sales until the Hunt has fully ended.\r
*\r
* @param to The address to approve.\r
* @param tokenId The token ID to approve.\r
*/\r
function approve(address to, uint256 tokenId) public virtual override {\r
address sender = _msgSenderERC721A();\r
\r
if(marketsPaused) {\r
// Revert if sender is not Owner nor Admin.\r
require(sender == owner() || sender == Admin, "Paused until HuntOut");\r
}\r
super.approve(to, tokenId);\r
}\r
\r
/**\r
* @dev Overrides ERC721 `setApprovalForAll` to restrict approvals while `marketsPaused` is true.\r
* (i.e., before Hunting is complete). Only the owner or admin can approve transfers \r
* before _isHuntOut() is triggered to prevent secondary sales until Hunting ends.\r
*\r
* @param operator The address to be approved or disapproved as an operator.\r
* @param approved Boolean indicating approval status (true = approve, false = revoke).\r
*/\r
function setApprovalForAll(address operator, bool approved) public virtual override {\r
address sender = _msgSenderERC721A();\r
\r
if(marketsPaused) {\r
// Revert if sender is not Owner nor Admin.\r
require(sender == owner() || sender == Admin, "Paused until HuntOut");\r
}\r
super.setApprovalForAll(operator, approved);\r
}\r
\r
/**\r
* @dev Internal hook called before any token transfer, including Hunting.\r
* Enforces royalty or transfer restrictions via an external validator contract, if set.\r
*\r
* Note Derived from Limit Break's ERC721-C implementation.\r
* Allows configurable transfer validation logics for on-chain royalty enforcement,\r
* operator filtering, and marketplace restrictions.\r
* \r
* Requirements:\r
* - If both `from` and `to` are non-zero (i.e., not Hunt or burn), the transfer validator (if configured)\r
* is called to validate the transfer.\r
*/\r
function _beforeTokenTransfers(\r
address from,\r
address to,\r
uint256 startTokenId,\r
uint256 quantity \r
) internal override {\r
if (from != address(0) && to != address(0)) {\r
// Call the transfer validator if one is set.\r
address transferValidator = _transferValidator;\r
if (transferValidator != address(0)) {\r
ITransferValidator721(transferValidator).validateTransfer(\r
msg.sender,\r
from,\r
to,\r
startTokenId\r
);\r
}\r
}\r
super._beforeTokenTransfers(from, to, startTokenId, quantity);\r
}\r
\r
/**\r
* @dev Hook called after any token transfer, including Hunting.\r
* Resets the token holding start time on transfer to track new ownership duration.\r
*\r
* Effects:\r
* - For each token transferred, updates `tokenHoldingStart` to the current timestamp.\r
*/\r
function _afterTokenTransfers(address from, address to, uint256 startTokenId, \r
uint256 quantity) internal override {\r
\r
//Reset token holdingTime to zero when ownership changes.\r
for (uint256 i = 0; i < quantity; i++) {\r
tokenHoldingStart[startTokenId + i] = uint96(block.timestamp);\r
}\r
super._afterTokenTransfers(from, to, startTokenId, quantity);\r
}\r
\r
/**\r
* @notice Returns the current holding duration (in seconds) for a specific token.\r
* @dev Reverts if the token has not been Hunted or has no recorded holding start time.\r
*\r
* @param tokenId The ID of the token to query.\r
* @return The number of seconds the current owner has held the token.\r
*/\r
function getTokenHoldingTime(uint256 tokenId) external view returns (uint256) {\r
if (tokenHoldingStart[tokenId] == 0) {\r
revert HoldingTimeQueryForNonExistantToken();\r
}\r
return block.timestamp - tokenHoldingStart[tokenId];\r
}\r
\r
/**\r
* @notice Updates the base URI for token metadata (Admin only).\r
* @dev Reverts if metadata is frozen. Emits `BatchMetadataUpdate` event \r
* covering all Hunted tokens when total supply is non-zero.\r
*\r
* @param newBaseURI The new base URI to set for all tokens.\r
*/\r
function setBaseURI(string calldata newBaseURI) external {\r
_isOwnerOrAdmin();\r
\r
if(isMetadataFrozen) {revert FrozenMetadata();} \r
\r
// Set the new base URI.\r
baseURI = newBaseURI;\r
\r
// Emit an event with the update.\r
if (totalSupply() != 0) {\r
emit BatchMetadataUpdate(1, _nextTokenId());\r
}\r
}\r
\r
/**\r
* @notice Sets the contract-level metadata URI (Admin only).\r
* @dev Reverts if metadata is frozen.\r
*\r
* @param newContractURI The new URI pointing to the contract metadata.\r
*/\r
function setContractURI(string calldata newContractURI) external {\r
_isOwnerOrAdmin();\r
\r
if(isMetadataFrozen) {revert FrozenMetadata();} \r
\r
// Set the new contract URI.\r
contractURI = newContractURI;\r
\r
// Emit an event with the update.\r
emit ContractURIUpdated(newContractURI);\r
}\r
\r
/**\r
* @notice Permanently freezes metadata, preventing further updates (Admin only).\r
* @dev This action is irreversible. Emits a confirmation event upon freezing.\r
*/\r
function freezeMetadata() external {\r
_isOwnerOrAdmin();\r
isMetadataFrozen = true;\r
emit MetadataFrozen();\r
}\r
\r
/**\r
* @notice Updates the royalty recipient address and royalty fee basis points (Admin only).\r
* @dev Reverts if the royalty address is zero or if the basis points exceed 10,000 (100%).\r
*\r
* @param RoyaltyAddress The address to receive royalty payments.\r
* @param RoyaltyFeesInBips The royalty fee in basis points (1 basis point = 0.01%).\r
*/\r
function setRoyaltyInfo(address RoyaltyAddress, uint96 RoyaltyFeesInBips) external {\r
_isOwnerOrAdmin();\r
// Revert if the provided royalty address is the zero address.\r
if (RoyaltyAddress == address(0)) {\r
revert RoyaltyAddressCannotBeZeroAddress();\r
}\r
\r
// Revert if the royalty fee exceeds exceeds 100% (10,000 basis points).\r
if (RoyaltyFeesInBips > 10_000) {\r
revert InvalidRoyaltyBasisPoints(RoyaltyFeesInBips);\r
}\r
\r
// Set the new royalty info.\r
_setDefaultRoyalty(RoyaltyAddress, RoyaltyFeesInBips);\r
\r
// Emit an event with the updated params.\r
emit RoyaltyInfoUpdated(RoyaltyAddress, RoyaltyFeesInBips);\r
}\r
\r
/**\r
* @notice Returns the selector of the transfer validation function used by the contract.\r
* @dev Indicates whether the function is a view or modifies state.\r
*\r
* @return functionSignature The function selector of `validateTransfer`.\r
* @return isViewFunction Boolean indicating if the function is a view function (false in this case).\r
*/\r
function getTransferValidationFunction()\r
external\r
pure\r
returns (bytes4 functionSignature, bool isViewFunction)\r
{\r
functionSignature = ITransferValidator721.validateTransfer.selector;\r
isViewFunction = false;\r
}\r
\r
/**\r
* @notice Sets the address of the transfer validator contract (Admin only).\r
* @dev The transfer validator enforces custom rules on token transfers.\r
*\r
* @param newValidator The address of the new transfer validator contract.\r
*/\r
function setTransferValidator(address newValidator) external {\r
_isOwnerOrAdmin();\r
// Set the new transfer validator.\r
_setTransferValidator(newValidator);\r
}\r
\r
/**\r
* @notice Helper function for marketplaces to check if a transfer is allowed by the validator.\r
* @dev Calls the external transfer validator’s `validateTransfer` function via staticcall.\r
* Returns a boolean indicating if transfer is allowed, and a string with the reason if disallowed.\r
* If no validator is set, returns allowed by default.\r
*\r
* @param from The current owner of the token.\r
* @param to The recipient address of the token transfer.\r
* @param tokenId The ID of the token to be transferred.\r
* @return _allowed (bool) : Whether the transfer is permitted.\r
* @return _reason : Human-readable reason why the transfer is disallowed or an empty string if allowed.\r
*/\r
function isTransferable(address from, address to, uint256 tokenId)\r
external\r
view\r
returns (bool _allowed, string memory _reason)\r
{\r
address transferValidator = _transferValidator;\r
\r
if (transferValidator == address(0)) {\r
return (true, "");\r
}\r
\r
(bool success, bytes memory result) = transferValidator.staticcall(\r
abi.encodeWithSignature(\r
"validateTransfer(address,address,uint256)",\r
from,\r
to,\r
tokenId\r
)\r
);\r
\r
if (success) {\r
// Validator returned normally: decode as (bool, string)\r
(bool allowed, string memory reason) = abi.decode(result, (bool, string));\r
return (allowed, reason);\r
}\r
\r
if (result.length > 68) {\r
// if this is a standard Error(string) revert: decode string\r
string memory reason = abi.decode(result, (string));\r
return (false, reason);\r
}\r
\r
// If revert reason is short or unknown, return a default string\r
return (false, "Reverted without reason");\r
}\r
\r
/**\r
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\r
* Can only be called by the current owner.\r
*\r
* Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.\r
*/\r
function transferOwnership(address newOwner) public virtual override onlyOwner {\r
pendingOwner = newOwner;\r
emit OwnershipTransferStarted(owner(), newOwner);\r
}\r
\r
/**\r
* @notice Allows the contract owner to set the price required for an ownership transfer.\r
* @param _ownershipPrice The new price (in wei) for transferring ownership.\r
*/\r
function setOwnershipPrice(uint256 _ownershipPrice) public virtual payable onlyOwner {\r
ownershipPrice = _ownershipPrice;\r
}\r
\r
/**\r
* @notice Allows the pending owner to accept ownership transfer by paying the required price.\r
* @dev Reverts if the caller is not the pending owner or if the sent ETH amount does not match `ownershipPrice`.\r
* Uses `nonReentrant` modifier to prevent reentrancy attacks.\r
* Transfers ownership upon successful payment and verification.\r
*/\r
function acceptOwnership() nonReentrant public virtual payable {\r
require(msg.value == ownershipPrice, "Incorrect Price");\r
address sender = _msgSender();\r
if (pendingOwner != sender) {\r
revert OwnableUnauthorizedAccount(sender);\r
}\r
_transferOwnership(sender);\r
}\r
\r
/**\r
* @dev Transfers contract ownership to `newOwner` and clears any pending ownership transfer.\r
* This is an internal function without access restrictions and overrides the parent implementation.\r
*\r
* @param newOwner The address to transfer ownership to.\r
*/\r
function _transferOwnership(address newOwner) internal virtual override {\r
delete pendingOwner;\r
super._transferOwnership(newOwner);\r
}\r
\r
/**\r
* @dev Internal helper function to verify if the caller is either the contract owner or the designated admin.\r
* Reverts with `NotOwnerNorAdmin` error if unauthorized.\r
*/ \r
function _isOwnerOrAdmin() internal view {\r
if(msg.sender != owner() && msg.sender != Admin) {\r
revert NotOwnerNorAdmin();\r
}\r
}\r
\r
/**\r
* @notice Grants the Admin role to a specified account. Use zero address to revoke.\r
* @dev Can only be called by the owner or current admin.\r
*\r
* @param newAdmin The address to assign as Admin. Use `address(0)` to revoke the role.\r
*/\r
function grantAdminRole(address newAdmin) external {\r
_isOwnerOrAdmin();\r
Admin = newAdmin; // Grant the role to an address\r
emit AdminRoleGranted(newAdmin);\r
}\r
\r
/**\r
* @notice On-chain gamification: Allows an account to claim the "MasterHunter" position.\r
* Caller must own at least 15 WANTEDs and hold more than the current MasterHunter.\r
* Reverts if conditions are not met.\r
* @dev Updates the MasterHunter to the caller and emits an event.\r
*/ \r
function claimMasterHunter() external {\r
\r
// Claimer must hold 15+ WANTEDs.\r
if(balanceOf(msg.sender) < 15) {\r
revert MasterHunterNotClaimable();\r
} \r
// Claimer must hold more WANTEDs than current MasterHunter.\r
if(MasterHunter != address(0) && balanceOf(msg.sender) <= balanceOf(MasterHunter)) {\r
revert MasterHunterNotClaimable();\r
}\r
\r
// Set claimer as the new MasterHunter.\r
MasterHunter = msg.sender;\r
\r
// Emit an event with the updated account.\r
emit NewMasterHunter(msg.sender);\r
}\r
\r
/**\r
* @notice Withdraw all Ether from the contract. Only owner or Admin.\r
*/\r
function withdraw() external {\r
_isOwnerOrAdmin();\r
\r
require(Admin != address(0), "Admin not set");\r
uint256 balance = address(this).balance;\r
require(balance > 0, "0 Balance");\r
\r
payable(owner()).transfer(balance * 25/100);\r
payable(Admin).transfer(balance * 75/100);\r
\r
emit Withdrawal(msg.sender, balance);\r
}\r
}\r
"
},
"@openzeppelin/contracts/utils/structs/EnumerableSet.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
import {Arrays} from "../Arrays.sol";
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
* - Set can be cleared (all elements removed) in O(n).
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function _clear(Set storage set) private {
uint256 len = _length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(Bytes32Set storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(AddressSet storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(UintSet storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
}
"
},
"WANTED/lib/WANTEDErrorsAndEvents.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.29;\r
\r
interface WANTEDErrorsAndEvents {\r
\r
/// @dev Revert if the royalty basis points is greater than 10_000.\r
error InvalidRoyaltyBasisPoints(uint256 basisPoints);\r
\r
/// @dev Revert if the royalty address is being set to the zero address.\r
error RoyaltyAddressCannotBeZeroAddress();\r
\r
/// @dev Revert if Metadata is fully frozen.\r
error FrozenMetadata();\r
\r
/// @dev Revert if set Supply > MaxSupply.\r
error ExceedMaxSupply();\r
\r
/// @dev Revert if Hunt quantity is zero or Hunt is Paused.\r
error ZeroQuantityOrPaused();\r
\r
/// @dev Revert if Public Hunt not Opened.\r
error PublicHuntPaused();\r
\r
/// @dev Revert if account is not on the HuntList.\r
error NotHuntListed();\r
\r
/// @dev Revert if quantity exceeds wallet limit.\r
error WalletHuntLimit();\r
\r
/// @dev Revert if Public Hunt Supply has been capped for a Stage and Hunt quantity Exceeds it.\r
error QuantityExceedsStageMaxSupply();\r
\r
/// @dev Revert if Hunt quantity will overflow MaxSupply.\r
error QuantityExceedMaxSupply();\r
\r
/// @dev Revert if paid price (sent price) too low.\r
error InvalidPaidPrice();\r
\r
/// @dev Revert if Hunt quantity is zero.\r
error ZeroQuantity();\r
\r
/// @dev Revert if holding time is queried but token doesn't exist.\r
error HoldingTimeQueryForNonExistantToken();\r
\r
/// @dev Revert if Caller is neither Owner nor Admin.\r
error NotOwnerNorAdmin();\r
\r
/// @dev Revert if Dutch Auction paused.\r
error DutchAuctionNotStarted();\r
\r
/// @dev Revert if Dutch Auction paused.\r
error DutchAuctionEnded();\r
\r
/// @dev Revert if not meeting requirements to become the MasterHunter.\r
error MasterHunterNotClaimable(); \r
\r
/// @dev Revert if ether withdrawl failed.\r
error WithdrawalFailed(); \r
\r
// @dev Emit an event when accounts are added (true) to or removed (false) from the HuntList.\r
event HuntListUpdated(address[] accounts, bool addedOrRemoved);\r
\r
// @dev Emit an event when the HuntList has been cleared of amount entries.\r
event HuntListCleared(uint256 amount);\r
\r
/// @dev Emit a Hunted event for HuntFor() and Hunt()\r
event Hunted(address Hunter, uint256 HuntedAmount);\r
\r
// @dev Emit an event for token transfer with holding time update.\r
event TokenHeldTime(address from, uint256 tokenId, uint256 heldTime);\r
\r
// @dev Emit an event for HuntOut.\r
event HuntOutAchieved(uint256 HuntOutTime);\r
\r
/**\r
* @dev Emit an event for token metadata reveals/updates,\r
* according to EIP-4906.\r
*\r
* @param _fromTokenId The start token id.\r
* @param _toTokenId The end token id.\r
*/\r
event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);\r
\r
/**\r
* @dev Emit an event when the URI for the collection-level metadata\r
* is updated.\r
*/\r
event TokenURIUpdated(string newTokentURI);\r
\r
/**\r
* @dev Emit an event when the URI for the collection-level metadata\r
* is updated.\r
*/\r
event ContractURIUpdated(string newContractURI);\r
\r
/**\r
* @dev Emit an event when Metadata is Frozen.\r
*/\r
event MetadataFrozen(); \r
\r
/**\r
* @dev Emit an event when the royalties info is updated.\r
*/\r
event RoyaltyInfoUpdated(address receiver, uint256 bps);\r
\r
/**\r
* @dev Emit an event when Admin Role is granted.\r
*/\r
event AdminRoleGranted(address account);\r
\r
/**\r
* @dev Emit an event when an Ownership transfer is initialized.\r
*/\r
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\r
\r
/**\r
* @dev Emit an event when MasterHunter position is claimed.\r
*/\r
event NewMasterHunter(address account);\r
\r
/**\r
* @dev Event to log Withdrawal.\r
*/\r
event Withdrawal(address account, uint256 amount);\r
}"
},
"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
},
"lib/openzeppelin-contracts/contracts/interfaces/IERC2981.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)
pragma solidity ^0.8.0;
import "../utils/introspection/IERC165.sol";
/**
* @dev Interface for the NFT Royalty Standard.
*
* A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
* support for royalty payments across all NFT marketplaces and ecosystem participants.
*
* _Available since v4.5._
*/
interface IERC2981 is IERC165 {
/**
* @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
* exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
*/
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
returns (address receiver, uint256 royaltyAmount);
}
"
},
"@openzeppelin/contracts/token/common/ERC2981.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/common/ERC2981.sol)
pragma solidity ^0.8.20;
import {IERC2981} from "../../interfaces/IERC2981.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
/**
* @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information.
*
* Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for
* specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first.
*
* Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the
* fee is specified in basis points by default.
*
* IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See
* https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the ERC. Marketplaces are expected to
* voluntarily pay royalties together with sales, but note that this standard is not yet widely supported.
*/
abstract contract ERC2981 is IERC2981, ERC165 {
struct RoyaltyInfo {
address receiver;
uint96 royaltyFraction;
}
RoyaltyInfo internal _defaultRoyaltyInfo;
mapping(uint256 tokenId => RoyaltyInfo) internal _tokenRoyaltyInfo;
/**
* @dev The default royalty set is invalid (eg. (numerator / denominator) >= 1).
*/
error ERC2981InvalidDefaultRoyalty(uint256 numerator, uint256 denominator);
/**
* @dev The default royalty receiver is invalid.
*/
error ERC2981InvalidDefaultRoyaltyReceiver(address receiver);
/**
* @dev The royalty set for a specific `tokenId` is invalid (eg. (numerator / denominator) >= 1).
*/
error ERC2981InvalidTokenRoyalty(uint256 tokenId, uint256 numerator, uint256 denominator);
/**
* @dev The royalty receiver for `tokenId` is invalid.
*/
error ERC2981InvalidTokenRoyaltyReceiver(uint256 tokenId, address receiver);
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId);
}
/// @inheritdoc IERC2981
function royaltyInfo(
uint256 tokenId,
uint256 salePrice
) public view virtual returns (address receiver, uint256 amount) {
RoyaltyInfo storage _royaltyInfo = _tokenRoyaltyInfo[tokenId];
address royaltyReceiver = _royaltyInfo.receiver;
uint96 royaltyFraction = _royaltyInfo.royaltyFraction;
if (royaltyReceiver == address(0)) {
royaltyReceiver = _defaultRoyaltyInfo.receiver;
royaltyFraction = _defaultRoyaltyInfo.royaltyFraction;
}
uint256 royaltyAmount = (salePrice * royaltyFraction) / _feeDenominator();
return (royaltyReceiver, royaltyAmount);
}
/**
* @notice Returns the address that receives royalties.
*/
function royaltyAddress() external view returns (address) {
return _defaultRoyaltyInfo.receiver;
}
/**
* @notice Returns the royalty basis points out of 10_000.
*/
function royaltyBasisPoints() external view returns (uint256) {
return _defaultRoyaltyInfo.royaltyFraction;
}
/**
* @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a
* fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an
* override.
*/
function _feeDenominator() internal pure virtual returns (uint96) {
return 10000;
}
/**
* @dev Sets the royalty information that all ids in this contract will default to.
*
* Requirements:
*
* - `receiver` cannot be the zero address.
* - `feeNumerator` cannot be greater than the fee denominator.
*/
function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
uint256 denominator = _feeDenominator();
if (feeNumerator > denominator) {
// Royalty fee will exceed the sale price
revert ERC2981InvalidDefaultRoyalty(feeNumerator, denominator);
}
if (receiver == address(0)) {
revert ERC2981InvalidDefaultRoyaltyReceiver(address(0));
}
_defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
}
/**
* @dev Removes default royalty information.
*/
function _deleteDefaultRoyalty() internal virtual {
delete _defaultRoyaltyInfo;
}
/**
* @dev Sets the royalty information for a specific token id, overriding the global default.
*
* Requirements:
*
* - `receiver` cannot be the zero address.
* - `feeNumerator` cannot be greater than the fee denominator.
*/
function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual {
uint256 denominator = _feeDenominator();
if (feeNumerator > denominator) {
// Royalty fee will exceed the sale price
revert ERC2981InvalidTokenRoyalty(tokenId, feeNumerator, denominator);
}
if (receiver == address(0)) {
revert ERC2981InvalidTokenRoyaltyReceiver(tokenId, address(0));
}
_tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
}
/**
* @dev Resets royalty information for the token id back to the global default.
*/
function _resetTokenRoyalty(uint256 tokenId) internal virtual {
delete _tokenRoyaltyInfo[tokenId];
}
}
"
},
"src/interfaces/ITransferValidator.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.17;\r
\r
interface ITransferValidator721 {\r
/// @notice Ensure that a transfer has been authorized for a specific tokenId\r
function validateTransfer(\r
address caller,\r
address from,\r
address to,\r
uint256 tokenId\r
) external view;\r
}\r
\r
interface ITransferValidator1155 {\r
/// @notice Ensure that a transfer has been authorized for a specific amount of a specific tokenId, and reduce the transferable amount remaining\r
function validateTransfer(\r
address caller,\r
address from,\r
address to,\r
uint256 tokenId,\r
uint256 amount\r
) external;\r
}\r
"
},
"src/interfaces/ICreatorToken.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.4;\r
\r
interface ICreatorToken {\r
event TransferValidatorUpdated(address oldValidator, address newValidator);\r
\r
function getTransferValidator() external view returns (address validator);\r
\r
function getTransferValidationFunction()\r
external\r
view\r
returns (bytes4 functionSignature, bool isViewFunction);\r
\r
function setTransferValidator(address validator) external;\r
}\r
\r
interface ILegacyCreatorToken {\r
event TransferValidatorUpdated(address oldValidator, address newValidator);\r
\r
function getTransferValidator() external view returns (address validator);\r
\r
function setTransferValidator(address validator) external;\r
}\r
"
},
"src/lib/ERC721TransferValidator.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.17;\r
\r
import { ICreatorToken } from "../interfaces/ICreatorToken.sol";\r
\r
/**\r
* @title ERC721TransferValidator\r
* @notice Functionality to use a transfer validator.\r
*/\r
abstract contract ERC721TransferValidator is ICreatorToken {\r
/// @dev Store the transfer validator. The null address means no transfer validator is set.\r
address internal _transferValidator;\r
\r
/// @notice Revert with an error if the transfer validator is being set to the same address.\r
error SameTransferValidator();\r
\r
/// @notice Returns the currently active transfer validator.\r
/// The null address means no transfer validator is set.\r
function getTransferValidator() external view returns (address) {\r
return _transferValidator;\r
}\r
\r
/// @notice Set the transfer validator.\r
/// The external method that uses this must include access control.\r
function _setTransferValidator(address newValidator) internal {\r
address oldValidator = _transferValidator;\r
if (oldValidator == newValidator) {\r
revert SameTransferValidator();\r
}\r
_transferValidator = newValidator;\r
emit TransferValidatorUpdated(oldValidator, newValidator);\r
}\r
}\r
"
},
"lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonRee
Submitted on: 2025-10-21 10:30:27
Comments
Log in to comment.
No comments yet.