Hi all. I finally decided to start learning solidity and with the current NFT hype, ERC721 seemed like a good place to start. I would love it someone could let me know if I have made any glaring mistakes - huge thanks in advance to anyone willing to help.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC721/ERC721.sol';
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC721/extensions/ERC721Enumerable.sol';
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/access/Ownable.sol';
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/utils/Counters.sol';
contract TestNFT is ERC721Enumerable, Ownable {
uint256 private constant MAX_KIDS = 100;
bool private hasSaleStarted = true;
uint256[] private _specialIds;
using Strings for uint256;
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
Counters.Counter private _reserved;
mapping(uint256 => string) private _generationURIs;
mapping(uint256 => string) private _specialsURIs;
event Birth(uint256 quantity);
constructor(string memory uri) ERC721("TestNFT","KIDS") {
setGenerationURI(1, uri);
_reserved._value = 5;
}
function giveSpecial(address _to, string memory _uri, uint256 _amount) public onlyOwner() {
require(_reserved.current() > 0, "No more specials to give");
require(_reserved.current() >= _amount, "Not enough specials to give away");
for(uint256 i = 0; i < _amount; i++){
_reserved.decrement();
_tokenIds.increment();
uint mintIndex = _tokenIds.current();
_safeMint(_to, mintIndex);
_specialIds.push(mintIndex);
_specialsURIs[mintIndex] = _uri;
}
}
function getSpecialIds() external view returns(uint256[] memory) {
return _specialIds;
}
function walletOfOwner(address _owner) external view returns(uint256[] memory) {
uint256 tokenCount = balanceOf(_owner);
uint256[] memory tokensId = new uint256[](tokenCount);
for(uint256 i = 0; i < tokenCount; i++){
tokensId[i] = tokenOfOwnerByIndex(_owner, i);
}
return tokensId;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
// check specials
if(bytes(_specialsURIs[tokenId]).length > 0){
return _specialsURIs[tokenId];
}
// concatenate generation with tokenId.
uint256 gen = getTokenGeneration(tokenId);
string memory generationURI = _generationURIs[gen];
return string(abi.encodePacked(generationURI, tokenId.toString()));
}
function totalSold() public view returns (uint256) {
return _tokenIds.current();
}
function currentGeneration() public view returns (uint256) {
return getTokenGeneration(_tokenIds.current());
}
function currentPrice() public view returns (uint256) {
return calculatePrice();
}
function getTokenGeneration(uint256 tokenId_) public pure returns (uint256) {
if (tokenId_ >= 90) {
return 10;
} else if (tokenId_ >= 80) {
return 9;
} else if (tokenId_ >= 70) {
return 8;
} else if (tokenId_ >= 60) {
return 7;
} else if (tokenId_ >= 50) {
return 6;
} else if (tokenId_ >= 40) {
return 5;
} else if (tokenId_ >= 30) {
return 4;
} else if (tokenId_ >= 20) {
return 3;
} else if (tokenId_ >= 10) {
return 2;
}
return 1;
}
function calculatePrice() private view returns (uint256) {
// look ahead to the next token minted
uint256 currentSupply = _tokenIds.current() + 1;
if (currentSupply >= 90) {
return 800000000000000000; // 0.80 ETH: #9900-10000
} else if (currentSupply >= 80) {
return 400000000000000000; // 0.40 ETH: #9000-9899
} else if (currentSupply >= 70) {
return 200000000000000000; // 0.20 ETH: #8500-8999
} else if (currentSupply >= 60) {
return 150000000000000000; // 0.15 ETH: #7500-8499
} else if (currentSupply >= 50) {
return 100000000000000000; // 0.10 ETH: #4500-7499
} else if (currentSupply >= 40) {
return 80000000000000000; // 0.08 ETH: #2600-4499
} else if (currentSupply >= 30) {
return 60000000000000000; // 0.06 ETH: #1000-2599
} else if (currentSupply >= 20) {
return 40000000000000000; // 0.04 ETH: #600-999
} else if (currentSupply >= 10) {
return 30000000000000000; // 0.03 ETH: #300-599
} else {
return 20000000000000000; // 0.02 ETH: #0-299
}
}
// We should include price/generation changes in the pricing check but we are not greedy. If someone is lucky
// enough to bridge a generation with their multi-purchase, let them have the discount :)
function adopt(uint256 numKids) public payable {
require(hasSaleStarted == true, "Sale is paused");
require(_tokenIds.current() <= sellable(), "Sale has already ended");
require(add(_tokenIds.current(), numKids) <= sellable(), "Exceeds maximum kid supply.");
require(numKids > 0 && numKids <= 20, "You can adopt a minimum 1, maximum 20 kids");
require(msg.value >= mul(calculatePrice(), numKids), "Oh No. Amount of Ether sent is not correct.");
for (uint i = 0; i < numKids; i++) {
_tokenIds.increment();
uint mintIndex = _tokenIds.current();
_safeMint(msg.sender, mintIndex);
}
emit Birth(numKids);
}
function sellable() private view returns (uint256) {
return sub(MAX_KIDS, _reserved.current());
}
function setGenerationURI(uint256 generation, string memory uri) public onlyOwner() {
_generationURIs[generation] = uri;
}
function getGenerationURI(uint256 generation) public view onlyOwner returns (string memory) {
return _generationURIs[generation];
}
function getReservedLeft() public view onlyOwner returns (uint256) {
return _reserved.current();
}
function startSale() public onlyOwner {
hasSaleStarted = true;
}
function pauseSale() public onlyOwner {
hasSaleStarted = false;
}
function withdrawAll() public payable onlyOwner {
require(payable(msg.sender).send(address(this).balance));
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
if (a == 0) {
return 0;
}
c = a * b;
assert(c / a == b);
return c;
}
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
}