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": {
"src/KingdomlyNFTStandardV3.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol";
import {Context as OZContext} from "@openzeppelin/contracts/utils/Context.sol";
import {ERC721AC} from "@creator-token-standards/src/erc721c/ERC721AC.sol";
import {IDelegateRegistry} from "src/utils/IDelegateRegistry.sol";
import {IKingdomlyFeeContract} from "src/utils/IKingdomlyFeeContract.sol";
import {IDelegateRegistry} from "src/utils/IDelegateRegistry.sol";
import {IKingdomlyFeeContract} from "src/utils/IKingdomlyFeeContract.sol";
contract KingdomlyNFTStandardV3 is ERC721AC, ERC2981, Ownable, ReentrancyGuard {
struct BaseVariables {
string name;
string symbol;
address ownerPayoutAddress;
string initialBaseURI;
uint256 maxSupply;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:´:°•.°*/
/* ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•.•°:°.*/
error KingdomlyNFTStandardV3__MintInactive();
error KingdomlyNFTStandardV3__MintOnBehalfNotActive();
error KingdomlyNFTStandardV3__AddressZero();
error KingdomlyNFTStandardV3__Unauthorized(address caller);
error KingdomlyNFTStandardV3__InvalidOperation(string reason);
error KingdomlyNFTStandardV3__ExceedsMaxSupply(uint256 requested, uint256 available);
error KingdomlyNFTStandardV3__InsufficientEther(uint256 required, uint256 provided);
error KingdomlyNFTStandardV3__ExceedsMaxPerWallet(uint256 requested, uint256 allowed);
error KingdomlyNFTStandardV3__ExceedsMintQuota(uint256 requested, uint256 allowed);
error KingdomlyNFTStandardV3__ExceedsMaxMintGroupSupply(uint256 requested, uint256 available);
error KingdomlyNFTStandardV3__MintGroupInactive(uint256 mintGroup);
error KingdomlyNFTStandardV3__NotInPresale(address caller, uint256 mintGroup);
error KingdomlyNFTStandardV3__MintGroupDoesNotExist(uint256 mintGroup);
error KingdomlyNFTStandardV3__ArrayLengthMismatch();
error KingdomlyNFTStandardV3__InvalidKingdomlyFeeContract();
error KingdomlyNFTStandardV3__InvalidKingdomlyAdminAddress();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:´:°•.°*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•.•°:°.*/
event BatchMetadataUpdate(uint256 indexed fromTokenId, uint256 indexed toTokenId);
event TokensMinted(address indexed recipient, uint256 amount, uint256 mintGroup);
event TokensDelegateMinted(address indexed vault, address indexed hotWallet, uint256 amount, uint256 mintGroup);
event SalePriceChanged(uint256 indexed mintGroup, uint256 newPrice);
event PublicMaxMintPerWalletChanged(uint256 newMaxMintPerWallet);
event PreSaleMintStatusChanged(bool status, uint256 mintGroupId);
event PreSaleMintScheduledStartTimestampChanged(uint256 timestamp, uint256 mintGroupId);
event KingdomlyFeeContractChanged(address feeContractAddress);
event KingdomlyAdminChanged(address kingdomlyAdmin);
event MintOnBehalfStatusChanged(bool status);
event CreatorFeeUpdated(uint256 newCreatorFee);
event MintFeeUpdated(uint256 newMintFee);
event MintQuotasAdded(uint256 mintGroup, uint256 numberOfAddresses);
event MintOnBehalfUpdated(bool newStatus);
event MintStatusUpdated(bool newStatus);
event MaxPerMintGroupUpdated(uint256 mintGroup, uint256 newMax);
event MintLiveTimestampUpdated(uint256 timestamp);
event OwnerPayoutAddressUpdated(address newOwnerPayoutAddress);
event DefaultRoyaltyUpdated(address receiver, uint96 feeNumerator);
event TokenRoyaltyUpdated(uint256 tokenId, address receiver, uint96 feeNumerator);
event NftsAirdropped(uint256 recipients, uint256 amount);
event MintFundsWithdrew();
event FeeFundsWithdrew();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:´:°•.°*/
/* STORAGE VARIABLES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•.•°:°.*/
uint256 private constant BASIS_POINTS = 10000;
address private constant DELEGATE_REGISTRY = 0x00000000000000447e69651d841bD8D104Bed493;
uint256 private immutable i_maxSupply;
address private immutable i_feeAddress;
uint256 private s_threeDollarsInCents;
uint256 private s_scheduledMintLiveTimestamp;
uint256 private s_creatorFeeBasisPoints = 300;
uint256 private s_publicMaxMintPerWallet;
uint256[] private s_activeMintGroups;
bool private s_contractMintLive;
bool private s_mintOnBehalfActive;
string private s_baseURI;
address private s_ownerPayoutAddress;
address private s_kingdomlyAdmin;
address private s_kingdomlyPriceOracleContract;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:´:°•.°*/
/* MAPPINGS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•.•°:°.*/
mapping(address => uint256) private s_pendingBalances;
mapping(uint256 => uint256) private s_mintPrice;
mapping(uint256 => uint256) private s_maxSupplyPerMintGroup;
mapping(uint256 => uint256) private s_mintGroupMints;
mapping(uint256 => uint256) private s_presaleScheduledStartTimestamp;
mapping(address => mapping(uint256 => uint256)) private s_addressMints;
mapping(uint256 => mapping(address => uint256)) private s_mintQuotas;
mapping(uint256 => bool) private s_contractPresaleActive;
constructor(
BaseVariables memory _baseVariables,
uint256[] memory _maxSupplyPerMintGroup,
uint256[] memory _mintPrice,
uint256 _publicMaxMintPerWallet,
uint96 _royaltyPercentage,
address _owner,
address _royaltyPayoutAddress
) ERC721AC(_baseVariables.name, _baseVariables.symbol) Ownable(_owner) {
if (_mintPrice.length != _maxSupplyPerMintGroup.length) {
revert KingdomlyNFTStandardV3__ArrayLengthMismatch();
}
uint256 totalMaxSupplyPerMintGroup = 0;
for (uint256 i = 0; i < _maxSupplyPerMintGroup.length; i++) {
totalMaxSupplyPerMintGroup += _maxSupplyPerMintGroup[i];
s_maxSupplyPerMintGroup[i] = _maxSupplyPerMintGroup[i];
s_mintPrice[i] = _mintPrice[i];
s_mintGroupMints[i] = 0;
s_activeMintGroups.push(i);
}
if (totalMaxSupplyPerMintGroup > _baseVariables.maxSupply) {
revert KingdomlyNFTStandardV3__InvalidOperation({
reason: "Max supply per mint group exceeds total max supply"
});
}
i_maxSupply = _baseVariables.maxSupply;
s_contractMintLive = false;
s_scheduledMintLiveTimestamp = 0;
s_baseURI = _baseVariables.initialBaseURI;
s_ownerPayoutAddress = _baseVariables.ownerPayoutAddress;
i_feeAddress = 0x10317Fa93da2a2e6d7B8e29D2BC2e6B95f2ECc84;
s_kingdomlyAdmin = 0x89695e9D4C489128471798766f1EE4b2a8AB5B5B;
s_kingdomlyPriceOracleContract = 0x65Db9966492C0A5aC0EF15c018C19eE383F7a8Cf;
s_publicMaxMintPerWallet = _publicMaxMintPerWallet;
/**
* Set default royalty for ERC2981
*/
_setDefaultRoyalty(_royaltyPayoutAddress, _royaltyPercentage);
s_threeDollarsInCents = 300;
}
/**
* @notice Mints NFTs to the caller for a specific mint group
* @param amount Amount of NFTs to be minted
* @param mintGroup Id of mint group
* @return totalCostWithFee Total cost of the mint for batch mint
*/
function batchMint(uint256 amount, uint256 mintGroup) external payable returns (uint256 totalCostWithFee) {
totalCostWithFee = _batchMint(msg.sender, amount, mintGroup, msg.sender);
emit TokensMinted(msg.sender, amount, mintGroup);
_refundExcessEther(totalCostWithFee);
}
/**
* @notice Mints NFTs on behalf of a vault using delegation
* @param amount Number of tokens to mint.
* @param mintGroup Identifier for the minting group or phase.
* @param vault The vault whose delegation is being exercised.
*/
function delegatedMint(uint256 amount, uint256 mintGroup, address vault)
external
payable
returns (uint256 totalCostWithFee)
{
// Verify delegation
if (!IDelegateRegistry(DELEGATE_REGISTRY).checkDelegateForContract(msg.sender, vault, address(this), "")) {
revert KingdomlyNFTStandardV3__Unauthorized(vault);
}
totalCostWithFee = _batchMint(vault, amount, mintGroup, msg.sender);
emit TokensDelegateMinted(vault, msg.sender, amount, mintGroup);
_refundExcessEther(totalCostWithFee);
}
/**
* @notice Mints NFTs to a specified receiver address
* @param amount The number of tokens to mint
* @param mintGroup The mint group ID
* @param receiver The address to mint the tokens to
*/
function mintOnBehalf(uint256 amount, uint256 mintGroup, address receiver)
external
payable
returns (uint256 totalCostWithFee)
{
if (!s_mintOnBehalfActive) {
revert KingdomlyNFTStandardV3__MintOnBehalfNotActive();
}
totalCostWithFee = _batchMint(receiver, amount, mintGroup, receiver);
emit TokensMinted(receiver, amount, mintGroup);
_refundExcessEther(totalCostWithFee);
}
/**
* @notice Allows the contract owner to withdraw the funds that have been paid into the contract
*/
function withdrawMintFunds() external nonReentrant {
_withdrawFor(s_ownerPayoutAddress);
_withdrawFor(i_feeAddress);
emit MintFundsWithdrew();
}
/**
* @notice Allows the fee address to withdraw their portion of the funds
*/
function withdrawFeeFunds() external nonReentrant {
_withdrawFor(i_feeAddress);
emit FeeFundsWithdrew();
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:´:°•.°*/
/* KINGDOMLY ADMIN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•.•°:°.*/
/**
* @notice Sets the creator fee basis points for Kingdomly
* @param _creatorFeeBasisPoints The new creator fee for kingdomly
*/
function setCreatorFee(uint256 _creatorFeeBasisPoints) external isKingdomlyAdmin {
if (_creatorFeeBasisPoints > BASIS_POINTS) {
revert KingdomlyNFTStandardV3__InvalidOperation({reason: "Creator fee cannot exceed 100%"});
}
s_creatorFeeBasisPoints = _creatorFeeBasisPoints;
emit CreatorFeeUpdated(_creatorFeeBasisPoints);
}
/**
* @notice Updates the Kingdomly price oracle contract address
* @param _kingdomlyPriceOracleContract address of new kingdomly price oracle contract
*/
function setNewKingdomlyPriceOracleContract(address _kingdomlyPriceOracleContract) external isKingdomlyAdmin {
if (_kingdomlyPriceOracleContract == address(0)) {
revert KingdomlyNFTStandardV3__InvalidKingdomlyFeeContract();
}
s_kingdomlyPriceOracleContract = _kingdomlyPriceOracleContract;
emit KingdomlyFeeContractChanged(address(_kingdomlyPriceOracleContract));
}
/**
* @notice Updates the Kingdomly admin address
* @param _kingdomlyAdmin Address to be the new Kingdomly Admin
*/
function setNewKingdomlyAdmin(address _kingdomlyAdmin) external isKingdomlyAdmin {
if (_kingdomlyAdmin == address(0)) {
revert KingdomlyNFTStandardV3__InvalidKingdomlyAdminAddress();
}
s_kingdomlyAdmin = _kingdomlyAdmin;
emit KingdomlyAdminChanged(address(_kingdomlyAdmin));
}
/**
* @notice Sets the fee amount in cents for minting operations
* @param _newCents New fee per mint
*/
function setThreeDollarsInCents(uint256 _newCents) external isKingdomlyAdmin {
s_threeDollarsInCents = _newCents;
emit MintFeeUpdated(_newCents);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:´:°•.°*/
/* OWNER FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•.•°:°.*/
/**
*
* @param addressToAdd Addresses to change mint quota
* @param mintGroup mint ID group of addresses to change mint quota
* @param _mintQuotas mint quotas address in array
* @notice Changed add to presale to set mint quota for individual addresses.
*/
function setMintQuota(address[] memory addressToAdd, uint256 mintGroup, uint256[] memory _mintQuotas)
external
onlyOwner
{
if (addressToAdd.length != _mintQuotas.length) {
revert KingdomlyNFTStandardV3__ArrayLengthMismatch();
}
if (!isMintGroupActive(mintGroup)) {
initializeNewMintGroup(mintGroup);
}
uint256 addressesToAddLength = addressToAdd.length;
for (uint256 i = 0; i < addressesToAddLength; i++) {
s_mintQuotas[mintGroup][addressToAdd[i]] = _mintQuotas[i];
}
emit MintQuotasAdded(mintGroup, addressesToAddLength);
}
/**
* @notice Sets the maximum number of tokens that can be minted in a batch of the public mint group
*/
function setPublicMaxMintPerWallet(uint256 _newPublicMaxMintPerWallet) external onlyOwner {
s_publicMaxMintPerWallet = _newPublicMaxMintPerWallet;
emit PublicMaxMintPerWalletChanged(_newPublicMaxMintPerWallet);
}
/**
* @notice Sets the mint on behalf status
* @param status new mint on behalf status
*/
function setMintOnBehalfStatus(bool status) external onlyOwner {
s_mintOnBehalfActive = status;
emit MintOnBehalfUpdated(status);
}
/**
* @notice Changes the price to mint a token of a specific mint group id
* @param newMintPrice New mint Price
* @param mintGroup Mint ID to change price
*/
function changeSalePrice(uint256 newMintPrice, uint256 mintGroup) external onlyOwner {
//Checks if mintGroup already exists inside activeMintGroups. This allows the contract to adjust the mappings for new mint groups
if (!isMintGroupActive(mintGroup)) {
initializeNewMintGroup(mintGroup);
}
s_mintPrice[mintGroup] = newMintPrice;
emit SalePriceChanged(mintGroup, newMintPrice);
}
/**
* @notice Changes the minting status. Only the contract owner can call this function.
* @param status new status of mint
*/
function changeMintStatus(bool status) external onlyOwner {
if (s_contractMintLive == status) {
revert KingdomlyNFTStandardV3__InvalidOperation({reason: "Mint status is already the one you entered"});
}
if (!status) {
s_scheduledMintLiveTimestamp = 0;
}
s_contractMintLive = status;
emit MintStatusUpdated(status);
}
/**
* @notice Changes the max mint per mint group
* @param mintGroup mint ID of the group to change max mint
* @param newMax the new max mint per mint group
*/
function setNewMaxPerMintGroup(uint256 mintGroup, uint256 newMax) external onlyOwner {
//Checks if mintGroup already exists inside activeMintGroups. This allows the contract to adjust the mappings for new mint groups
if (!isMintGroupActive(mintGroup)) {
initializeNewMintGroup(mintGroup);
}
// Checker if new max exceeds total supply
uint256 totalMaxMintPerMG = 0;
uint256 activeMintGroupsLength = s_activeMintGroups.length;
for (uint256 i = 0; i < activeMintGroupsLength; i++) {
if (s_activeMintGroups[i] == mintGroup) {
totalMaxMintPerMG += newMax; // Use the new max for the specified mintGroup
} else {
totalMaxMintPerMG += s_maxSupplyPerMintGroup[s_activeMintGroups[i]];
}
}
if (totalMaxMintPerMG > i_maxSupply) {
revert KingdomlyNFTStandardV3__InvalidOperation({reason: "New supply per mint group exceeds total supply."});
}
s_maxSupplyPerMintGroup[mintGroup] = newMax;
emit MaxPerMintGroupUpdated(mintGroup, newMax);
}
/**
* @notice Starts or stops presale minting for a specific mint group
* @param presaleStatus status for presale
* @param mintGroup mintGroup to change presale status
*/
function stopOrStartpresaleMint(bool presaleStatus, uint256 mintGroup) external onlyOwner {
// Checks if mintGroup already exists inside activeMintGroups.
if (!isMintGroupActive(mintGroup)) {
revert KingdomlyNFTStandardV3__MintGroupDoesNotExist({mintGroup: mintGroup});
}
s_contractPresaleActive[mintGroup] = presaleStatus;
if (!presaleStatus) {
s_presaleScheduledStartTimestamp[mintGroup] = 0;
}
emit PreSaleMintStatusChanged(presaleStatus, mintGroup);
}
/**
* @notice Schedules when presale minting will start for a specific mint group
* @param startTimestamp timestamp of the mintGroup to start
* @param mintGroup mint ID to change start presale timestamp
*/
function schedulePresaleMintStart(uint256 startTimestamp, uint256 mintGroup) external onlyOwner {
if (!isMintGroupActive(mintGroup)) {
revert KingdomlyNFTStandardV3__MintGroupDoesNotExist({mintGroup: mintGroup});
}
s_presaleScheduledStartTimestamp[mintGroup] = startTimestamp;
emit PreSaleMintScheduledStartTimestampChanged(startTimestamp, mintGroup);
}
/**
* @notice Sets the base URI for the token metadata. Only the contract owner can call this function.
* @param newBaseURI the newBASEURI for the token metadata
*/
function setBaseURI(string memory newBaseURI) external onlyOwner {
s_baseURI = newBaseURI;
emit BatchMetadataUpdate(1, type(uint256).max);
}
/**
* @notice Sets the timestamp when minting should begin
* @param timestamp The Unix timestamp when minting should begin
*/
function setMintLiveTimestamp(uint256 timestamp) external onlyOwner {
s_scheduledMintLiveTimestamp = timestamp;
emit MintLiveTimestampUpdated(timestamp);
}
/**
* @notice Updates the owner's payout address and transfers pending balance to new address
* @dev Transfers existing balance from old to new address, then updates ownerPayoutAddress
* @param newOwnerPayoutAddress The new address for owner payouts
*/
function setOwnerPayoutAddress(address newOwnerPayoutAddress) external onlyOwner {
if (newOwnerPayoutAddress == address(0)) {
revert KingdomlyNFTStandardV3__AddressZero();
}
uint256 ownerBalance = s_pendingBalances[s_ownerPayoutAddress];
s_pendingBalances[newOwnerPayoutAddress] += ownerBalance;
s_pendingBalances[s_ownerPayoutAddress] = 0;
s_ownerPayoutAddress = newOwnerPayoutAddress;
emit OwnerPayoutAddressUpdated(newOwnerPayoutAddress);
}
/**
* @notice Sets the default royalty recipient and fee percentage for all tokens
* @dev Calls internal _setDefaultRoyalty function. Only callable by owner
* @param receiver Address that will receive royalty payments
* @param feeNumerator Royalty fee as basis points (e.g., 250 = 2.5%)
*/
function setDefaultRoyalty(address receiver, uint96 feeNumerator) external onlyOwner {
_setDefaultRoyalty(receiver, feeNumerator);
emit DefaultRoyaltyUpdated(receiver, feeNumerator);
}
/**
* @notice Sets royalty recipient and fee for a specific token, overriding default royalty
* @dev Calls internal _setTokenRoyalty function. Only callable by owner
* @param tokenId The token ID to set specific royalty for
* @param receiver Address that will receive royalty payments for this token
* @param feeNumerator Royalty fee as basis points (e.g., 250 = 2.5%)
*/
function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) external onlyOwner {
_setTokenRoyalty(tokenId, receiver, feeNumerator);
emit TokenRoyaltyUpdated(tokenId, receiver, feeNumerator);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:´:°•.°*/
/* AIRDROP FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•.•°:°.*/
/**
* @notice Airdrops NFTs to multiple recipients with a fixed fee charged to the owner
* @param recipients Recipent to receive NFTs
* @param amounts the number of NFTs to airdrop per recipient
* @dev Modified airdrop function to charge the owner threeDollarsEth per mint
*/
function airdropNFTs(address[] memory recipients, uint256[] memory amounts)
external
payable
onlyOwner
returns (uint256 totalCharge)
{
uint256 amountLengths = amounts.length;
if (recipients.length != amountLengths) {
revert KingdomlyNFTStandardV3__InvalidOperation({reason: "Mismatch between recipients and amounts"});
}
uint256 totalNFTToMint = 0;
for (uint256 i = 0; i < amountLengths; i++) {
totalNFTToMint += amounts[i];
}
if (totalSupply() + totalNFTToMint > i_maxSupply) {
revert KingdomlyNFTStandardV3__InvalidOperation({reason: "Airdrop exceeds max supply"});
}
totalCharge = quoteAirdropFees(totalNFTToMint);
if (msg.value < totalCharge) {
revert KingdomlyNFTStandardV3__InvalidOperation({reason: "Not enough Ether sent for the airdrop charge"});
}
s_pendingBalances[i_feeAddress] += totalCharge; // Fee goes to the fee address
uint256 recipientsLength = recipients.length;
for (uint256 j = 0; j < recipientsLength; j++) {
uint256 nftAmount = amounts[j];
_safeMint(recipients[j], nftAmount); // Mint NFTs to recipients
}
emit NftsAirdropped(recipientsLength, totalNFTToMint);
_refundExcessEther(totalCharge);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:´:°•.°*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•.•°:°.*/
/**
* @dev Ensures the caller is the Kingdomly Admin.
*/
modifier isKingdomlyAdmin() {
if (msg.sender != s_kingdomlyAdmin) {
revert KingdomlyNFTStandardV3__Unauthorized(msg.sender);
}
_;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:´:°•.°*/
/* VIEW FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•.•°:°.*/
function maxSupply() external view returns (uint256) {
return i_maxSupply;
}
function threeDollarsInCents() external view returns (uint256) {
return s_threeDollarsInCents;
}
function scheduledMintLiveTimestamp() external view returns (uint256) {
return s_scheduledMintLiveTimestamp;
}
function creatorFeeBasisPoints() external view returns (uint256) {
return s_creatorFeeBasisPoints;
}
function publicMaxMintPerWallet() external view returns (uint256) {
return s_publicMaxMintPerWallet;
}
function activeMintGroups() external view returns (uint256[] memory) {
return s_activeMintGroups;
}
function contractMintLive() external view returns (bool) {
return s_contractMintLive;
}
function mintOnBehalfActive() external view returns (bool) {
return s_mintOnBehalfActive;
}
function baseURI() external view returns (string memory) {
return s_baseURI;
}
function feeAddress() external view returns (address) {
return i_feeAddress;
}
function ownerPayoutAddress() external view returns (address) {
return s_ownerPayoutAddress;
}
function kingdomlyAdmin() external view returns (address) {
return s_kingdomlyAdmin;
}
function kingdomlyPriceOracleContract() external view returns (address) {
return s_kingdomlyPriceOracleContract;
}
function delegateRegistry() external pure returns (address) {
return DELEGATE_REGISTRY;
}
function mintPrice(uint256 mintGroup) external view returns (uint256) {
return s_mintPrice[mintGroup];
}
function maxSupplyPerMintGroup(uint256 mintGroup) external view returns (uint256) {
return s_maxSupplyPerMintGroup[mintGroup];
}
function mintGroupMints(uint256 mintGroup) external view returns (uint256) {
return s_mintGroupMints[mintGroup];
}
function presaleScheduledStartTimestamp(uint256 mintGroup) external view returns (uint256) {
return s_presaleScheduledStartTimestamp[mintGroup];
}
function mintQuotas(uint256 mintGroup, address minter) external view returns (uint256) {
return s_mintQuotas[mintGroup][minter];
}
function contractPresaleActive(uint256 mintGroup) external view returns (bool) {
return s_contractPresaleActive[mintGroup];
}
/**
* @notice Checks if a mint operation is valid for the given parameters
* @param amount Amount of ERC
* @param mintGroup mintGroup to check if eligible to mint
* @param minterAddress Address to mint
*/
function canMintCheck(uint256 amount, uint256 mintGroup, address minterAddress)
external
view
returns (bool, string memory)
{
if (amount == 0) {
return (false, "InvalidOperation");
}
// Pre-conditions checks
if (!mintLive()) {
return (false, "MintInactive");
}
if (!s_contractPresaleActive[mintGroup]) {
if (
s_presaleScheduledStartTimestamp[mintGroup] == 0
|| block.timestamp <= s_presaleScheduledStartTimestamp[mintGroup]
) {
return (false, "MintGroupInactive");
}
}
if (mintGroup != 0) {
if (s_mintQuotas[mintGroup][minterAddress] == 0) {
return (false, "NotInPresale");
}
if (amount > s_mintQuotas[mintGroup][minterAddress]) {
return (false, "ExceedsMintQuota");
}
} else {
if (amount + s_addressMints[minterAddress][mintGroup] > s_publicMaxMintPerWallet) {
return (false, "ExceedsPublicMaxMintPerWallet");
}
}
if (s_mintGroupMints[mintGroup] + amount > s_maxSupplyPerMintGroup[mintGroup]) {
return (false, "ExceedsMaxMintGroupSupply");
}
if (totalSupply() + amount > i_maxSupply) {
return (false, "ExceedsMaxSupply");
}
return (true, "");
}
/**
* @notice Checks if a delegated mint operation is valid for the given parameters
* @param amount amount of NFTs to be minted
* @param mintGroup mint group id to check mint eligibility
* @param vault Vault address with mint quota
* @param minterAddress Minter address that vault delgated to
*/
function canDelegateMintCheck(uint256 amount, uint256 mintGroup, address vault, address minterAddress)
external
view
returns (bool, string memory)
{
if (!IDelegateRegistry(DELEGATE_REGISTRY).checkDelegateForContract(minterAddress, vault, address(this), "")) {
return (false, "Unauthorized");
}
if (amount == 0) {
return (false, "InvalidOperation");
}
// Pre-conditions checks
if (!mintLive()) {
return (false, "MintInactive");
}
if (!s_contractPresaleActive[mintGroup]) {
if (
s_presaleScheduledStartTimestamp[mintGroup] == 0
|| block.timestamp <= s_presaleScheduledStartTimestamp[mintGroup]
) {
return (false, "MintGroupInactive");
}
}
if (mintGroup != 0) {
if (s_mintQuotas[mintGroup][vault] == 0) {
return (false, "NotInPresale");
}
if (amount > s_mintQuotas[mintGroup][vault]) {
return (false, "ExceedsMintQuota");
}
} else {
if (amount + s_addressMints[vault][mintGroup] > s_publicMaxMintPerWallet) {
return (false, "ExceedsPublicMaxPerWallet");
}
}
if (s_mintGroupMints[mintGroup] + amount > s_maxSupplyPerMintGroup[mintGroup]) {
return (false, "ExceedsMaxMintGroupSupply");
}
if (totalSupply() + amount > i_maxSupply) {
return (false, "ExceedsMaxSupply");
}
return (true, "");
}
/**
* @notice Quote the total cost of minting a batch of tokens
* @dev This is the same price for both the owner and the delegate
* @param mintGroup The mint group ID
* @param amount The number of tokens to mint
* @return totalCostWithFee The total cost of minting the batch, including the minter fee
* @return mintingCost The base cost of minting without any fees
* @return minterFee The fixed $3 fee per token minted
* @return creatorFee The 3% fee calculated from the base minting cost
*/
function quoteBatchMint(uint256 mintGroup, uint256 amount)
public
view
returns (uint256 totalCostWithFee, uint256 mintingCost, uint256 minterFee, uint256 creatorFee)
{
mintingCost = s_mintPrice[mintGroup] * amount;
minterFee = threeDollarsEth() * amount;
creatorFee = (mintingCost * s_creatorFeeBasisPoints) / BASIS_POINTS;
totalCostWithFee = mintingCost + minterFee + creatorFee;
}
/**
* @notice Checks the balance pending withdrawal for the sender.
*/
function checkPendingBalance() external view returns (uint256) {
return s_pendingBalances[msg.sender];
}
/**
* @notice Checks the balance pending withdrawal for the user.
*/
function checkPendingBalanceFor(address user) external view returns (uint256) {
return s_pendingBalances[user];
}
/**
* @notice Returns whether this contract supports the given interface
*/
function supportsInterface(bytes4 interfaceId) public view override(ERC721AC, ERC2981) returns (bool) {
return ERC721AC.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId);
}
/**
* @notice Checks if a mint group ID is active
*/
function isMintGroupActive(uint256 mintGroup) private view returns (bool) {
uint256 activeMintGroupsLength = s_activeMintGroups.length;
for (uint256 i = 0; i < activeMintGroupsLength; i++) {
if (s_activeMintGroups[i] == mintGroup) {
return true;
}
}
return false;
}
/**
* @notice Returns whether minting is currently live
*/
function mintLive() public view returns (bool) {
if (!s_contractMintLive) {
if (s_scheduledMintLiveTimestamp == 0 || block.timestamp <= s_scheduledMintLiveTimestamp) {
return false;
}
}
return true;
}
/**
* @notice Returns the current cost of $3 in ETH using the price oracle
*/
function threeDollarsEth() public view returns (uint256) {
uint256 oneDollarInWei = IKingdomlyFeeContract(s_kingdomlyPriceOracleContract).getOneDollarInWei();
return (oneDollarInWei * s_threeDollarsInCents) / 100;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
if (bytes(s_baseURI).length == 0) {
return "";
}
if (bytes(s_baseURI)[bytes(s_baseURI).length - 1] != bytes("/")[0]) {
return s_baseURI;
}
return string(abi.encodePacked(s_baseURI, _toString(tokenId)));
}
/**
* @notice Returns the creator fee percentage
*/
function getCreatorFeePercentage() external view returns (uint256) {
return (s_creatorFeeBasisPoints * 100) / BASIS_POINTS;
}
/**
* @notice Calculates the total cost for airdropping a given amount of tokens
* @param amount The number of tokens to mint
* @return totalAirdropCostWithFee The total cost of minting the batch ($0.33 per nft)
*/
function quoteAirdropFees(uint256 amount) public view returns (uint256 totalAirdropCostWithFee) {
totalAirdropCostWithFee = (threeDollarsEth() * amount * 11) / 100;
}
/**
* @notice Checker whether presale is already active both on timestmap and the mapping
*/
function presaleActive(uint256 mintGroup) external view returns (bool) {
if (!s_contractPresaleActive[mintGroup]) {
if (
s_presaleScheduledStartTimestamp[mintGroup] == 0
|| block.timestamp <= s_presaleScheduledStartTimestamp[mintGroup]
) {
return false;
}
}
return true;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:´:°•.°*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•.•°:°.*/
/**
* @notice Initializes a new mint group with default values
*/
function initializeNewMintGroup(uint256 mintGroup) internal {
s_mintPrice[mintGroup] = 0;
s_maxSupplyPerMintGroup[mintGroup] = 0;
s_mintGroupMints[mintGroup] = 0;
s_activeMintGroups.push(mintGroup);
}
/**
* @notice Internal function to handle batch minting logic
*/
function _batchMint(address quotaHolder, uint256 amount, uint256 mintGroup, address receiver)
internal
returns (uint256)
{
if (amount == 0) {
revert KingdomlyNFTStandardV3__InvalidOperation({reason: "Amount must be greater than 0"});
}
// Pre-conditions checks
if (!mintLive()) {
revert KingdomlyNFTStandardV3__MintInactive();
}
if (!s_contractPresaleActive[mintGroup]) {
if (
s_presaleScheduledStartTimestamp[mintGroup] == 0
|| block.timestamp <= s_presaleScheduledStartTimestamp[mintGroup]
) {
revert KingdomlyNFTStandardV3__MintGroupInactive({mintGroup: mintGroup});
}
}
// Checks required for non public mint
if (mintGroup != 0) {
if (s_mintQuotas[mintGroup][quotaHolder] == 0) {
revert KingdomlyNFTStandardV3__NotInPresale({caller: quotaHolder, mintGroup: mintGroup});
}
if (amount > s_mintQuotas[mintGroup][quotaHolder]) {
revert KingdomlyNFTStandardV3__ExceedsMintQuota({
requested: amount,
allowed: s_mintQuotas[mintGroup][quotaHolder]
});
}
s_mintQuotas[mintGroup][quotaHolder] -= amount;
} else {
if (amount + s_addressMints[quotaHolder][mintGroup] > s_publicMaxMintPerWallet) {
revert KingdomlyNFTStandardV3__ExceedsMaxPerWallet({
requested: amount,
allowed: s_publicMaxMintPerWallet - s_addressMints[quotaHolder][mintGroup]
});
}
}
// Check mint group supply limits
if (s_mintGroupMints[mintGroup] + amount > s_maxSupplyPerMintGroup[mintGroup]) {
revert KingdomlyNFTStandardV3__ExceedsMaxMintGroupSupply({
requested: amount,
available: s_maxSupplyPerMintGroup[mintGroup] - s_mintGroupMints[mintGroup]
});
}
// Check total supply limit
if (totalSupply() + amount > i_maxSupply) {
revert KingdomlyNFTStandardV3__ExceedsMaxSupply({requested: amount, available: i_maxSupply - totalSupply()});
}
// Calculate fees, check if we have enough msg.value
(uint256 totalCostWithFee, uint256 mintingCost, uint256 minterFee, uint256 creatorFee) =
quoteBatchMint(mintGroup, amount);
if (msg.value < totalCostWithFee) {
revert KingdomlyNFTStandardV3__InsufficientEther({required: totalCostWithFee, provided: msg.value});
}
uint256 totalFee = minterFee + creatorFee;
s_addressMints[quotaHolder][mintGroup] += amount;
// Update balances
s_pendingBalances[i_feeAddress] += totalFee;
s_pendingBalances[s_ownerPayoutAddress] += mintingCost - creatorFee;
// Finalize minting
s_mintGroupMints[mintGroup] += amount;
_safeMint(receiver, amount);
return totalCostWithFee;
}
/**
* @notice Internal function to withdraw funds for a specific user
*/
function _withdrawFor(address user) internal returns (uint256 payout) {
payout = s_pendingBalances[user];
s_pendingBalances[user] = 0;
(bool success,) = payable(user).call{value: payout}("");
if (!success) {
revert KingdomlyNFTStandardV3__InvalidOperation({reason: "Withdraw Transfer Failed"});
}
}
/**
* @notice Internal function to refund excess Ether sent in a transaction
*/
function _refundExcessEther(uint256 totalCharge) internal {
uint256 excess = msg.value - totalCharge;
if (excess > 0) {
(bool success,) = payable(msg.sender).call{value: excess}("");
if (!success) {
s_pendingBalances[msg.sender] += excess;
}
}
}
/**
* @notice Overrides the start token ID function from the ERC721A contract
*/
function _startTokenId() internal view virtual override returns (uint256) {
return 1;
}
/**
* @notice Returns the base URI for the token metadata
*/
function _baseURI() internal view override returns (string memory) {
return s_baseURI;
}
/**
* @notice Internal function to check if caller is contract owner
*/
function _requireCallerIsContractOwner() internal view virtual override {
_checkOwner();
}
/**
* @notice Returns the message data for the current call
*/
function _msgData() internal view override(OZContext) returns (bytes calldata) {
return super._msgData();
}
/**
* @notice Returns the sender of the current call
*/
function _msgSender() internal view override(OZContext) returns (address) {
return super._msgSender();
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @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 EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* 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 nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"lib/openzeppelin-contracts/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 private _defaultRoyaltyInfo;
mapping(uint256 tokenId => RoyaltyInfo) private _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);
}
/**
* @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];
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"lib/creator-token-standards/src/erc721c/ERC721AC.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../utils/AutomaticValidatorTransferApproval.sol";
import "../utils/CreatorTokenBase.sol";
import "erc721a/contracts/ERC721A.sol";
import {TOKEN_TYPE_ERC721} from "@limitbreak/permit-c/Constants.sol";
/**
* @title ERC721AC
* @author Limit Break, Inc.
* @notice Extends Azuki's ERC721-A implementation with Creator Token functionality, which
* allows the contract owner to update the transfer validation logic by managing a security policy in
* an external transfer validation security policy registry. See {CreatorTokenTransferValidator}.
*/
abstract contract ERC721AC is ERC721A, CreatorTokenBase, AutomaticValidatorTransferApproval {
constructor(string memory name_, string memory symbol_) CreatorTokenBase() ERC721A(name_, symbol_) {}
/**
* @notice Overrides behavior of isApprovedFor all such that if an operator is not explicitly approved
* for all, the contract owner can optionally auto-approve the 721-C transfer validator for transfers.
*/
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool isApproved) {
isApproved = super.isApprovedForAll(owner, operator);
if (!isApproved) {
if (autoApproveTransfersFromValidator) {
isApproved = operator == address(getTransferValidator());
}
}
}
/**
* @notice Indicates whether the contract implements the specified interface.
* @dev Overrides supportsInterface in ERC165.
* @param interfaceId The interface id
* @return true if the contract implements the specified interface, false otherwise
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(ICreatorToken).interfaceId ||
interfaceId == type(ICreatorTokenLegacy).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @notice Returns the function selector for the transfer validator's validation function to be called
* @notice for transaction simulation.
*/
function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) {
functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)"));
isViewFunction = true;
}
/// @dev Ties the erc721a _beforeTokenTransfers hook to more granular transfer validation logic
function _beforeTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 quantity
) internal virtual override {
for (uint256 i = 0; i < quantity;) {
_validateBeforeTransfer(from, to, startTokenId + i);
unchecked {
++i;
}
}
}
/// @dev Ties the erc721a _afterTokenTransfer hook to more granular transfer validation logic
function _afterTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 quantity
) internal virtual override {
for (uint256 i = 0; i < quantity;) {
_validateAfterTransfer(from, to, startTokenId + i);
unchecked {
++i;
}
}
}
function _msgSenderERC721A() internal view virtual override returns (address) {
return _msgSender();
}
function _tokenType() internal pure override returns(uint16) {
return uint16(TOKEN_TYPE_ERC721);
}
}
"
},
"src/utils/IDelegateRegistry.sol": {
"content": "// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.13;
/**
* @title IDelegateRegistry
* @custom:version 2.0
* @custom:author foobar (0xfoobar)
* @notice A standalone immutable registry storing delegated permissions from one address to another
*/
interface IDelegateRegistry {
/// @notice Delegation type, NONE is used when a delegation does not exist or is revoked
enum DelegationType {
NONE,
ALL,
CONTRACT,
ERC721,
ERC20,
ERC1155
}
/// @notice Struct for returning delegations
struct Delegation {
DelegationType type_;
address to;
address from;
bytes32 rights;
address contract_;
uint256 tokenId;
uint256 amount;
}
/// @notice Emitted when an address delegates or revokes rights for their entire wallet
event DelegateAll(address indexed from, address indexed to, bytes32 rights, bool enable);
/// @notice Emitted when an address delegates or revokes rights for a contract address
event DelegateContract(
address indexed from, address indexed to, address indexed contract_, bytes32 rights, bool enable
);
/// @notice Emitted when an address delegates or revokes rights for an ERC721 tokenId
event DelegateERC721(
address indexed from,
address indexed to,
address indexed contract_,
uint256 tokenId,
bytes32 rights,
bool enable
);
/// @notice Emitted when an address delegates or revokes rights for an amount of ERC20 tokens
event DelegateERC20(
address indexed from, address indexed to, address indexed contract_, bytes32 rights, uint256 amount
);
/// @notice Emitted when an address delegates or revokes rights for an amount of an ERC1155 tokenId
event DelegateERC1155(
address indexed from,
address indexed to,
address indexed contract_,
uint256 tokenId,
bytes32 rights,
uint256 amount
);
/// @notice Thrown if multicall calldata is malformed
error MulticallFailed();
/**
* ----------- WRITE -----------
*/
/**
* @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
* @param data The encoded function data for each of the calls to make to this contract
* @return results The results from each of the calls passed in via data
*/
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for all contracts
* @param to The address to act as delegate
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param enable Whether to enable or disable this delegation, true delegates and false revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateAll(address to, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific contract
* @param to The address to act as delegate
* @param contract_ The contract whose rights are being delegated
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param enable Whether to enable or disable this delegation, true delegates and false revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateContract(address to, address contract_, bytes32 rights, bool enable)
external
payable
returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific ERC721 token
* @param to The address to act as delegate
* @param contract_ The contract whose rights are being delegated
* @param tokenId The token id to delegate
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param enable Whether to enable or disable this delegation, true delegates and false revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable)
external
payable
returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC20 tokens
* @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
* @param to The address to act as delegate
* @param contract_ The address for the fungible token contract
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param amount The amount to delegate, > 0 delegates and 0 revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount)
external
payable
returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC1155 tokens
* @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
* @param to The address to act as delegate
* @param contract_ The address of the contract that holds the token
* @param tokenId The token id to delegate
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param amount The amount of that token id to delegate, > 0 delegates and 0 revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount)
external
payable
returns (bytes32 delegationHash);
/**
* ----------- CHECKS -----------
*/
/**
* @notice Check if `to` is a delegate of `from` for the entire wallet
* @param to The potential delegate address
* @param from The potential address who delegated rights
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return valid Whether delegate is granted to act on the from's behalf
*/
function checkDelegateForAll(address to, address from, bytes32 rights) external view returns (bool);
Submitted on: 2025-09-22 15:19:54
Comments
Log in to comment.
No comments yet.