// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
address constant CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS = 0x000000000000AAeB6D7670E522A718067333cd4E;
address constant CANONICAL_CORI_SUBSCRIPTION = 0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6;
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @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;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
pragma solidity ^0.8.0;
/**
* @title Counters
* @author Matt Condon (@shrugs)
* @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
*
* Include with `using Counters for Counters.Counter;`
*/
library Counters {
struct Counter {
// This variable should never be directly accessed by users of the library: interactions must be restricted to
// the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
// this feature: see https://github.com/ethereum/solidity/issues/4637
uint256 _value; // default: 0
}
function current(Counter storage counter) internal view returns (uint256) {
return counter._value;
}
function increment(Counter storage counter) internal {
unchecked {
counter._value += 1;
}
}
function decrement(Counter storage counter) internal {
uint256 value = counter._value;
require(value > 0, "Counter: decrement overflow");
unchecked {
counter._value = value - 1;
}
}
function reset(Counter storage counter) internal {
counter._value = 0;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
interface IOperatorFilterRegistry {
/**
* @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
* true if supplied registrant address is not registered.
*/
function isOperatorAllowed(address registrant, address operator) external view returns (bool);
/**
* @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
*/
function register(address registrant) external;
/**
* @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes.
*/
function registerAndSubscribe(address registrant, address subscription) external;
/**
* @notice Registers an address with the registry and copies the filtered operators and codeHashes from another
* address without subscribing.
*/
function registerAndCopyEntries(address registrant, address registrantToCopy) external;
/**
* @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner.
* Note that this does not remove any filtered addresses or codeHashes.
* Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes.
*/
function unregister(address addr) external;
/**
* @notice Update an operator address for a registered address - when filtered is true, the operator is filtered.
*/
function updateOperator(address registrant, address operator, bool filtered) external;
/**
* @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates.
*/
function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
/**
* @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered.
*/
function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
/**
* @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates.
*/
function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
/**
* @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous
* subscription if present.
* Note that accounts with subscriptions may go on to subscribe to other accounts - in this case,
* subscriptions will not be forwarded. Instead the former subscription's existing entries will still be
* used.
*/
function subscribe(address registrant, address registrantToSubscribe) external;
/**
* @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes.
*/
function unsubscribe(address registrant, bool copyExistingEntries) external;
/**
* @notice Get the subscription address of a given registrant, if any.
*/
function subscriptionOf(address addr) external returns (address registrant);
/**
* @notice Get the set of addresses subscribed to a given registrant.
* Note that order is not guaranteed as updates are made.
*/
function subscribers(address registrant) external returns (address[] memory);
/**
* @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant.
* Note that order is not guaranteed as updates are made.
*/
function subscriberAt(address registrant, uint256 index) external returns (address);
/**
* @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr.
*/
function copyEntriesOf(address registrant, address registrantToCopy) external;
/**
* @notice Returns true if operator is filtered by a given address or its subscription.
*/
function isOperatorFiltered(address registrant, address operator) external returns (bool);
/**
* @notice Returns true if the hash of an address's code is filtered by a given address or its subscription.
*/
function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
/**
* @notice Returns true if a codeHash is filtered by a given address or its subscription.
*/
function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
/**
* @notice Returns a list of filtered operators for a given address or its subscription.
*/
function filteredOperators(address addr) external returns (address[] memory);
/**
* @notice Returns the set of filtered codeHashes for a given address or its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredCodeHashes(address addr) external returns (bytes32[] memory);
/**
* @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
* its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredOperatorAt(address registrant, uint256 index) external returns (address);
/**
* @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
* its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
/**
* @notice Returns true if an address has registered
*/
function isRegistered(address addr) external returns (bool);
/**
* @dev Convenience method to compute the code hash of an arbitrary contract
*/
function codeHashOf(address addr) external returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {IOperatorFilterRegistry} from "./IOperatorFilterRegistry.sol";
import {CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS} from "./lib/Constants.sol";
/**
* @title OperatorFilterer
* @notice Abstract contract whose constructor automatically registers and optionally subscribes to or copies another
* registrant's entries in the OperatorFilterRegistry.
* @dev This smart contract is meant to be inherited by token contracts so they can use the following:
* - `onlyAllowedOperator` modifier for `transferFrom` and `safeTransferFrom` methods.
* - `onlyAllowedOperatorApproval` modifier for `approve` and `setApprovalForAll` methods.
* Please note that if your token contract does not provide an owner with EIP-173, it must provide
* administration methods on the contract itself to interact with the registry otherwise the subscription
* will be locked to the options set during construction.
*/
abstract contract OperatorFilterer {
/// @dev Emitted when an operator is not allowed.
error OperatorNotAllowed(address operator);
IOperatorFilterRegistry public constant OPERATOR_FILTER_REGISTRY =
IOperatorFilterRegistry(CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS);
/// @dev The constructor that is called when the contract is being deployed.
constructor(address subscriptionOrRegistrantToCopy, bool subscribe) {
// If an inheriting token contract is deployed to a network without the registry deployed, the modifier
// will not revert, but the contract will need to be registered with the registry once it is deployed in
// order for the modifier to filter addresses.
if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
if (subscribe) {
OPERATOR_FILTER_REGISTRY.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy);
} else {
if (subscriptionOrRegistrantToCopy != address(0)) {
OPERATOR_FILTER_REGISTRY.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy);
} else {
OPERATOR_FILTER_REGISTRY.register(address(this));
}
}
}
}
/**
* @dev A helper function to check if an operator is allowed.
*/
modifier onlyAllowedOperator(address from) virtual {
// Allow spending tokens from addresses with balance
// Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
// from an EOA.
if (from != msg.sender) {
_checkFilterOperator(msg.sender);
}
_;
}
/**
* @dev A helper function to check if an operator approval is allowed.
*/
modifier onlyAllowedOperatorApproval(address operator) virtual {
_checkFilterOperator(operator);
_;
}
/**
* @dev A helper function to check if an operator is allowed.
*/
function _checkFilterOperator(address operator) internal view virtual {
// Check registry code length to facilitate testing in environments without a deployed registry.
if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
// under normal circumstances, this function will revert rather than return false, but inheriting contracts
// may specify their own OperatorFilterRegistry implementations, which may behave differently
if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
revert OperatorNotAllowed(operator);
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../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.
*
* By default, the owner account will be the one that deploys the contract. 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;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @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 {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing 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 {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_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);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
// SPDX-License-Identifier: MIT
// @title Teiko Key for Teiko World
// @author TheV
// _________________________
// [... [......[........[..[.. [.. [.... [.. [.. [.... [....... [.. [..... /// __ __ \
// [.. [.. [..[.. [.. [.. [.. [.. [.. [.. [.. [.. [.. [.. [.. [.. ||| | | | | |
// [.. [.. [..[.. [.. [.. [.. [.. [. [..[.. [..[.. [.. [.. [.. [.. ||| |__| _ |__| |
// [.. [...... [..[. [. [.. [.. [.. [.. [..[.. [..[. [.. [.. [.. [.. \\\______ /_\ _____/
// [.. [.. [..[.. [.. [.. [.. [.. [. [.. [..[.. [..[.. [.. [.. [.. [.. \\\__________/
// [.. [.. [..[.. [.. [.. [.. [. [. [.... [.. [.. [.. [.. [.. [.. [.. ||| |
// [.. [........[..[.. [.. [.... [.. [.. [.... [.. [..[........[..... \\\_______/
pragma solidity >=0.7.0;
import "https://github.com/chiru-labs/ERC721A/contracts/ERC721A.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "operator_filter/OperatorFilterer.sol";
import {CANONICAL_CORI_SUBSCRIPTION} from "operator_filter/lib/Constants.sol";
contract TeikoKey is ERC721A, Ownable, OperatorFilterer
{
//////////////////////////////////////////////////////////////////
// Library
//////////////////////////////////////////////////////////////////
using Counters for Counters.Counter;
//////////////////////////////////////////////////////////////////
// Enums
//////////////////////////////////////////////////////////////////
enum MintPhase
{
CLOSED,
WHITELIST,
ALLOWLIST,
PUBLIC
}
enum KeyType
{
REGULAR,
ROBOT,
ZOMBIE,
APE,
SPIRIT,
GOLD
}
//////////////////////////////////////////////////////////////////
// Attributes
//////////////////////////////////////////////////////////////////
Counters.Counter private _tokenIds;
MintPhase public currentMintPhase = MintPhase.CLOSED;
uint256 public constant MAX_SUPPLY = 2008;
bytes32 constant public whitelistRole = keccak256("whitelisted");
bytes32 constant public allowlistRole = keccak256("allowlisted");
uint8 public maxMintWhitelist = 2;
uint8 public maxMintAllowlist = 1;
uint8 public maxMintPublic = 1;
address public mainAddress = 0x9Cc8C097251d71f68f674c0f4d2c86fB170e7BCD;
address public signerAddress = 0x8fD261f08991619c2c0ad36B3a73E8f874BB5372;
string public baseURI = "https://ipfs.io/ipns/k51qzi5uqu5djzdmli4l9utu46cvd35sn9plo3u50pkam7gtqw3ifq0crypp0p";
uint256 public price = 0 ether;
mapping (address => uint256) public mints;
mapping (uint256 => uint8) public keyPercents;
mapping(address => int8) public approvedCallers;
mapping(uint256 => string) public tokenURIs;
mapping(uint256 => KeyType) public keyTypes;
mapping(KeyType => uint16) public maxCountKeyTypes;
mapping(KeyType => uint8) public currentCountKeyTypes;
//////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////
constructor() ERC721A("Teiko Key", "TEIKOKEY") OperatorFilterer(CANONICAL_CORI_SUBSCRIPTION, false)
{
maxCountKeyTypes[KeyType.REGULAR] = 1083;
maxCountKeyTypes[KeyType.ROBOT] = 450;
maxCountKeyTypes[KeyType.ZOMBIE] = 300;
maxCountKeyTypes[KeyType.APE] = 120;
maxCountKeyTypes[KeyType.SPIRIT] = 50;
maxCountKeyTypes[KeyType.GOLD] = 5;
}
//////////////////////////////////////////////////////////////////
// Modifiers
//////////////////////////////////////////////////////////////////
modifier onlyApprovedOrOwner(address addr)
{
require(approvedCallers[addr] == 1 || addr == owner(), "Caller is not approved nor owner");
_;
}
modifier onlyOwnerOfOrApproved(address addr, uint256 tokenId)
{
require(ownerOf(tokenId) == addr || approvedCallers[addr] == 1, "Caller is not owner of that token id nor approved");
_;
}
//////////////////////////////////////////////////////////////////
// OperatorFilter functions
//////////////////////////////////////////////////////////////////
/**
* @notice Registers self with the operator filter registry
*/
function register()
external onlyOwner
{
OPERATOR_FILTER_REGISTRY.register(address(this));
}
/**
* @notice Unregisters self from the operator filter registry
*/
function unregister()
external onlyOwner
{
OPERATOR_FILTER_REGISTRY.unregister(address(this));
}
/**
* @notice Registers self with the operator filter registry, and susbscribe to
the filtered operators of the given subscription
*/
function registerAndSubscribe(address subscription)
external onlyOwner
{
OPERATOR_FILTER_REGISTRY.registerAndSubscribe(address(this), subscription);
}
/**
* @notice Registers self with the operator filter registry, and copy
the filtered operators of the given subscription
*/
function registerAndCopyEntries(address registrantToCopy)
external onlyOwner
{
OPERATOR_FILTER_REGISTRY.registerAndCopyEntries(address(this), registrantToCopy);
}
/**
* @notice Update given operator address to filtered/unfiltered state
*/
function updateOperator(address operator, bool filtered)
external onlyOwner
{
OPERATOR_FILTER_REGISTRY.updateOperator(address(this), operator, filtered);
}
/**
* @notice Update given operator smart contract code hash to filtered/unfiltered state
*/
function updateCodeHash(bytes32 codeHash, bool filtered)
external onlyOwner
{
OPERATOR_FILTER_REGISTRY.updateCodeHash(address(this), codeHash, filtered);
}
/**
* @notice Batch function for updateOperator
*/
function updateOperators(address[] calldata operators, bool filtered)
external onlyOwner
{
OPERATOR_FILTER_REGISTRY.updateOperators(address(this), operators, filtered);
}
/**
* @notice Batch function for updateCodeHash
*/
function updateCodeHashes(bytes32[] calldata codeHashes, bool filtered)
external onlyOwner
{
OPERATOR_FILTER_REGISTRY.updateCodeHashes(address(this), codeHashes, filtered);
}
/**
* @notice Check if a given operator address is currently filtered
*/
function isOperatorAllowed(address operator)
external view
returns (bool)
{
return OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator);
}
/**
* @notice Subscribe to OperatorFilterRegistry contract : activate modifiers
*/
function subscribe(address subscription)
external onlyOwner
{
return OPERATOR_FILTER_REGISTRY.subscribe(address(this), subscription);
}
/**
* @notice Unsubscribe to OperatorFilterRegistry contract : deactivate modifiers
*/
function unsubscribe(bool copyExistingEntries)
external onlyOwner
{
return OPERATOR_FILTER_REGISTRY.unsubscribe(address(this), copyExistingEntries);
}
/**
* @notice Copy filtered operators of a given OperatorFilterRegistry
registered smart contract
*/
function copyEntriesOf(address registrantToCopy)
external onlyOwner
{
return OPERATOR_FILTER_REGISTRY.copyEntriesOf(address(this), registrantToCopy);
}
/**
* @notice Returns the list of filtered operators
*/
function filteredOperators()
external
returns (address[] memory)
{
return OPERATOR_FILTER_REGISTRY.filteredOperators(address(this));
}
/**
* @notice Overriding ERC721A.supportsInterface as advised for OperatorFilterRegistry
smart contract
*/
function supportsInterface(bytes4 interfaceId)
public view virtual
override
returns (bool)
{
return super.supportsInterface(interfaceId);
}
/**
* @notice Overriding ERC721A.approve to integrate OperatorFilter modifier
onlyAllowedOperatorApproval
*/
function approve(address operator, uint256 tokenId)
public payable
override
onlyAllowedOperatorApproval(operator)
{
super.approve(operator, tokenId);
}
/**
* @notice Overriding ERC721A.setApprovalForAll to integrate OperatorFilter modifier
onlyAllowedOperatorApproval
*/
function setApprovalForAll(address operator, bool approved)
public
override
onlyAllowedOperatorApproval(operator)
{
super.setApprovalForAll(operator, approved);
}
/**
* @notice Overriding ERC721A.safeTransferFrom to integrate
OperatorFilter modifier onlyAllowedOperator
*/
function safeTransferFrom(address from, address to, uint256 tokenId)
public payable
override
onlyAllowedOperator(from)
{
super.safeTransferFrom(from, to, tokenId);
}
/**
* @notice Overriding ERC721A.safeTransferFrom to integrate
OperatorFilter modifier onlyAllowedOperator
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
public payable
override
onlyAllowedOperator(from)
{
super.safeTransferFrom(from, to, tokenId, data);
}
/**
* @notice Overriding ERC721A.transferFrom to integrate
OperatorFilter modifier onlyAllowedOperator
*/
function transferFrom(address from, address to, uint256 tokenId)
public payable
override
onlyAllowedOperator(from)
{
super.transferFrom(from, to, tokenId);
}
// Mint Functions
////////////////////
/**
* @notice Internal mint logic
*/
function _mintNFT(uint256 nMint, address recipient)
private
{
require(_tokenIds.current() + nMint <= MAX_SUPPLY, "No more NFT to mint");
mints[recipient] += nMint;
for (uint256 i = 0; i < nMint; i++)
{
_tokenIds.increment();
}
// Use _mint instead of _safeMint
// Because _mint is better when INDIVIDUALS are going to mint
// While _safeMint is better when SMART CONTRACTS are going to mint
_mint(recipient, nMint);
}
/**
* @notice Main mint entry point
*/
function mintNFT(uint256 nMint, uint8 v, bytes32 r, bytes32 s, string calldata nonce, uint256 deadline)
external payable
{
require(currentMintPhase != MintPhase.CLOSED, "Mint period have not started yet");
require(tx.origin == msg.sender, "No bots allowed");
require(msg.value >= price * nMint, "Not enough ETH to mint");
if (currentMintPhase == MintPhase.WHITELIST)
{
require(isRole(whitelistRole, nonce, deadline, v, r, s), "You are not whitelisted");
require(mints[msg.sender] + nMint <= maxMintWhitelist, "Too much NFT minted");
}
else if (currentMintPhase == MintPhase.ALLOWLIST)
{
require(isRole(allowlistRole, nonce, deadline, v, r, s), "You are not allowlisted");
require(mints[msg.sender] + nMint <= maxMintAllowlist, "Too much NFT minted");
}
else if (currentMintPhase == MintPhase.PUBLIC)
{
require(mints[msg.sender] + nMint <= maxMintPublic, "Too much NFT minted");
}
return _mintNFT(nMint, msg.sender);
}
/**
* @notice Team giveway mint entry point
*/
function giveaway(uint256 nMint, address recipient)
external
onlyApprovedOrOwner(msg.sender)
{
return _mintNFT(nMint, recipient);
}
/**
* @notice Burn function
*/
function burnNFT(uint256 tokenId)
external
onlyOwnerOfOrApproved(msg.sender, tokenId)
{
_burn(tokenId);
}
// Attributes getters
////////////////////
/**
* @notice Get metadatas of the given <tokenId>
*/
function tokenURI(uint256 tokenId)
public view
override
returns (string memory)
{
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory tokenIdURI = tokenURIs[tokenId];
string memory emptyString = "";
if (keccak256(abi.encodePacked(tokenIdURI)) == keccak256(abi.encodePacked(emptyString)))
{
return string(abi.encodePacked(abi.encodePacked(abi.encodePacked(baseURI, "/"), Strings.toString(tokenId)), ".json"));
}
else
{
return tokenIdURI;
}
}
// Attributes setters
////////////////////
/**
* @notice If <addr> is an approved caller, remove it from the list.
* otherwise, add it to the list of approved callers
*/
function toggleApprovedCaller(address addr)
external
onlyOwner
{
if (approvedCallers[addr] == 1)
{
approvedCallers[addr] = 0;
}
else
{
approvedCallers[addr] = 1;
}
}
/**
* @notice Set a specific <tokenIdURI> for a given <tokenId>
*/
function setTokenUri(uint8 tokenId, string memory tokenIdURI)
external
onlyApprovedOrOwner(msg.sender)
{
tokenURIs[tokenId] = tokenIdURI;
}
/**
* @notice Set the current mint phase (Whitelist, Allowlist or Public)
*/
function setMintPhase(MintPhase _mintPhase)
external
onlyOwner
{
require(uint256(_mintPhase) >= 0 && uint256(_mintPhase) <= 3, "_mintPhase have to be between 0 and 3");
require(_mintPhase > currentMintPhase, "new mint phase must be strictly greater than current one");
currentMintPhase = _mintPhase;
}
/**
* @notice Set the mint price
*/
function setPrice(uint256 priceGwei)
external
onlyOwner
{
price = priceGwei * 10**9;
}
/**
* @notice Set the base tokenURI, on which metadatas are stored like <_baseURI>/<token_id>.json
*/
function setBaseUri(string memory _baseURI)
external
onlyOwner
{
baseURI = _baseURI;
}
/**
* @notice Set the max mintable NFTs by Whitelisted address
*/
function setMaxPerWalletWhitelist(uint8 maxMint)
external
onlyOwner
{
maxMintWhitelist = maxMint;
}
/**
* @notice Set the max mintable NFTs by Allowlisted address
*/
function setMaxPerWalletAllowlist(uint8 maxMint)
external
onlyOwner
{
maxMintAllowlist = maxMint;
}
/**
* @notice Set the max mintable NFTs by Public (neither Whitelist or Allowlist) address
*/
function setMaxPerWalletPublic(uint8 maxMint)
external
onlyOwner
{
maxMintPublic = maxMint;
}
/**
* @notice Set the main team wallet
*/
function setMainAddress(address _mainAddress)
external
onlyOwner
{
mainAddress = _mainAddress;
}
/**
* @notice Set the address of the signer
*/
function setSignerAddress(address addr)
external
onlyOwner
{
signerAddress = addr;
}
// Helpers function
//////////////////////////
/**
* @notice Freeze the <tokenId> metadata, and attribute its key type (Regular, Robot, Ape, Zombie or Spirit)
*/
function freezeKey(uint256 tokenId, uint8 percent, string calldata nonce, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external
{
require(ownerOf(tokenId) == msg.sender || msg.sender == owner(), "Not your NFT nor the owner");
require(isAllowed(nonce, deadline, v, r, s), "You are not allowed");
require(keyTypes[tokenId] == KeyType.REGULAR, "Key have already been frozen");
require(percent >= 0 && percent <= 100, "Percent have to be between 0 and 100");
keyPercents[tokenId] = percent;
if (percent < 25)
{
revert("Nothing to claim");
}
else if (percent < 50 && (currentCountKeyTypes[KeyType.ROBOT] < maxCountKeyTypes[KeyType.ROBOT]))
{
keyTypes[tokenId] = KeyType.ROBOT;
currentCountKeyTypes[KeyType.ROBOT]++;
}
else if (percent < 75 && (currentCountKeyTypes[KeyType.ZOMBIE] < maxCountKeyTypes[KeyType.ZOMBIE]))
{
keyTypes[tokenId] = KeyType.ZOMBIE;
currentCountKeyTypes[KeyType.ZOMBIE]++;
}
else if (percent < 100 && (currentCountKeyTypes[KeyType.APE] < maxCountKeyTypes[KeyType.APE]))
{
keyTypes[tokenId] = KeyType.APE;
currentCountKeyTypes[KeyType.APE]++;
}
else if (percent == 100 && (currentCountKeyTypes[KeyType.SPIRIT] < maxCountKeyTypes[KeyType.SPIRIT]))
{
keyTypes[tokenId] = KeyType.SPIRIT;
currentCountKeyTypes[KeyType.SPIRIT]++;
}
}
/**
* @notice Freeze the Gold keys (1/1 NFTs)
*/
function freezeGoldKeys(uint256[] calldata tokenIds)
external
onlyOwner
{
for (uint256 idx = 0; idx < tokenIds.length; idx++)
{
unchecked
{
keyPercents[tokenIds[idx]] = 100;
keyTypes[tokenIds[idx]] = KeyType.GOLD;
}
}
}
/**
* @notice Check the given signature params against signer address
Used to verify signature validity
*/
function isAllowed(string calldata nonce, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
internal view
returns (bool)
{
require(block.timestamp <= deadline, "Signing too late");
require(
recoverSigner(msg.sender, nonce, deadline, v, r, s) == signerAddress,
"Wrong signer"
);
return true;
}
/**
* @notice Check the given signature params against signer address
Used to verify role validity
*/
function isRole(bytes32 role, string calldata nonce, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
internal view
returns (bool)
{
require(block.timestamp <= deadline, "Signing too late");
require(
recoverRole(role, msg.sender, nonce, deadline, v, r, s) == signerAddress,
"Wrong signer"
);
return true;
}
/**
* @notice Retrieve the signer address induced by the given signature params
*/
function recoverSigner(address addr, string memory nonce, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
internal pure
returns (address)
{
return ecrecover(sha256(abi.encodePacked(addr, nonce, deadline)), v, r, s);
}
function recoverRole(bytes32 role, address addr, string memory nonce, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
internal pure
returns (address)
{
return ecrecover(sha256(abi.encodePacked(role, addr, nonce, deadline)), v, r, s);
}
function withdraw()
external onlyOwner
{
uint256 amount = address(this).balance;
require(amount > 0, "Nothing to withdraw");
// bool success = payable(mainAddress).send(amount);
(bool success, ) = payable(mainAddress).call{value: amount}("");
require(success, "Failed to withdraw");
}
receive()
external payable
{
}
fallback()
external
{
}
}
{
"compilationTarget": {
"teiko_key/TeikoKey.sol": "TeikoKey"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"ApprovalQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"BalanceQueryForZeroAddress","type":"error"},{"inputs":[],"name":"MintERC2309QuantityExceedsLimit","type":"error"},{"inputs":[],"name":"MintToZeroAddress","type":"error"},{"inputs":[],"name":"MintZeroQuantity","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"OperatorNotAllowed","type":"error"},{"inputs":[],"name":"OwnerQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"OwnershipNotInitializedForExtraData","type":"error"},{"inputs":[],"name":"TransferCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToNonERC721ReceiverImplementer","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[],"name":"URIQueryForNonexistentToken","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"toTokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"ConsecutiveTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"MAX_SUPPLY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OPERATOR_FILTER_REGISTRY","outputs":[{"internalType":"contract IOperatorFilterRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allowlistRole","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"approvedCallers","outputs":[{"internalType":"int8","name":"","type":"int8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burnNFT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"registrantToCopy","type":"address"}],"name":"copyEntriesOf","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum TeikoKey.KeyType","name":"","type":"uint8"}],"name":"currentCountKeyTypes","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentMintPhase","outputs":[{"internalType":"enum TeikoKey.MintPhase","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"filteredOperators","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"freezeGoldKeys","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint8","name":"percent","type":"uint8"},{"internalType":"string","name":"nonce","type":"string"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"freezeKey","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"nMint","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"giveaway","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"isOperatorAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"keyPercents","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"keyTypes","outputs":[{"internalType":"enum TeikoKey.KeyType","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mainAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum TeikoKey.KeyType","name":"","type":"uint8"}],"name":"maxCountKeyTypes","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxMintAllowlist","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxMintPublic","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxMintWhitelist","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"nMint","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"string","name":"nonce","type":"string"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"mintNFT","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"mints","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"register","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"registrantToCopy","type":"address"}],"name":"registerAndCopyEntries","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"subscription","type":"address"}],"name":"registerAndSubscribe","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_baseURI","type":"string"}],"name":"setBaseUri","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_mainAddress","type":"address"}],"name":"setMainAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"maxMint","type":"uint8"}],"name":"setMaxPerWalletAllowlist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"maxMint","type":"uint8"}],"name":"setMaxPerWalletPublic","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"maxMint","type":"uint8"}],"name":"setMaxPerWalletWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum TeikoKey.MintPhase","name":"_mintPhase","type":"uint8"}],"name":"setMintPhase","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"priceGwei","type":"uint256"}],"name":"setPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setSignerAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"tokenId","type":"uint8"},{"internalType":"string","name":"tokenIdURI","type":"string"}],"name":"setTokenUri","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"signerAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"subscription","type":"address"}],"name":"subscribe","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"toggleApprovedCaller","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenURIs","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unregister","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"copyExistingEntries","type":"bool"}],"name":"unsubscribe","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"codeHash","type":"bytes32"},{"internalType":"bool","name":"filtered","type":"bool"}],"name":"updateCodeHash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"codeHashes","type":"bytes32[]"},{"internalType":"bool","name":"filtered","type":"bool"}],"name":"updateCodeHashes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"filtered","type":"bool"}],"name":"updateOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"operators","type":"address[]"},{"internalType":"bool","name":"filtered","type":"bool"}],"name":"updateOperators","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"whitelistRole","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]