// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/token/ERC721/ERC721.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/access/Ownable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/math/SafeMath.sol";
contract LuckyLlamas is ERC721, Ownable {
using SafeMath for uint256;
struct Whitelist {
bool approved;
uint256 minted;
}
uint256 constant public TOTAL_TOKEN_TO_MINT = 11777;
uint256 constant public OWNER_MINT = 111;
uint256 constant public WHITELIST_MINT_PER_USER = 5;
uint256 constant public PUBLIC_MINT_PER_USER = 10;
uint256 constant public ITEM_PRICE = 0.0777 ether; // 0.0777 ETH;
uint256 public mintedTokens;
uint256 public startingIpfsId;
uint256 private _lastIpfsId;
address public fundWallet;
bool public isSaleActive;
bool public isPreSaleActive;
mapping(address => Whitelist) public whitelistInfo;
modifier beforeMint(uint256 _howMany, uint256 _limit) {
require(_howMany > 0, "LuckyLlamas: Minimum 1 tokens need to be minted");
require(_howMany <= tokenRemainingToBeMinted(), "LuckyLlamas: Mint amount is greater than the token available");
require(_howMany <= _limit, "LuckyLlamas: Max tokens at once limit reached");
require(ITEM_PRICE.mul(_howMany) == msg.value, "LuckyLlamas: Insufficient ETH to mint");
require(!_isContract(msg.sender), "LuckyLlamas: Caller cannot be contract");
_;
}
constructor (string memory _tokenBaseUri, address _fundWallet) ERC721("Lucky Llamas", "LL") {
_setBaseURI(_tokenBaseUri);
fundWallet = _fundWallet;
}
////////////////////
// Action methods //
////////////////////
function mintLuckyLlamas(uint256 _howMany) beforeMint(_howMany, PUBLIC_MINT_PER_USER) external payable {
require(isSaleActive, "LuckyLlamas: Sale is not active");
for (uint256 i = 0; i < _howMany; i++) {
_mintLuckyLlamas(_msgSender());
}
}
function presaleMint(uint256 _howMany) beforeMint(_howMany, WHITELIST_MINT_PER_USER) external payable {
require(isPreSaleActive, "LuckyLlamas: Presale is not active");
require(isWhitelisted(_msgSender()), "LuckyLlamas: You are not whitelist to mint in presale");
require(whitelistUserMint(_msgSender()) < WHITELIST_MINT_PER_USER, "LuckyLlamas: Presale max limit reached");
for (uint256 i = 0; i < _howMany; i++) {
require(whitelistUserMint(_msgSender()) < WHITELIST_MINT_PER_USER, "LuckyLlamas: Presale max limit reached");
_mintLuckyLlamas(_msgSender());
whitelistInfo[_msgSender()].minted++;
}
}
function mintToOwner(address _to) external onlyOwner {
require(mintedTokens < OWNER_MINT, "LuckyLlamas: Owner already minted");
for (uint256 i = 1; i <= OWNER_MINT; i++) {
require(!_exists(i), "LuckyLlamas: Token already exist.");
_mint(_to, i);
}
mintedTokens = mintedTokens + OWNER_MINT;
}
function _mintLuckyLlamas(address _to) private {
if(mintedTokens == 111) {
_lastIpfsId = random(112, TOTAL_TOKEN_TO_MINT, uint256(uint160(address(_msgSender()))) + 1);
startingIpfsId = _lastIpfsId;
} else {
_lastIpfsId = getIpfsIdToMint();
}
mintedTokens++;
require(!_exists(mintedTokens), "LuckyLlamas: Token already exist.");
_mint(_to, mintedTokens);
_setTokenURI(mintedTokens, uint2str(_lastIpfsId));
}
function _isContract(address _addr) private view returns (bool) {
uint32 _size;
assembly {
_size := extcodesize(_addr)
}
return (_size > 0);
}
function uint2str(uint256 _i) private pure returns (string memory _uintAsString) {
if (_i == 0) {
return "0";
}
uint256 j = _i;
uint256 len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint256 k = len;
while (_i != 0) {
k = k - 1;
uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
bytes1 b1 = bytes1(temp);
bstr[k] = b1;
_i /= 10;
}
return string(bstr);
}
function burn(uint256 tokenId) public {
require(_exists(tokenId), "LuckyLlamas: token does not exist.");
require(_isApprovedOrOwner(_msgSender(), tokenId), "LuckyLlamas: caller is not owner nor approved");
_burn(tokenId);
}
///////////////////
// Query methods //
///////////////////
function exists(uint256 _tokenId) external view returns (bool) {
return _exists(_tokenId);
}
function isWhitelisted(address _address) public view returns(bool) {
return whitelistInfo[_address].approved;
}
function whitelistUserMint(address _address) public view returns(uint256) {
return whitelistInfo[_address].minted;
}
function tokenRemainingToBeMinted() public view returns (uint256) {
return TOTAL_TOKEN_TO_MINT.sub(mintedTokens);
}
function isAllTokenMinted() public view returns (bool) {
return mintedTokens == TOTAL_TOKEN_TO_MINT;
}
function getIpfsIdToMint() public view returns(uint256 _nextIpfsId) {
require(!isAllTokenMinted(), "LuckyLlamas: All tokens have been minted");
if(_lastIpfsId == TOTAL_TOKEN_TO_MINT && mintedTokens < TOTAL_TOKEN_TO_MINT) {
_nextIpfsId = 112;
} else if(mintedTokens < TOTAL_TOKEN_TO_MINT) {
_nextIpfsId = _lastIpfsId + 1;
}
}
function isApprovedOrOwner(address _spender, uint256 _tokenId) external view returns (bool) {
return _isApprovedOrOwner(_spender, _tokenId);
}
//random number
function random(
uint256 from,
uint256 to,
uint256 salty
) private view returns (uint256) {
uint256 seed =
uint256(
keccak256(
abi.encodePacked(
block.timestamp +
block.difficulty +
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (block.timestamp)) +
block.gaslimit +
((uint256(keccak256(abi.encodePacked(_msgSender())))) / (block.timestamp)) +
block.number +
salty
)
)
);
return seed.mod(to - from) + from;
}
/////////////
// Setters //
/////////////
function addToWhitelistMultiple(address[] memory _addresses) external onlyOwner {
for(uint256 i = 0; i < _addresses.length; i++) {
addToWhitelist(_addresses[i]);
}
}
function addToWhitelist(address _address) public onlyOwner {
whitelistInfo[_address].approved = true;
}
function removeFromWhitelist(address _address) external onlyOwner {
whitelistInfo[_address].approved = false;
}
function startSale() external onlyOwner {
isSaleActive = true;
}
function stopSale() external onlyOwner {
isSaleActive = false;
}
function startPreSale() external onlyOwner {
isPreSaleActive = true;
}
function stopPreSale() external onlyOwner {
isPreSaleActive = false;
}
function changeFundWallet(address _fundWallet) external onlyOwner {
fundWallet = _fundWallet;
}
function withdrawETH(uint256 _amount) external onlyOwner {
payable(fundWallet).transfer(_amount);
}
function setTokenURI(uint256 _tokenId, string memory _uri) external onlyOwner {
_setTokenURI(_tokenId, _uri);
}
function setBaseURI(string memory _baseURI) external onlyOwner {
_setBaseURI(_baseURI);
}
function _beforeTokenTransfer(address _from, address _to, uint256 _tokenId) internal virtual override(ERC721) {
super._beforeTokenTransfer(_from, _to, _tokenId);
}
}
{
"compilationTarget": {
"LuckyLlamas.sol": "LuckyLlamas"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"string","name":"_tokenBaseUri","type":"string"},{"internalType":"address","name":"_fundWallet","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"ITEM_PRICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OWNER_MINT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PUBLIC_MINT_PER_USER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_TOKEN_TO_MINT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WHITELIST_MINT_PER_USER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"addToWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_addresses","type":"address[]"}],"name":"addToWhitelistMultiple","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_fundWallet","type":"address"}],"name":"changeFundWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"exists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fundWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getIpfsIdToMint","outputs":[{"internalType":"uint256","name":"_nextIpfsId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isAllTokenMinted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"isApprovedOrOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPreSaleActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isSaleActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"isWhitelisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_howMany","type":"uint256"}],"name":"mintLuckyLlamas","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"}],"name":"mintToOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"mintedTokens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_howMany","type":"uint256"}],"name":"presaleMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"removeFromWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_baseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"string","name":"_uri","type":"string"}],"name":"setTokenURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startPreSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startingIpfsId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stopPreSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stopSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenRemainingToBeMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelistInfo","outputs":[{"internalType":"bool","name":"approved","type":"bool"},{"internalType":"uint256","name":"minted","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"whitelistUserMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawETH","outputs":[],"stateMutability":"nonpayable","type":"function"}]