// Contract: NtfULoanGenesis
// Target function: function getWalletQuantities(address wallet) external view returns(TQuantities memory)
 tokenId) internal view virtual returns (bool)
 {
 require(_exists(tokenId), "ERC721: operator query for nonexistent token");
 
 address owner = ERC721.ownerOf(tokenId);
 
 return (spender == owner || getApproved(tokenId) == spender || ERC721.isApprovedForAll(owner, spender));
 }
 function _safeMint(address to, uint tokenId) internal virtual
 {
 _safeMint(to, tokenId, "");
 }
 function _safeMint(address to, uint tokenId, bytes memory _data) internal virtual
 {
 _mint(to, tokenId);
 
 require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
 }
 function _mint(address to, uint tokenId) internal virtual
 {
 require(to != address(0), "ERC721: mint to the zero address");
 require(!_exists(tokenId), "ERC721: token already minted");

 _beforeTokenTransfer(address(0), to, tokenId);

 _balances[to] += 1;
 _owners[tokenId] = to;

 emit Transfer(address(0), to, tokenId);
 }
 function _batchMint(address to, uint[] memory tokenIds) internal virtual
 {
 require(to != address(0), "ERC721: mint to the zero address");
 
 _balances[to] += tokenIds.length;

 for (uint i=0; i < tokenIds.length; i++)
 {
 require(!_exists(tokenIds[i]), "ERC721: token already minted");

 _beforeTokenTransfer(address(0), to, tokenIds[i]);

 _owners[tokenIds[i]] = to;

 emit Transfer(address(0), to, tokenIds[i]);
 }
 }
 function _burn(uint tokenId) internal virtual
 {
 address owner = ERC721.ownerOf(tokenId);

 _beforeTokenTransfer(owner, address(0), tokenId);

 _approve(address(0), tokenId); // Clear approvals

 _balances[owner] -= 1;

 delete _owners[tokenId];

 emit Transfer(owner, address(0), tokenId);
 }
 function _transfer(address from, address to, uint tokenId) internal virtual
 {
 require(ERC721.ownerOf(tokenId)==from, "ERC721: transfer of token that is not own");
 require(to != address(0), "ERC721: transfer to the zero address");

 _beforeTokenTransfer(from, to, tokenId);

 _approve(address(0), tokenId); // Clear approvals from the previous owner

 _balances[from] -= 1;
 _balances[to] += 1;
 _owners[tokenId] = to;

 emit Transfer(from, to, tokenId);
 }
 function _approve(address to, uint tokenId) internal virtual
 {
 _tokenApprovals[tokenId] = to;
 
 emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
 }
 function _checkOnERC721Received(address from,address to,uint tokenId,bytes memory _data) private returns (bool)
 {
 if (to.isContract())
 {
 try
 
 IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data)
 
 returns (bytes4 retval)
 {
 return retval == IERC721Receiver(to).onERC721Received.selector;
 }
 catch (bytes memory reason)
 {
 if (reason.length==0)
 {
 revert("ERC721: transfer to non ERC721Receiver implementer");
 }
 else
 {
 assembly { revert(add(32, reason), mload(reason)) } //// solhint-disable-next-line no-inline-assembly
 }
 }
 }
 else
 {
 return true;
 }
 }
 function _beforeTokenTransfer(address from, address to, uint tokenId) internal virtual
 {
 //
 }
}
//==============================================================================
abstract contract ERC721Enumerable is ERC721, IERC721Enumerable
{
 mapping(address => mapping(uint => uint)) private _ownedTokens; // Mapping from owner to list of owned token IDs
 mapping(uint => uint) private _ownedTokensIndex; // Mapping from token ID to index of the owner tokens list
 mapping(uint => uint) private _allTokensIndex; // Mapping from token id to position in the allTokens array

 uint[] private _allTokens; // Array with all token ids, used for enumeration

 function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool)
 {
 return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
 }
 function totalSupply() public view virtual override returns (uint)
 {
 return _allTokens.length;
 }
 function tokenOfOwnerByIndex(address owner, uint index) public view virtual override returns (uint)
 {
 require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
 
 return _ownedTokens[owner][index];
 }
 function tokenByIndex(uint index) public view virtual override returns (uint)
 {
 require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds");
 
 return _allTokens[index];
 }
 function _beforeTokenTransfer(address from,address to,uint tokenId) internal virtual override
 {
 super._beforeTokenTransfer(from, to, tokenId);

 if (from == address(0)) _addTokenToAllTokensEnumeration(tokenId);
 else if (from != to) _removeTokenFromOwnerEnumeration(from, tokenId);
 
 if (to == address(0)) _removeTokenFromAllTokensEnumeration(tokenId);
 else if (to != from) _addTokenToOwnerEnumeration(to, tokenId);
 }
 function _addTokenToOwnerEnumeration(address to, uint tokenId) private
 {
 uint length = ERC721.balanceOf(to);
 
 _ownedTokens[to][length] = tokenId;
 _ownedTokensIndex[tokenId] = length;
 }
 function _addTokenToAllTokensEnumeration(uint tokenId) private
 {
 _allTokensIndex[tokenId] = _allTokens.length;
 
 _allTokens.push(tokenId);
 }
 function _removeTokenFromOwnerEnumeration(address from, uint tokenId) private
 {
 // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
 // then delete the last slot (swap and pop).

 uint lastTokenIndex = ERC721.balanceOf(from) - 1;
 uint tokenIndex = _ownedTokensIndex[tokenId];

 // When the token to delete is the last token, the swap operation is unnecessary
 if (tokenIndex != lastTokenIndex) {
 uint lastTokenId = _ownedTokens[from][lastTokenIndex];

 _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
 _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
 }

 // This also deletes the contents at the last position of the array
 delete _ownedTokensIndex[tokenId];
 delete _ownedTokens[from][lastTokenIndex];
 }
 function _removeTokenFromAllTokensEnumeration(uint tokenId) private
 {
 // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
 // then delete the last slot (swap and pop).

 uint lastTokenIndex = _allTokens.length - 1;
 uint tokenIndex = _allTokensIndex[tokenId];

 // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
 // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
 // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
 uint lastTokenId = _allTokens[lastTokenIndex];

 _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
 _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

 // This also deletes the contents at the last position of the array
 delete _allTokensIndex[tokenId];
 _allTokens.pop();
 }
}
//==============================================================================
contract NtfULoanGenesis is ERC721Enumerable
{
 using Address for address;
 using Strings for uint;

 modifier callerIsUser()
 {
 require(tx.origin == msg.sender, "The caller is another contract");
 _;
 }

 event onWidthdrawal(address from, address to, uint amount);
 event Reserved(address wallet, uint amount);
 event SetRaffleDates(uint startDate, uint endDate);
 event SetPresalesDate(uint newDate, uint oldDate);
 event SetSalesDate(uint newDate, uint oldDate);
 event SetWhitelistPrice(uint newPrice);
 event SetSalesPrice(uint newPrice);
 event SetVIPPrice(uint newPrice);
 
 uint private totalTokens = 3555; 
 uint private maxMintable = 3333;
 uint private leftTokenCount = totalTokens;
 uint private mintedTokenCount = 0;
 uint private generatedTokenCount = 0;

 string private baseURI = '/';

 address private ownerWallet;

 uint[] whitelistPrices = [ 0.2 ether, 0.2 ether ];
 uint[] salesPrices = [ 0.3 ether, 0.3 ether ];
 uint[] vipPrices = [ 0.2 ether, 0.2 ether ];

 uint public whitelistPrice;
 uint public salesPrice;
 uint public vipPrice;

 uint public raffleStartDate = 1656262800;
 uint public raffleEndDate = 1656277199;
 uint public presalesDate = 1656277200;
 uint public salesDate = 1656338400;

 string private signHeader = "\x19Ethereum Signed Message:\n32";

 mapping(bytes32 => bool) private proposedHashes; // used to avoid using the same hash on CreateLoan calls

 mapping(address => uint) private walletMintedTokenIds;

 mapping(address => uint) private mintedQuantities;
 mapping(address => uint) private vipQuantities;
 mapping(address => uint) private whitelistedQuantities;
 mapping(address => uint) private claimedQuantities;

 mapping(uint => uint) private mintedTokenTimestamps;

 string private magicLettersCode = "FFFNTNTAFAANAFOANTNTFNTLONOFNFNNTNOFFAAUTOOUNTNFNNANAFTNOFNNLTOLNNNOFNNNFTTFFNFTNTNFNOLUTTTUTUTATNFL";
 mapping(uint => string) private tokenMagicLetters;

 constructor() ERC721("Genesis Alpha", "ULGAP") // temporary Symbol and title
 //constructor() ERC721("ULoan Genesis Pass", "ULGP")
 {
 ownerWallet = msg.sender;

 uint priceIdx = 0;
 if (block.chainid!=1) priceIdx = 1;

 whitelistPrice = whitelistPrices[priceIdx];
 salesPrice = salesPrices[priceIdx];
 vipPrice = vipPrices[priceIdx];
 }
 //------------------------------------------------------------------------
 function isERC721ReceivedCheck(address from,address to,uint tokenId,bytes memory _data) private returns (bool)
 {
 if (to.isContract())
 {
 try
 
 IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data)
 
 returns (bytes4 retval)
 {
 return retval == IERC721Receiver(to).onERC721Received.selector;
 }
 catch (bytes memory reason)
 {
 if (reason.length==0)
 {
 revert("ERC721: transfer to non ERC721Receiver implementer");
 }
 else
 {
 assembly { revert(add(32, reason), mload(reason)) } //// solhint-disable-next-line no-inline-assembly
 }
 }
 }
 else
 {
 return true;
 }
 }
 //------------------------------------------------------------------------
 function setBaseTokenURI(string memory newUri) external onlyOwner
 {
 baseURI = newUri;
 }
 //------------------------------------------------------------------------
 function baseTokenURI() external view returns (string memory)
 {
 return baseURI;
 }
 //------------------------------------------------------------------------
 function getAvailableTokens() external view returns (uint)
 {
 return leftTokenCount;
 }
 //------------------------------------------------------------------------
 function _baseURI() internal view virtual override returns (string memory)
 {
 return baseURI;
 }
 //------------------------------------------------------------------------
 function getTokenIdsByWallet(address walletAddress) external view returns(uint[] memory)
 {
 require(walletAddress!=address(0), "BlackHole wallet is not a real owner");
 
 uint count = balanceOf(walletAddress);
 uint[] memory result = new uint[](count);
 
 for (uint i=0; i<count; i++)
 {
 result[i] = tokenOfOwnerByIndex(walletAddress, i);
 }
 
 return result;
 }
 //---------------------------------------------------------------------------
 function setWhitelistPrice(uint newPrice) external onlyOwner
 {
 whitelistPrice = newPrice;

 emit SetWhitelistPrice(newPrice);
 }
 //---------------------------------------------------------------------------
 function setVIPPrice(uint newPrice) external onlyOwner
 {
 vipPrice = newPrice;

 emit SetVIPPrice(newPrice);
 }
 //---------------------------------------------------------------------------
 function getVIPPrice() external view returns(uint price)
 {
 return vipPrice;
 }
 //---------------------------------------------------------------------------
 function setSalesPrice(uint newPrice) external onlyOwner
 {
 salesPrice = newPrice;

 emit SetSalesPrice(newPrice);
 }
 //---------------------------------------------------------------------------
 function reserve(uint amount) external onlyOwner
 {
 require(leftTokenCount >= amount, "Not enough tokens left to reserve anymore");

 for (uint i=0; i < amount; i++)
 {
 generatedTokenCount++;
 
 mintedTokenTimestamps[generatedTokenCount] = block.timestamp;
 
 _safeMint(msg.sender, generatedTokenCount);

 setTokenMagicLetter(generatedTokenCount);
 }

 leftTokenCount = totalTokens - generatedTokenCount;

 emit Reserved(msg.sender, amount);
 }
 //---------------------------------------------------------------------------
 function mint(address toWallet, uint quantity) external payable // toWallet is used for compatibility with crossmint.io
 {
 require(block.timestamp>=salesDate, "Minting is closed"); // mint possible only during public sales
 require(toWallet!=address(0), "Blackhole forbidden");
 require(salesPrice!=0, "Invalid internal price");
 require(quantity>0 && quantity<=2, "Invalid NFT quantity");
 require(msg.value==salesPrice*quantity, "Send exact Amount to claim your Nft");
 require(leftTokenCount > 0, "No tokens left to be claimed");
 require(mintedTokenCount<maxMintable, "Sold-out");
 require(mintedTokenCount+quantity<=maxMintable, "Not enough NFT left to mint");

 uint qty = mintedQuantities[toWallet] + quantity;

 require(qty<=2, "Too many claimed"); 

 mintedQuantities[toWallet] += quantity;

 for (uint i=0; i < quantity; i++)
 {
 mintedTokenCount++;
 generatedTokenCount++;
 leftTokenCount--;

 mintedTokenTimestamps[generatedTokenCount] = block.timestamp;

 _mint(toWallet, generatedTokenCount);

 setTokenMagicLetter(generatedTokenCount);
 }
 }
 //---------------------------------------------------------------------------
 function whitelistMint(address toWallet, uint quantity, bytes32 proposedHash,uint8 v,bytes32 r,bytes32 s) external payable // toWallet is used for compatibility with crossmint.io
 {
 //----- Signed function checker

 bool isProposedHashedUsed = proposedHashes[proposedHash];

 require(isProposedHashedUsed==false, "Bad Hash");

 proposedHashes[proposedHash] = true;

 bytes32 messageDigest = keccak256(abi.encodePacked(signHeader, proposedHash));
 bool isFromAdmin = (ecrecover(messageDigest, v, r, s)==admin());

 require(isFromAdmin==true, "Bad call");

 //----- 

 require(block.timestamp>=presalesDate, "Pre-minting is not opened"); 
 require(block.timestamp<salesDate, "Pre-minting is closed");
 require(toWallet!=address(0), "Blackhole forbidden");
 require(quantity>0 && quantity<=2, "Invalid NFT quantity");
 require(whitelistPrice!=0, "Invalid internal price");
 require(msg.value==whitelistPrice*quantity, "Send exact Amount to claim your Nft");
 require(leftTokenCount > 0, "No tokens left to be claimed");
 require(mintedTokenCount<maxMintable, "Sold-out");
 require(mintedTokenCount+quantity<=maxMintable, "Not enough NFT left to mint");

 uint qty = whitelistedQuantities[toWallet] + quantity;

 require(qty<=2, "Too many claimed"); 

 whitelistedQuantities[toWallet] += quantity;

 for (uint i=0; i < quantity; i++)
 {
 mintedTokenCount++;
 generatedTokenCount++;
 leftTokenCount--;

 mintedTokenTimestamps[generatedTokenCount] = block.timestamp;

 _mint(toWallet, generatedTokenCount);

 setTokenMagicLetter(generatedTokenCount);
 }
 }
 //---------------------------------------------------------------------------
 function vipMint(address toWallet, uint quantity, bytes32 proposedHash,uint8 v,bytes32 r,bytes32 s) external payable // toWallet is used for compatibility with crossmint.io
 {
 //----- Signed function checker

 bool isProposedHashedUsed = proposedHashes[proposedHash];

 require(isProposedHashedUsed==false, "Bad Hash");

 proposedHashes[proposedHash] = true;

 bytes32 messageDigest = keccak256(abi.encodePacked(signHeader, proposedHash));
 bool isFromAdmin = (ecrecover(messageDigest, v, r, s)==admin());

 require(isFromAdmin==true, "Bad call");

 //-----
 
 require(quantity>0 && quantity<=3, "Invalid NFT quantity");
 require(leftTokenCount>= quantity, "No tokens left to be minted");
 require(vipPrice!=0, "Invalid internal price");
 require(msg.value==vipPrice*quantity, "Bad price amount");
 require(getGenesisPassMode()!=0, "Mint is closed");

 uint qty = vipQuantities[toWallet] + quantity;

 require(qty<=3, "You cannot mint any more"); 

 //-----

 vipQuantities[toWallet] += quantity;

 for (uint i=0; i < quantity; i++)
 {
 mintedTokenCount++;
 generatedTokenCount++;
 leftTokenCount--;

 mintedTokenTimestamps[generatedTokenCount] = block.timestamp;

 _mint(toWallet, generatedTokenCount);

 setTokenMagicLetter(generatedTokenCount);
 }
 }
 //---------------------------------------------------------------------------
 function claim(uint quantity, bytes32 proposedHash,uint8 v,bytes32 r,bytes32 s) external 
 {
 //----- Signed function checker

 bool isProposedHashedUsed = proposedHashes[proposedHash];

 require(isProposedHashedUsed==false, "Bad Hash");

 proposedHashes[proposedHash] = true;

 bytes32 messageDigest = keccak256(abi.encodePacked(signHeader, proposedHash));
 bool isFromAdmin = (ecrecover(messageDigest, v, r, s)==admin());

 require(isFromAdmin==true, "Bad call");

 //-----
 
 require(getGenesisPassMode()>1, "Claim is not active");
 require(leftTokenCount>= quantity, "No tokens left to be claimed");

 claimedQuantities[msg.sender] += quantity;

 for (uint i=0; i < quantity; i++)
 {
 generatedTokenCount++;
 leftTokenCount--;

 mintedTokenTimestamps[generatedTokenCount] = block.timestamp;

 _mint(msg.sender, generatedTokenCount);

 setTokenMagicLetter(generatedTokenCount);
 }
 }
 //---------------------------------------------------------------------------
 function hasClaimed(address wallet) external view returns(bool)
 {
 return claimedQuantities[wallet]!=0;
 }