Last active
December 14, 2021 06:19
-
-
Save cjinghong/7b410363de25b066196a41f0d33fe416 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
##.......... ..........## | |
####.......... ...........#### | |
#######.......... ..........####### | |
#########.......... ...........######### | |
###########.......... ..........########### | |
##########.......... ...........########## | |
###########.......... ..........########### | |
##########........... ...........########## | |
###########.......... ..........########### | |
##########........... ...........########## | |
###########.................########### | |
##########.............########## | |
########..........##########, | |
##...........########## | |
..........###########.. | |
...........##########........ | |
..........#############.......... | |
/// ...........#################........... /// | |
////////.......##########, ###########.......//////// | |
/////////...########## ##########...///////// | |
/////////#######. ,#######///////// | |
%%%%/////////### ###/////////%%%% | |
%%%%%%%%%////////# /////////%%%%%%%%% | |
%%%%%%%%%% /////// /////// %%%%%%%%%% | |
%%%%%%%%% %%%%%%%%% | |
,,,,,,,%%%%%% %%%%%%,,,,,,, | |
,,,,,,,,% Loot Explorers .#,,,,,,,, | |
,,,,,,,, ,,,,,,,, | |
,,,, ,,,, | |
*/ | |
//SPDX-License-Identifier: Unlicense | |
pragma solidity ^0.8.0; | |
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | |
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; | |
import "@openzeppelin/contracts/utils/Counters.sol"; | |
import "./Helpers.sol"; | |
interface LootInterface { | |
function ownerOf(uint256 tokenId) external view returns (address owner); | |
} | |
contract LootExplorers is ERC721, Ownable, ReentrancyGuard { | |
using Counters for Counters.Counter; | |
Counters.Counter private tokenCounter; | |
// 8000 for sale, 3 reserved for team to mint. | |
uint256 public constant MAX_SUPPLY = 8003; | |
uint256 public constant MAX_PER_WALLET_OG_WHITELIST = 5; | |
uint256 public constant MAX_PER_MINT = 5; | |
uint256 public constant PRICE = 0.04 ether; | |
address public immutable lootAddress; | |
// Global stop/start sale | |
bool public saleIsActive = false; | |
// Current sale stage determines who can mint | |
// loot | whitelist | public | |
string public saleStage = "loot"; | |
bytes32 public communityWhitelistMerkleRoot; | |
// OG and whitelist are both limited to a max of 5 per wallet | |
mapping(address => uint256) private ogMintCount; | |
mapping(address => uint256) private whitelistMintCount; | |
uint16[] private unmintedNonownerLootIds; | |
// Pre-revealed baseUri | |
string private _baseURIextended = | |
"ipfs://bafybeifpzotydvs3lswq556szkxdkfommuzov77mcfpwzzuvlgtbvt4cee/metadata/"; | |
constructor(address _lootAddress, address _newOwner) | |
ERC721("LootExplorers", "EXPLRS") | |
{ | |
lootAddress = _lootAddress; | |
transferOwnership(_newOwner); | |
// Mint 8001 - 8003 for team | |
mint(_newOwner, 8001); | |
mint(_newOwner, 8002); | |
mint(_newOwner, 8003); | |
} | |
// ======================== | |
// MODIFIERS | |
// ======================== | |
modifier isValidMerkleProof( | |
bytes32[] calldata merkleProof, | |
bytes32 merkleRoot | |
) { | |
require( | |
MerkleProof.verify( | |
merkleProof, | |
merkleRoot, | |
keccak256(abi.encodePacked(msg.sender)) | |
), | |
"Address not whitelisted" | |
); | |
_; | |
} | |
modifier canSummon(uint256 amount) { | |
require(saleIsActive, "Sale must be active to mint"); | |
require( | |
(PRICE * amount) <= msg.value, | |
"Ether value sent is not correct" | |
); | |
require(amount <= MAX_PER_MINT, "Summoning too many explorers"); | |
require((totalSupply() + amount) <= MAX_SUPPLY, "No more explorers"); | |
_; | |
} | |
modifier canSummonWithLoots() { | |
require( | |
keccak256(bytes(saleStage)) == keccak256(bytes("loot")), | |
"Loot sale not started" | |
); | |
_; | |
} | |
modifier canSummonWithWhitelist(uint256 amount) { | |
require( | |
keccak256(bytes(saleStage)) == keccak256(bytes("whitelist")), | |
"Whitelist sale not started" | |
); | |
// Unminted token ids should have enough to mint | |
require(unmintedNonownerLootIds.length >= amount, "No more unminted explorers"); | |
_; | |
} | |
modifier canSummonPublic(uint256 amount) { | |
require( | |
keccak256(bytes(saleStage)) == keccak256(bytes("public")), | |
"Public sale not started" | |
); | |
// Unminted token ids should have enough to mint | |
require(unmintedNonownerLootIds.length >= amount, "No more unminted explorers"); | |
_; | |
} | |
// ======================== | |
// PRIVATE FUNCTIONS | |
// ======================== | |
// Mint and then increment the counter | |
function mint(address to, uint256 tokenId) private { | |
_safeMint(to, tokenId); | |
tokenCounter.increment(); | |
} | |
// ======================== | |
// ADMIN FUNCTIONS | |
// ======================== | |
// UPLOAD UNMINTED IDS | |
function setUnmintedTokenIds(uint16[] calldata tokenIds) external onlyOwner { | |
unmintedNonownerLootIds = tokenIds; | |
} | |
function appendToExistingUnmintedTokenIds(uint16[] calldata tokenIds) external onlyOwner { | |
for (uint256 i = 0; i < tokenIds.length; i++) { | |
unmintedNonownerLootIds.push(tokenIds[i]); | |
} | |
} | |
// START / STOP SALE TOGGLE | |
function setSaleStart(bool start) public onlyOwner { | |
saleIsActive = start; | |
} | |
function startLootSale() public onlyOwner { | |
saleStage = "loot"; | |
} | |
function startWhitelistSale() public onlyOwner { | |
saleStage = "whitelist"; | |
} | |
function startPublicSale() public onlyOwner { | |
saleStage = "public"; | |
} | |
function setBaseURI(string memory baseURI) public onlyOwner { | |
_baseURIextended = baseURI; | |
} | |
function withdraw(uint256 amount) public onlyOwner { | |
payable(owner()).transfer(amount); | |
} | |
function withdrawAll() public onlyOwner { | |
payable(owner()).transfer(address(this).balance); | |
} | |
function _baseURI() internal view virtual override returns (string memory) { | |
return _baseURIextended; | |
} | |
function setMerkleRoot(bytes32 _root) public onlyOwner { | |
communityWhitelistMerkleRoot = _root; | |
} | |
// ======================== | |
// PUBLIC FUNCTIONS | |
// ======================== | |
function explorerExists(uint256 tokenId) public view returns (bool) { | |
return _exists(tokenId); | |
} | |
function totalSupply() public view returns (uint256) { | |
return tokenCounter.current(); | |
} | |
// Loot owners mint | |
function summonWithLoots(uint256[] calldata lootIds) | |
external | |
payable | |
nonReentrant | |
canSummonWithLoots | |
canSummon(lootIds.length) | |
{ | |
// Check per wallet limit | |
uint256 amountMinted = ogMintCount[msg.sender]; | |
require( | |
amountMinted + lootIds.length <= MAX_PER_WALLET_OG_WHITELIST, | |
"Minted max amount per wallet" | |
); | |
unchecked { | |
for (uint256 i = 0; i < lootIds.length; i++) { | |
require( | |
LootInterface(lootAddress).ownerOf(lootIds[i]) == | |
msg.sender, | |
"You do not own this loot" | |
); | |
require( | |
!_exists(lootIds[i]), | |
string( | |
abi.encodePacked( | |
"Explorer #", | |
Helpers.toString(lootIds[i]), | |
" already summoned" | |
) | |
) | |
); | |
mint(msg.sender, lootIds[i]); | |
} | |
} | |
// Increment wallet limit minted count | |
ogMintCount[msg.sender] = amountMinted + lootIds.length; | |
} | |
function summonFirstExplorers( | |
uint256 amount, | |
bytes32[] calldata merkleProof | |
) | |
public | |
payable | |
nonReentrant | |
canSummonWithWhitelist(amount) | |
isValidMerkleProof(merkleProof, communityWhitelistMerkleRoot) | |
canSummon(amount) | |
{ | |
// Check per wallet limit | |
uint256 amountMinted = whitelistMintCount[msg.sender]; | |
require( | |
amountMinted + amount <= MAX_PER_WALLET_OG_WHITELIST, | |
"Minted max amount per wallet" | |
); | |
// Count from tokenid 1 to 8000, and mint the next available token | |
unchecked { | |
for (uint256 i = 0; i < amount; i++) { | |
// Mint and pop last index | |
uint256 lastId = unmintedNonownerLootIds[unmintedNonownerLootIds.length - 1]; | |
unmintedNonownerLootIds.pop(); | |
mint(msg.sender, lastId); | |
} | |
} | |
// Increment wallet limit minted count | |
whitelistMintCount[msg.sender] = amountMinted + amount; | |
} | |
function summon(uint256 amount) | |
public | |
payable | |
nonReentrant | |
canSummonPublic(amount) | |
canSummon(amount) | |
{ | |
// Count from startIndex to 8000, and mint the next available token | |
unchecked { | |
for (uint256 i = 0; i < amount; i++) { | |
// Mint and pop last index | |
uint256 lastId = unmintedNonownerLootIds[unmintedNonownerLootIds.length - 1]; | |
unmintedNonownerLootIds.pop(); | |
mint(msg.sender, lastId); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment