Code to reproduce
ERC721 contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "./SHELL.sol";
contract TurtleMint is ERC721Enumerable, Ownable {
using Strings for uint256;
string public baseURI;
string public baseExtension = ".json";
string public notRevealedUri;
uint256 public cost = 0.05 ether;
uint256 public maxSupply = 8888;
uint256 public oneOfOneStartingIndex = 8868;
uint256 public presaleSupply = 1000;
uint256 public publicSupply = 4444;
uint256 public maxMintAmount = 5;
uint256 public nftPerAddressLimit = 5;
uint256 public totalMinted = 0;
uint256 public costShell = 1;
bool public paused = false;
bool public revealed = false;
bool public onlyWhitelisted = true;
bool public burningPaused = true;
address[] public whitelistedAddresses;
mapping(address => uint256) public addressMintedBalance;
mapping(address => bool) private _whitelist;
address payable public payments;
SHELL public shell;
uint oneOfOneIndex = 8867; //gas friendly bc no calculation when accessing array
uint[] public shellMintingCost = [1, 2, 3, 4, 4]; //last 2 are the same to avoid the unlikely case of pointing to NULL
uint public currentShellMintingCost;
uint[] public turtleBurningCost = [1, 2, 3, 4, 4];
uint public currentTurtleBurningCost;
constructor(
string memory _name,
string memory _symbol,
string memory _initBaseURI,
string memory _initNotRevealedUri,
address _payments,
address _shell
) ERC721(_name, _symbol) {
shell = SHELL(_shell);
setBaseURI(_initBaseURI);
setNotRevealedURI(_initNotRevealedUri);
payments = payable(_payments);
}
// internal
function _baseURI() internal view virtual override returns (string memory) {
return baseURI;
}
// whitelisted minting
/*bytes32 public merkleRoot = "";
mapping(address => bool) public whitelistClaimed;
function whiteListMint(bytes32[] calldata _merkleProof, uint amount) public {
require(!whitelistClaimed[msg.sender], "Address has already claimed.");
bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
require(MerkleProof.verify(_merkleProof, merkleRoot, leaf), "Invalid proof.");
whitelistClaimed[msg.sender] = true;
if(amount == 1) mint(1);
else if(amount == 2) mint(2);
else return;
}
*/
// public
function mint(uint256 _mintAmount) public payable {
uint256 supply = totalSupply();
require(!paused, "the contract is paused");
require(_mintAmount > 0, "need to mint at least 1 NFT");
require(_mintAmount <= maxMintAmount, "max mint amount per session exceeded");
require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");
if (msg.sender != owner()) {
if(onlyWhitelisted) {
require(_whitelist[msg.sender], "Mint: Unauthorized Access");
uint256 ownerMintedCount = addressMintedBalance[msg.sender];
require(ownerMintedCount + _mintAmount <= nftPerAddressLimit, "max NFT per address exceeded");
require(totalMinted + _mintAmount <= presaleSupply);
}
require(totalMinted <= publicSupply, "Only mintable with $SHELL");
require((totalMinted + _mintAmount) <= publicSupply, "Not enough public supply left, try a smaller amount");
require(msg.value >= cost * _mintAmount, "insufficient funds");
}
for (uint256 i = 1; i <= _mintAmount; i++) {
addressMintedBalance[msg.sender]++;
_mint(msg.sender, totalMinted++);
}
}
/*function mintWithShell(uint256 _mintAmount) public payable {
require(totalMinted >= publicSupply);
require(totalMinted < oneOfOneStartingIndex, "Not enough Turtles left! :("); // not higher than 8868 so the 1of1s won't be given away
costShell = currentShellMintingCost;
require(msg.value >= costShell * _mintAmount, "insufficient $SHELL");
shell.burn(_msgSender(), costShell * _mintAmount);
for (uint256 i = 1; i <= _mintAmount; i++){
if(totalMinted % 1111 == 0) {
currentShellMintingCost = shellMintingCost[(totalMinted - 4444) / 1111];
}
addressMintedBalance[msg.sender]++;
_safeMint(msg.sender, totalMinted++); // changed supply + i to totalMinted + i
}
}
function burnTurtle(uint256 tokenID) external payable { // changed made payable
require(burningPaused == false);
require(msg.sender == ownerOf(tokenID));
require(totalMinted < oneOfOneStartingIndex, "There are no more Turtles left to mint!");
if(totalMinted % 1111 == 0) {
currentTurtleBurningCost = turtleBurningCost[(totalMinted - 4444) / 1111];
}
require(msg.value >= currentTurtleBurningCost);
_burn(tokenID);
shell.burn(_msgSender(), currentTurtleBurningCost); // why not msg.sender?
_safeMint(msg.sender, totalMinted++); // not sure; changed to totalMinted instead of supply
}
// maybe it would be smarter to just allow exactly 2 turtles to be burned --> see next function
function burnMultipleTurtles(uint256 [] calldata tokenIDs) external {
require(burningPaused == false);
require(tokenIDs.length > 1, "You need to burn at least 2 Turtles!");
for(uint256 i; i < tokenIDs.length; i++) {
require(msg.sender == ownerOf(tokenIDs[i]));
_burn(tokenIDs[i]); // this would actually burn one and if you dont own the second one that you want to burn you burned one for nothing, might wanna check in advance
if(i % 2 == 1 ) {
_safeMint(msg.sender, totalMinted++); // not sure; changed to totalMinted instead of supply, eigentlich wird das totalMinted ja eh erhöht also braucht man kein +i wenn ichs richtig verstehe
}
}
}
function burnTwoTurtles(uint256 turtle1, uint256 turtle2) external {
require(burningPaused == false);
require(msg.sender == ownerOf(turtle1) && msg.sender == ownerOf(turtle2), "Hey! You can only burn your own Turtles!");
require(turtle1 != turtle2, "Hey! You need to use 2 different Turtles!");
require(totalMinted < maxSupply, "There are no more Turtles left to mint!");
_burn(turtle1);
_burn(turtle2);
if(random(turtle2) <= 5) { // keine ahnung welche zahlen das ausgibt, ist nur mal platzhalter
if(oneOfOneIndex < maxSupply) { //oder kleiner gleich, je nachdem wie arrays zählen (array erstellen, letzes element ausgeben lassen, index anschauen)
_safeMint(msg.sender, oneOfOneIndex++);
}
else { //wenns keine rares mehr gibt
_safeMint(msg.sender, totalMinted++);
}
}
else {
_safeMint(msg.sender, totalMinted++);
}
}
*/
function walletOfOwner(address _owner) public view returns (uint256[] memory){
uint256 ownerTokenCount = balanceOf(_owner);
uint256[] memory tokenIds = new uint256[](ownerTokenCount);
for (uint256 i; i < ownerTokenCount; i++) {
tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
}
return tokenIds;
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory){
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
if(revealed == false) {
return notRevealedUri;
}
string memory currentBaseURI = _baseURI();
return bytes(currentBaseURI).length > 0
? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
: "";
}
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
//if (_msgSender() != address(staking))
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
_transfer(from, to, tokenId);
}
function burn(uint256 tokenID) external {
require(burningPaused == false);
require(msg.sender == ownerOf(tokenID));
_burn(tokenID);
}
function burnMultiple(uint256 [] calldata tokenIDs) external {
require(burningPaused == false);
for(uint256 i; i < tokenIDs.length; i++) {
require(msg.sender == ownerOf(tokenIDs[i]));
_burn(tokenIDs[i]);
if(i % 2 == 1 ) {
// _safeMint(msg.sender, supply + i);
}
}
}
/*function checkWhitelist(address testAddress) external view returns (bool) {
return bytes(currentBaseURI).length > 0 ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension)) : "";
}*/
function checkWhitelist(address testAddress) external view returns (bool) {
if (_whitelist[testAddress] == true) { return true; }
return false;
}
//only owner
function reveal(bool _revealed) public onlyOwner {
revealed = _revealed;
}
function setNftPerAddressLimit(uint256 _limit) public onlyOwner {
nftPerAddressLimit = _limit;
}
function setCost(uint256 _newCost) public onlyOwner {
cost = _newCost;
}
function setShellCost(uint256 _shellCost) external onlyOwner {
costShell = _shellCost;
}
function setmaxMintAmount(uint256 _newmaxMintAmount) public onlyOwner {
maxMintAmount = _newmaxMintAmount;
}
function setBaseURI(string memory _newBaseURI) public onlyOwner {
baseURI = _newBaseURI;
}
function setBaseExtension(string memory _newBaseExtension) public onlyOwner {
baseExtension = _newBaseExtension;
}
function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
notRevealedUri = _notRevealedURI;
}
function pause(bool _state) public onlyOwner {
paused = _state;
}
function setOnlyWhitelisted(bool _state) public onlyOwner {
onlyWhitelisted = _state;
}
function whiteListMany(address[] memory accounts) public onlyOwner {
for (uint256 i; i < accounts.length; i++) {
_whitelist[accounts[i]] = true;
}
}
function setBurningPaused(bool _state) public onlyOwner {
burningPaused = _state;
}
function withdraw() public payable onlyOwner {
(bool success, ) = payable(owner()).call{value: address(this).balance}("");
require(success);
}
}
ERC20 Contract
// SPDX-License-Identifier: MIT LICENSE
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SHELL is ERC20, Ownable, ReentrancyGuard {
// a mapping from an address to whether or not it can mint / burn
mapping(address => bool) controllers;
constructor() ERC20("SHELL", "SHELL") { }
/*
* mints $SHELL to a recipient
* @param to the recipient of the $SHELL
* @param amount the amount of $SHELL to mint
*/
function mint(address to, uint256 amount) external nonReentrant {
require(controllers[msg.sender], "Only controllers can mint");
_mint(to, amount);
}
/*
* burns $SHELL from a holder
* @param from the holder of the $SHELL
* @param amount the amount of $SHELL to burn
*/
function burn(address from, uint256 amount) external nonReentrant {
require(controllers[msg.sender], "Only controllers can burn");
_burn(from, amount);
}
/*
* enables an address to mint / burn
* @param controller the address to enable
*/
function addController(address controller) external onlyOwner {
controllers[controller] = true;
}
/*
* disables an address from minting / burning
* @param controller the address to disbale
*/
function removeController(address controller) external onlyOwner {
controllers[controller] = false;
}
}