Skip to content

Instantly share code, notes, and snippets.

@cjinghong
Last active December 14, 2021 06:19
Show Gist options
  • Save cjinghong/7b410363de25b066196a41f0d33fe416 to your computer and use it in GitHub Desktop.
Save cjinghong/7b410363de25b066196a41f0d33fe416 to your computer and use it in GitHub Desktop.
/**
##.......... ..........##
####.......... ...........####
#######.......... ..........#######
#########.......... ...........#########
###########.......... ..........###########
##########.......... ...........##########
###########.......... ..........###########
##########........... ...........##########
###########.......... ..........###########
##########........... ...........##########
###########.................###########
##########.............##########
########..........##########,
##...........##########
..........###########..
...........##########........
..........#############..........
/// ...........#################........... ///
////////.......##########, ###########.......////////
/////////...########## ##########.../////////
/////////#######. ,#######/////////
%%%%/////////### ###/////////%%%%
%%%%%%%%%////////# /////////%%%%%%%%%
%%%%%%%%%% /////// /////// %%%%%%%%%%
%%%%%%%%% %%%%%%%%%
,,,,,,,%%%%%% %%%%%%,,,,,,,
,,,,,,,,% 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