Skip to content

Instantly share code, notes, and snippets.

@caffeinum
Last active December 3, 2021 16:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save caffeinum/5674a31f148dbe3d48ebe1d250f6be61 to your computer and use it in GitHub Desktop.
Save caffeinum/5674a31f148dbe3d48ebe1d250f6be61 to your computer and use it in GitHub Desktop.
Simplest ERC721 Avatar Collection Template (use TemplateNFT.sol, change values to yours)
// SPDX-License-Identifier: MIT
// Adapted from World of Women: https://etherscan.io/token/0xe785e82358879f061bc3dcac6f0444462d4b5330#readContract
pragma solidity ^0.8.2;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract AvatarNFT is ERC721, ERC721Enumerable, Ownable {
uint256 internal _price; // = 0.03 ether;
uint256 internal _reserved; // = 200;
uint256 public MAX_SUPPLY; // = 10000;
uint256 public MAX_TOKENS_PER_MINT; // = 20;
uint256 public startingIndex;
bool private _saleStarted;
string public PROVENANCE_HASH = "";
string public baseURI;
constructor(
uint256 _startPrice, uint256 _maxSupply,
uint256 _nReserved,
uint256 _maxTokensPerMint,
string memory _uri,
string memory _name, string memory _symbol
) ERC721(_name, _symbol) {
_price = _startPrice;
_reserved = _nReserved;
MAX_SUPPLY = _maxSupply;
MAX_TOKENS_PER_MINT = _maxTokensPerMint;
baseURI = _uri;
}
function _baseURI() internal view override returns (string memory) {
return baseURI;
}
function contractURI() public view returns (string memory) {
return baseURI;
}
function setBaseURI(string calldata uri) public onlyOwner {
baseURI = uri;
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId)
internal
override(ERC721, ERC721Enumerable)
{
super._beforeTokenTransfer(from, to, tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
function _checkSaleAllowed(address)
internal
view
virtual
returns (bool)
{
// override this if you need custom logic
return true;
}
modifier whenSaleStarted() {
require(_saleStarted, "Sale not started");
_;
}
modifier whenSaleAllowed(address _to) {
require(_checkSaleAllowed(_to), "Sale not allowed");
_;
}
function mint(uint256 _nbTokens) external payable whenSaleStarted whenSaleAllowed(msg.sender) {
uint256 supply = totalSupply();
require(_nbTokens <= MAX_TOKENS_PER_MINT, "You cannot mint more than MAX_TOKENS_PER_MINT tokens at once!");
require(supply + _nbTokens <= MAX_SUPPLY - _reserved, "Not enough Tokens left.");
require(_nbTokens * _price <= msg.value, "Inconsistent amount sent!");
for (uint256 i; i < _nbTokens; i++) {
_safeMint(msg.sender, supply + i);
}
}
function flipSaleStarted() external onlyOwner {
_saleStarted = !_saleStarted;
if (_saleStarted && startingIndex == 0) {
setStartingIndex();
}
}
function saleStarted() public view returns(bool) {
return _saleStarted;
}
// Make it possible to change the price: just in case
function setPrice(uint256 _newPrice) external onlyOwner {
_price = _newPrice;
}
function getPrice() public view returns (uint256){
return _price;
}
function getReservedLeft() public view returns (uint256) {
return _reserved;
}
// This should be set before sales open.
function setProvenanceHash(string memory provenanceHash) public onlyOwner {
PROVENANCE_HASH = provenanceHash;
}
// Helper to list all the Tigers of a wallet
function walletOfOwner(address _owner) public view returns(uint256[] memory) {
uint256 tokenCount = balanceOf(_owner);
uint256[] memory tokensId = new uint256[](tokenCount);
for(uint256 i; i < tokenCount; i++){
tokensId[i] = tokenOfOwnerByIndex(_owner, i);
}
return tokensId;
}
function claimReserved(uint256 _number, address _receiver) external onlyOwner {
require(_number <= _reserved, "That would exceed the max reserved.");
uint256 _tokenId = totalSupply();
for (uint256 i; i < _number; i++) {
_safeMint(_receiver, _tokenId + i);
}
_reserved = _reserved - _number;
}
function setStartingIndex() public {
require(startingIndex == 0, "Starting index is already set");
// BlockHash only works for the most 256 recent blocks.
uint256 _block_shift = uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp)));
_block_shift = 1 + (_block_shift % 255);
// This shouldn't happen, but just in case the blockchain gets a reboot?
if (block.number < _block_shift) {
_block_shift = 1;
}
uint256 _block_ref = block.number - _block_shift;
startingIndex = uint(blockhash(_block_ref)) % MAX_SUPPLY;
// Prevent default sequence
if (startingIndex == 0) {
startingIndex = startingIndex + 1;
}
}
function withdraw() public onlyOwner {
uint256 _balance = address(this).balance;
require(payable(msg.sender).send(_balance));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "./AvatarNFT.sol";
contract TemplateNFT is AvatarNFT {
constructor() AvatarNFT(1 ether, 10000, 200, 20, "https://metadata.buildship.dev/api/token/SYMBOL", "Avatar Collection NFT", "SYMBOL") {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment