// 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: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
/// @dev Note that balanceOf does not revert if passed the zero address, in defiance of the ERC.
abstract contract ERC721 {
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 indexed id);
event Approval(address indexed owner, address indexed spender, uint256 indexed id);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/*///////////////////////////////////////////////////////////////
METADATA STORAGE/LOGIC
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
function tokenURI(uint256 id) public view virtual returns (string memory);
/*///////////////////////////////////////////////////////////////
ERC721 STORAGE
//////////////////////////////////////////////////////////////*/
mapping(address => uint256) public balanceOf;
mapping(uint256 => address) public ownerOf;
mapping(uint256 => address) public getApproved;
mapping(address => mapping(address => bool)) public isApprovedForAll;
/*///////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
}
/*///////////////////////////////////////////////////////////////
ERC721 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 id) public virtual {
address owner = ownerOf[id];
require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");
getApproved[id] = spender;
emit Approval(owner, spender, id);
}
function setApprovalForAll(address operator, bool approved) public virtual {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function transferFrom(
address from,
address to,
uint256 id
) public virtual {
require(from == ownerOf[id], "WRONG_FROM");
require(to != address(0), "INVALID_RECIPIENT");
require(
msg.sender == from || msg.sender == getApproved[id] || isApprovedForAll[from][msg.sender],
"NOT_AUTHORIZED"
);
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
unchecked {
balanceOf[from]--;
balanceOf[to]++;
}
ownerOf[id] = to;
delete getApproved[id];
emit Transfer(from, to, id);
}
function safeTransferFrom(
address from,
address to,
uint256 id
) public virtual {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes memory data
) public virtual {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
/*///////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}
/*///////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 id) internal virtual {
require(to != address(0), "INVALID_RECIPIENT");
require(ownerOf[id] == address(0), "ALREADY_MINTED");
// Counter overflow is incredibly unrealistic.
unchecked {
balanceOf[to]++;
}
ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function _burn(uint256 id) internal virtual {
address owner = ownerOf[id];
require(ownerOf[id] != address(0), "NOT_MINTED");
// Ownership check above ensures no underflow.
unchecked {
balanceOf[owner]--;
}
delete ownerOf[id];
delete getApproved[id];
emit Transfer(owner, address(0), id);
}
/*///////////////////////////////////////////////////////////////
INTERNAL SAFE MINT LOGIC
//////////////////////////////////////////////////////////////*/
function _safeMint(address to, uint256 id) internal virtual {
_mint(to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function _safeMint(
address to,
uint256 id,
bytes memory data
) internal virtual {
_mint(to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
}
/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
interface ERC721TokenReceiver {
function onERC721Received(
address operator,
address from,
uint256 id,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol)
pragma solidity ^0.8.0;
import "../utils/introspection/IERC165.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol)
pragma solidity ^0.8.0;
import "../token/ERC20/IERC20.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/IERC2981.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Interface for the NFT Royalty Standard.
*
* A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
* support for royalty payments across all NFT marketplaces and ecosystem participants.
*
* _Available since v4.5._
*/
interface IERC2981 is IERC165 {
/**
* @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
* exchange. The royalty amount is denominated and should be payed in that same unit of exchange.
*/
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
returns (address receiver, uint256 royaltyAmount);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.13;
interface IKitten {
enum Trait {
Background,
Body,
Clothes,
Ears,
Eyes,
Head,
Neck,
Special,
Weapon
}
struct Kitten {
uint8 background;
uint8 body;
uint8 clothes;
uint8 ears;
uint8 eyes;
uint8 head;
uint8 neck;
uint8 special;
uint8 weapon;
}
/// ERC721-like
function getOwner(uint256 tokenId) external view returns (address);
function getNextTokenId() external view returns (uint256);
/// WarKittens
function mint(uint256 amount, address to) external payable;
function mintCommunitySale(address to, bytes32[] calldata merkleProof) external payable;
function reserveForGifting(uint256 amount) external;
function giftKittens(address[] calldata addresses) external;
function getKitten(uint256 tokenId) external view returns (Kitten memory);
function getTrait(uint256 tokenId, Trait trait) external view returns (uint8);
function updateTrait(uint256 tokenId, Trait trait, uint8 value) external;
}
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.13;
import "./IKitten.sol";
interface IMetadata {
function getPlaceholderURI(uint256 tokenId) external view returns (string memory);
function getTokenURI(uint256 tokenId, IKitten.Kitten calldata kitten, bool offChain)
external
view
returns (string memory);
function uploadTraits(
uint8 trait,
uint8[] calldata traitIds,
string[] calldata names,
string[] calldata images
) external;
}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@rari-capital/solmate/src/tokens/ERC721.sol";
import "./interfaces/IKitten.sol";
import "./interfaces/IMetadata.sol";
/// @title Kitten
/// @author kitten devs
/// @notice h/t crypto coven for some beautiful ERC721 inspiration.
contract Kitten is IKitten, ERC721, IERC2981, Ownable {
/// STORAGE ///
/// @notice Allowed max supply of Kittens.
uint256 public constant MAX_KITTENS = 9500;
/// @notice Per-wallet Kitten cap.
uint256 public constant MAX_KITTENS_PER_WALLET = 4;
/// @notice Public sale.
bool public isPublicSaleActive;
uint256 public constant PUBLIC_SALE_PRICE = 0.088 ether;
/// @notice Community sale (determined in Discord).
bool public isCommunitySaleActive;
uint256 public constant COMMUNITY_SALE_PRICE = 0.077 ether;
/// @notice Merkle root for first community sale claims.
bytes32 public communityFirstClaimMerkleRoot;
/// @notice Addresses that have claimed a first community mint.
mapping(address => bool) public claimedFirst;
/// @notice Second community sale list for addresses with two claims.
bytes32 public communitySecondClaimMerkleRoot;
/// @notice Addresses that have claimed a second community mint.
mapping(address => bool) public claimedSecond;
/// @notice Counters for addresses that have participated in public sale.
mapping(address => bool) public publicSaleParticipants;
/// @notice Team + gift kittens.
uint256 public numGiftedKittens;
uint256 public constant MAX_GIFTED_KITTENS = 400;
/// @notice Royalty percentage. Must be an integer percentage.
uint8 public royaltyPercent = 5;
/// @notice Shifting kitten metadata prereveal.
bool public hasShifted = false;
uint256 public metadataOffset = 0;
/// @notice Metadata reveal trigger.
bool public isRevealed = false;
/// @notice Mapping of token ID to traits.
mapping(uint256 => Kitten) public kittens;
/// @notice Mapping of Kittens using special renderer.
mapping(uint256 => bool) public specialMode;
/// @notice List of probabilities for each trait type.
uint16[][9] public traitRarities;
/// @notice List of aliases for AJ Walker's Alias Algorithm.
uint16[][9] public traitAliases;
/// @notice Sauce up the prices.
uint256[4] private sauce = [
0.00002 ether,
0.00006 ether,
0.00001 ether,
0.00007 ether
];
/// @notice Addresses that can perform admin functionality, e.g. gifting.
mapping(address => bool) private admins;
/// @notice Address of the metadata renderer.
address public renderer;
/// @notice Used for increased pseudorandomness.
bytes32 private entropy;
/// @notice Counter of minted tokens.
uint256 public tokenCounter;
/// CONSTRUCTOR ///
constructor() ERC721("WarKitten", "KITTEN") {
// This looks insane, but it works.
// Backgrounds.
traitRarities[0] = [
232, 201, 77, 70, 47, 136,
174, 93, 110, 232, 77, 115,
118, 117, 198, 121, 56, 61,
62, 106, 106, 255, 52
];
traitAliases[0] = [
1, 5,
0, 1,
1, 6,
8, 5,
9, 10,
11, 12,
14, 8,
15, 21,
8, 9,
10, 12,
12, 0,
15
];
// Bodies.
traitRarities[1] = [
167, 192, 215, 98, 221, 252, 165, 207, 210, 133,
184, 182, 109, 219, 143, 202, 61, 249, 207, 196,
185, 214, 143, 120, 135, 76, 109, 233, 168, 247,
141, 176, 56, 143, 109, 130, 204, 148, 170, 240,
232, 165, 165, 143, 250, 244, 201, 245, 72, 131,
139, 200, 120, 207, 133, 173, 63, 70, 83, 185,
223, 253, 120, 126, 165, 193, 120, 255, 128
];
traitAliases[1] = [
1, 2, 4, 1,
8, 2, 2, 2,
9, 10, 15, 2,
4, 4, 4, 17,
8, 21, 9, 9,
9, 27, 9, 9,
15, 17, 21, 28,
29, 30, 38, 21,
28, 29, 29, 30,
38, 38, 39, 45,
38, 38, 39, 45,
45, 46, 47, 48,
51, 45, 46, 54,
47, 47, 55, 60,
48, 54, 61, 61,
61, 63, 63, 65,
65, 67, 65, 0,
67
];
// Clothes.
traitRarities[2] = [
201, 251, 190, 122, 143, 97, 46, 224, 233, 131, 203,
253, 194, 153, 153, 153, 56, 126, 81, 66, 106, 97,
62, 50, 50, 72, 50, 50, 81, 179, 153, 72, 253,
184, 228, 112, 130, 108, 62, 62, 131, 153, 108, 151,
234, 91, 203, 111, 233, 102, 54, 72, 85, 58, 184,
153, 112, 81, 131, 91, 31, 215, 117, 203, 153, 212,
97, 153, 184, 218, 114, 255
];
traitAliases[2] = [
1, 2, 8, 0, 2, 8, 12, 12, 12, 12, 29, 29,
29, 29, 34, 34, 36, 43, 47, 61, 62, 65, 69, 69,
70, 70, 70, 71, 71, 34, 71, 71, 71, 71, 36, 71,
43, 71, 71, 71, 71, 71, 71, 44, 47, 71, 71, 48,
61, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
71, 62, 65, 71, 71, 69, 71, 71, 71, 70, 71, 0
];
// Ears.
traitRarities[3] = [
23, 26, 52, 35, 152,
158, 200, 107, 157, 30,
87, 189, 255
];
traitAliases[3] = [
4, 7, 7, 11, 5,
6, 7, 8, 11, 11,
12, 12, 0
];
// Eyes.
traitRarities[4] = [
51, 70, 187, 151, 126, 163, 60,
51, 27, 30, 97, 237, 69, 249,
19, 34, 163, 90, 144, 117, 105,
125, 169, 136, 27, 90, 109, 47,
255
];
traitAliases[4] = [
4, 28, 28, 28, 5, 22, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 0
];
// Heads.
traitRarities[5] = [
145, 127, 47, 13, 27, 221, 228, 147, 241, 114, 65, 199,
49, 29, 215, 51, 98, 255, 189, 53, 56, 58, 32, 42,
67, 209, 89, 212, 60, 45, 31, 33, 16, 20, 40, 62,
168, 189, 131, 36, 105, 216, 74, 87, 155, 252, 71, 71,
118, 193, 216, 67, 196, 203, 78, 242, 22, 145, 46, 89,
100, 123, 221, 147, 167, 25, 38, 125, 138, 214, 143, 190,
232, 74, 175, 40, 216, 221, 218, 130, 242, 191, 255
];
traitAliases[5] = [
6, 11, 16, 22, 37, 37, 8, 37, 11, 41, 43, 14,
48, 58, 16, 62, 17, 22, 71, 73, 79, 81, 26, 82,
82, 82, 36, 82, 82, 82, 82, 82, 82, 82, 82, 82,
37, 41, 82, 82, 82, 43, 82, 44, 45, 48, 82, 82,
49, 50, 55, 82, 82, 82, 82, 58, 82, 82, 61, 82,
82, 62, 71, 82, 82, 82, 82, 82, 82, 82, 82, 72,
73, 74, 77, 82, 82, 79, 82, 80, 81, 82, 0
];
// Necks.
traitRarities[6] = [
67, 70, 23, 86, 51,
56, 121, 67, 60, 255
];
traitAliases[6] = [
9, 9, 9, 9, 9,
9, 9, 9, 9, 0
];
// Paws.
traitRarities[7] = [
193, 133, 111, 230, 106, 89, 59, 126, 94, 197, 94,
236, 239, 115, 198, 85, 24, 204, 28, 150, 78, 80,
217, 222, 90, 128, 120, 48, 130, 188, 122, 192, 61,
63, 67, 69, 70, 87, 33, 97, 120, 232, 124, 89,
99, 218, 41, 244, 93, 230, 107, 239, 96, 81, 109,
85, 74, 25, 98, 87, 254, 130, 102, 165, 56, 78,
250, 119, 255
];
traitAliases[7] = [
1, 3, 0, 8, 1, 8, 24, 28, 9, 11, 39, 12,
24, 44, 44, 44, 57, 65, 65, 67, 67, 68, 68, 68,
28, 68, 68, 68, 29, 31, 68, 39, 68, 68, 68, 68,
68, 68, 68, 40, 41, 44, 68, 68, 45, 47, 68, 57,
68, 68, 68, 68, 68, 68, 68, 68, 68, 63, 68, 68,
68, 68, 68, 65, 68, 66, 67, 68, 0
];
// Special.
traitRarities[8] = [
12, 10, 8, 14,
19, 17, 255
];
traitAliases[8] = [
6, 6, 6, 6, 6, 6, 0
];
}
/// MODIFIERS ///
/// @notice Some anti-bot restrictions.
modifier noCheats() {
uint256 size = 0;
address account = msg.sender;
assembly {
size := extcodesize(account)
}
require(
admins[msg.sender] || (msg.sender == tx.origin && size == 0),
"You're trying to cheat!"
);
_;
// Use the last caller hash to add entropy to next caller.
entropy = keccak256(abi.encodePacked(account, block.coinbase));
}
modifier increaseEntropy() {
_;
// Use the last caller hash to add entropy to next caller.
entropy = keccak256(abi.encodePacked(msg.sender, block.coinbase));
}
modifier onlyAdmin() {
require(admins[msg.sender], "Must be an admin");
_;
}
modifier publicSaleActive() {
require(isPublicSaleActive, "Public sale is not open");
_;
}
modifier communitySaleActive() {
require(isCommunitySaleActive, "Community sale is not open");
_;
}
modifier canMintKittens(uint256 numberOfTokens) {
require(numberOfTokens > 0, "Cannot mint zero");
require(numberOfTokens <= MAX_KITTENS_PER_WALLET, "Max kittens to mint exceeded");
require(
tokenCounter + numberOfTokens <= MAX_KITTENS,
"Not enough kittens remaining to mint"
);
_;
}
modifier canGiftKittens(uint256 num) {
require(
numGiftedKittens + num <= MAX_GIFTED_KITTENS,
"Not enough kittens remaining to gift"
);
require(
tokenCounter + num <= MAX_KITTENS,
"Not enough kittens remaining to mint"
);
_;
}
modifier isCorrectCommunitySalePayment() {
require(msg.value == COMMUNITY_SALE_PRICE, "Incorrect ETH value sent");
_;
}
modifier isCorrectPublicSalePayment(uint256 number) {
require(msg.value == (PUBLIC_SALE_PRICE * number) + sauce[number - 1], "Incorrect ETH value sent");
_;
}
/// MINTING ///
function mint(uint256 amount, address to)
external
payable
publicSaleActive
canMintKittens(amount)
isCorrectPublicSalePayment(amount)
noCheats
{
require(!publicSaleParticipants[msg.sender], "Already minted");
publicSaleParticipants[msg.sender] = true;
uint256 seed = _rand();
for (uint64 i = 0; i < amount; ++i) {
_mintKitten(seed, to);
}
}
function mintCommunitySale(
address to,
bytes32[] calldata merkleProof
)
external
payable
communitySaleActive
canMintKittens(1)
isCorrectCommunitySalePayment
noCheats
{
// We have two checks here, since some addresses have two claims.
if (claimedFirst[to]) {
// Check for second claim.
require(!claimedSecond[to], "Already claimed");
require(_isValidMerkleProof(merkleProof, communitySecondClaimMerkleRoot, to), "Already claimed");
claimedSecond[to] = true;
_mintKitten(_rand(), to);
} else {
// First claim.
require(_isValidMerkleProof(merkleProof, communityFirstClaimMerkleRoot, to), "Address not in list");
claimedFirst[to] = true;
_mintKitten(_rand(), to);
}
}
function reserveForGifting(uint256 amount)
external
onlyAdmin
canGiftKittens(amount)
increaseEntropy
{
numGiftedKittens += amount;
uint256 seed = _rand();
for (uint256 i = 0; i < amount; i++) {
_mintKitten(seed, msg.sender);
}
}
function giftKittens(address[] calldata addresses)
external
onlyAdmin
canGiftKittens(addresses.length)
increaseEntropy
{
uint256 numToGift = addresses.length;
numGiftedKittens += numToGift;
uint256 seed = _rand();
for (uint256 i = 0; i < numToGift; i++) {
_mintKitten(seed, addresses[i]);
}
}
function _mintKitten(uint256 seed, address to) internal {
uint256 tokenId = _getNextTokenId();
kittens[tokenId] = selectTraits(_randomize(seed, tokenId));
_safeMint(to, tokenId);
}
/// IKITTEN ///
function getOwner(uint256 tokenId) public view returns (address) {
return ownerOf[tokenId];
}
function getNextTokenId() public view returns (uint256) {
return tokenCounter + 1;
}
function getKitten(uint256 tokenId) public view returns (Kitten memory) {
require(_exists(tokenId), "No such kitten");
return kittens[_getOffsetTokenId(tokenId)];
}
/// @notice Returns a single trait value for a Kitten.
function getTrait(uint256 tokenId, Trait trait) public view returns (uint8) {
require(_exists(tokenId), "No such kitten");
Kitten storage kitten = kittens[_getOffsetTokenId(tokenId)];
if (trait == Trait.Background) return kitten.background;
else if (trait == Trait.Body) return kitten.body;
else if (trait == Trait.Clothes) return kitten.clothes;
else if (trait == Trait.Ears) return kitten.ears;
else if (trait == Trait.Eyes) return kitten.eyes;
else if (trait == Trait.Head) return kitten.head;
else if (trait == Trait.Neck) return kitten.neck;
else if (trait == Trait.Special) return kitten.special;
else return kitten.weapon;
}
/// @notice Updates a single trait for a Kitten.
/// @dev Used for swapping traits after battles.
function updateTrait(uint256 tokenId, Trait trait, uint8 value) public onlyAdmin {
require(_exists(tokenId), "No such kitten");
Kitten storage kitten = kittens[_getOffsetTokenId(tokenId)];
if (trait == Trait.Background) kitten.background = value;
else if (trait == Trait.Body) kitten.body = value;
else if (trait == Trait.Clothes) kitten.clothes = value;
else if (trait == Trait.Ears) kitten.ears = value;
else if (trait == Trait.Eyes) kitten.eyes = value;
else if (trait == Trait.Head) kitten.head = value;
else if (trait == Trait.Neck) kitten.neck = value;
else if (trait == Trait.Special) kitten.special = value;
else if (trait == Trait.Weapon) kitten.weapon = value;
}
/// @notice Toggle the Kitten's mode.
function setKittenMode(uint256 tokenId, bool special) external {
// Must be sender's Kitten.
require(ownerOf[tokenId] == msg.sender, "Not your kitten");
specialMode[tokenId] = special;
}
/// HELPERS ///
function _isValidMerkleProof(
bytes32[] calldata merkleProof,
bytes32 root,
address account
) internal pure returns (bool) {
return MerkleProof.verify(
merkleProof,
root,
keccak256(abi.encodePacked(account))
);
}
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return ownerOf[tokenId] != address(0) && tokenId <= MAX_KITTENS;
}
/// @notice Returns a shifted token ID once we've performed the reveal shift.
/// @dev Ensure the shift is done *before* reveal is toggled.
function _getOffsetTokenId(uint256 tokenId) internal view virtual returns (uint256) {
if (!hasShifted || metadataOffset == 0) {
return tokenId;
}
return ((tokenId + metadataOffset) % MAX_KITTENS) + 1;
}
function _getNextTokenId() private returns (uint256) {
++ tokenCounter;
return tokenCounter;
}
/// TRAITS ///
/// @notice Uses AJ Walker's Alias algorithm for O(1) rarity table lookup.
/// @notice Ensures O(1) instead of O(n), reduces mint cost.
/// @notice Probability & alias tables are generated off-chain beforehand.
function selectTrait(uint16 seed, uint8 traitType)
internal
view
returns (uint8)
{
uint8 trait = uint8(seed) % uint8(traitRarities[traitType].length);
// If a selected random trait probability is selected (biased coin) return that trait.
if (seed >> 8 < traitRarities[traitType][trait]) return trait;
return uint8(traitAliases[traitType][trait]);
}
/// @notice Constructs a Kitten with weighted random attributes.
function selectTraits(uint256 seed)
internal
view
returns (Kitten memory kitten)
{
kitten.background = selectTrait(uint16(seed & 0xFFFF), 0) + 1;
seed >>= 16;
kitten.body = selectTrait(uint16(seed & 0xFFFF), 1) + 1;
seed >>= 16;
kitten.clothes = selectTrait(uint16(seed & 0xFFFF), 2) + 1;
seed >>= 16;
kitten.ears = selectTrait(uint16(seed & 0xFFFF), 3) + 1;
seed >>= 16;
kitten.eyes = selectTrait(uint16(seed & 0xFFFF), 4) + 1;
seed >>= 16;
kitten.head = selectTrait(uint16(seed & 0xFFFF), 5) + 1;
seed >>= 16;
kitten.neck = selectTrait(uint16(seed & 0xFFFF), 6) + 1;
seed >>= 16;
kitten.weapon = selectTrait(uint16(seed & 0xFFFF), 7) + 1;
seed >>= 16;
kitten.special = selectTrait(uint16(seed & 0xFFFF), 8) + 1;
}
/// ADMIN ///
/// @notice Adds or removes an admin address.
function setAdmin(address admin, bool isAdmin) external onlyOwner {
admins[admin] = isAdmin;
}
function setRenderer(address _renderer) external onlyAdmin {
renderer = _renderer;
}
function setIsPublicSaleActive(bool _isPublicSaleActive)
external
onlyOwner
{
isPublicSaleActive = _isPublicSaleActive;
}
function setIsCommunitySaleActive(bool _isCommunitySaleActive)
external
onlyOwner
{
isCommunitySaleActive = _isCommunitySaleActive;
}
function setFirstCommunityListMerkleRoot(bytes32 merkleRoot) external onlyOwner {
communityFirstClaimMerkleRoot = merkleRoot;
}
function setSecondCommunityListMerkleRoot(bytes32 merkleRoot) external onlyOwner {
communitySecondClaimMerkleRoot = merkleRoot;
}
function setRoyaltyPercentage(uint8 percent) external onlyOwner {
royaltyPercent = percent;
}
/// @notice Sets a shifted metadata offset so that Kitten traits aren't mapped precisely to token ID.
/// @notice This ensures you can't predict which Kitten traits you'll get at mint time.
/// @dev The actual offset ends up being this offset + 1, since we do a modulo on the supply and start with token 1.
function setMetadataOffset(uint256 offset) external onlyOwner {
if (!hasShifted) {
metadataOffset = offset;
hasShifted = true;
}
}
/// @notice Resets the metadata shift offset to 0 in case something unexpected happens.
/// @dev We wouldn't be able to shift again, since the offset is a one-time setter (no rugs!).
function resetMetadataOffset() external onlyOwner {
metadataOffset = 0;
}
function setRevealed() external onlyOwner {
isRevealed = true;
}
/// @notice Break glass in case of emergency.
function deSauce() external onlyOwner {
sauce = [0, 0, 0, 0];
}
/// @notice Send contract balance to owner.
function withdraw() external onlyOwner {
(bool success, ) = owner().call{value: address(this).balance}("");
require(success, "Withdraw failed");
}
/// @notice Do our best to get mistakenly sent ERC20s out of the contract.
function withdrawTokens(address token) external onlyOwner {
uint256 balance = IERC20(token).balanceOf(address(this));
IERC20(token).transfer(msg.sender, balance);
}
/// RANDOMNESS ///
/// @notice Create a bit more randomness by hashing a seed with another input value.
/// @dev We do this to "re-hash" pseudorandom values within the same tx.
/// @dev h/t 0xBasset.
function _randomize(uint256 rand, uint256 zest) internal pure returns (uint256) {
return uint256(keccak256(abi.encode(rand, zest)));
}
/// @notice Generates a pseudorandom number based on the current block and caller.
/// @dev This will be the same if called in the same tx without changing entropy.
function _rand() internal view returns (uint256) {
return uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.basefee, block.coinbase, entropy)));
}
/// OVERRIDES ///
function supportsInterface(bytes4 interfaceId)
public
pure
virtual
override(ERC721, IERC165)
returns (bool)
{
return
interfaceId == type(IERC2981).interfaceId ||
super.supportsInterface(interfaceId);
}
function tokenURI(uint256 tokenId)
public
view
override
returns (string memory)
{
require(_exists(tokenId), "Nonexistent token");
if (!isRevealed) {
return IMetadata(renderer).getPlaceholderURI(tokenId);
}
Kitten storage kitten = kittens[_getOffsetTokenId(tokenId)];
return IMetadata(renderer).getTokenURI(tokenId, kitten, specialMode[tokenId]);
}
/// @notice Royalty metadata.
/// @dev See {IERC165-royaltyInfo}.
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
override
returns (address receiver, uint256 royaltyAmount)
{
require(_exists(tokenId), "Nonexistent token");
return (address(this), (salePrice * royaltyPercent) / 100);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Trees proofs.
*
* The proofs can be generated using the JavaScript library
* https://github.com/miguelmota/merkletreejs[merkletreejs].
* Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
*
* See `test/utils/cryptography/MerkleProof.test.js` for some examples.
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merklee tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
computedHash = _efficientHash(computedHash, proofElement);
} else {
// Hash(current element of the proof + current computed hash)
computedHash = _efficientHash(proofElement, computedHash);
}
}
return computedHash;
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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 Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
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);
}
}
{
"compilationTarget": {
"contracts/Kitten.sol": "Kitten"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 2000
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","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":"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":"id","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"COMMUNITY_SALE_PRICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_GIFTED_KITTENS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_KITTENS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_KITTENS_PER_WALLET","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PUBLIC_SALE_PRICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"claimedFirst","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"claimedSecond","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"communityFirstClaimMerkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"communitySecondClaimMerkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deSauce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getKitten","outputs":[{"components":[{"internalType":"uint8","name":"background","type":"uint8"},{"internalType":"uint8","name":"body","type":"uint8"},{"internalType":"uint8","name":"clothes","type":"uint8"},{"internalType":"uint8","name":"ears","type":"uint8"},{"internalType":"uint8","name":"eyes","type":"uint8"},{"internalType":"uint8","name":"head","type":"uint8"},{"internalType":"uint8","name":"neck","type":"uint8"},{"internalType":"uint8","name":"special","type":"uint8"},{"internalType":"uint8","name":"weapon","type":"uint8"}],"internalType":"struct IKitten.Kitten","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNextTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"enum IKitten.Trait","name":"trait","type":"uint8"}],"name":"getTrait","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"giftKittens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"hasShifted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isCommunitySaleActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPublicSaleActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isRevealed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"kittens","outputs":[{"internalType":"uint8","name":"background","type":"uint8"},{"internalType":"uint8","name":"body","type":"uint8"},{"internalType":"uint8","name":"clothes","type":"uint8"},{"internalType":"uint8","name":"ears","type":"uint8"},{"internalType":"uint8","name":"eyes","type":"uint8"},{"internalType":"uint8","name":"head","type":"uint8"},{"internalType":"uint8","name":"neck","type":"uint8"},{"internalType":"uint8","name":"special","type":"uint8"},{"internalType":"uint8","name":"weapon","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"metadataOffset","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"mintCommunitySale","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numGiftedKittens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"publicSaleParticipants","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renderer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"reserveForGifting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resetMetadataOffset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"salePrice","type":"uint256"}],"name":"royaltyInfo","outputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"royaltyAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"royaltyPercent","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"bool","name":"isAdmin","type":"bool"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"}],"name":"setFirstCommunityListMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_isCommunitySaleActive","type":"bool"}],"name":"setIsCommunitySaleActive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_isPublicSaleActive","type":"bool"}],"name":"setIsPublicSaleActive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bool","name":"special","type":"bool"}],"name":"setKittenMode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"offset","type":"uint256"}],"name":"setMetadataOffset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_renderer","type":"address"}],"name":"setRenderer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setRevealed","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"percent","type":"uint8"}],"name":"setRoyaltyPercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"}],"name":"setSecondCommunityListMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"specialMode","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenCounter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"traitAliases","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"traitRarities","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"enum IKitten.Trait","name":"trait","type":"uint8"},{"internalType":"uint8","name":"value","type":"uint8"}],"name":"updateTrait","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"withdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]