// SPDX-License-Identifier: MIT pragma solidity 0.8.17; import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "./GNET.sol"; import "./Valhalla.sol"; import "./lib/ValhallaPool.sol"; import "./NFTGenesis.sol"; contract NFT is ValhallaPool, Initializable, OwnableUpgradeable, ERC721Upgradeable, ERC721EnumerableUpgradeable, UUPSUpgradeable { using CountersUpgradeable for CountersUpgradeable.Counter; using StringsUpgradeable for uint256; uint256 private randNonce; mapping(uint => OwnedToken) public ownedTokenMap; mapping(uint => Card) public cardMap; mapping(address => uint) public totalValueMap; mapping(address => uint) public rewardMap; mapping(address => uint) public rankRewardClaimedAtMap; GNET public gnetERC20; Valhalla public valhalla; CountersUpgradeable.Counter private _tokenIdCounter; CountersUpgradeable.Counter private _cardIdCounter; NFTGenesis public nftGenesis; // events event Buy(address indexed _from, uint indexed _tokenId); event Farm(uint indexed _tokenId, uint _value); event ClaimReward(address indexed _from, uint value); event ClaimRankReward(address indexed _from, uint value); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize( GNET _gnetERC20, Valhalla _valhalla ) public initializer { // this is not a real name, please check this contract before deploy __ERC721_init("GlobalNetworkNFT", "GNFT"); __ERC721Enumerable_init(); __Ownable_init(); __UUPSUpgradeable_init(); gnetERC20 = _gnetERC20; valhalla = _valhalla; /** * initiate 6 card on deployment */ uint[6] memory initialCardPriceList = [ uint(5000), uint(25000), uint(100000), uint(500000), uint(2500000), uint(10000000) ]; for (uint i = 0; i < initialCardPriceList.length; i++) { uint _cardId = _cardIdCounter.current(); Card storage _card = cardMap[_cardId]; _card.price = initialCardPriceList[_cardId] * 10 ** gnetERC20.decimals(); _card.halfingPercentage = 100; _card.isMintable = true; _cardIdCounter.increment(); } } function _authorizeUpgrade( address newImplementation ) internal override onlyOwner {} // The following functions are overrides required by Solidity. function _beforeTokenTransfer( address from, address to, uint256 tokenId, uint256 batchSize ) internal override(ERC721Upgradeable, ERC721EnumerableUpgradeable) { super._beforeTokenTransfer(from, to, tokenId, batchSize); } function supportsInterface( bytes4 interfaceId ) public view override(ERC721Upgradeable, ERC721EnumerableUpgradeable) returns (bool) { return super.supportsInterface(interfaceId); } function _random(uint256 modulus) private returns (uint256) { randNonce++; return uint256( keccak256( abi.encodePacked(block.timestamp, msg.sender, randNonce) ) ) % modulus; } function tokenURI( uint256 tokenId ) public view override returns (string memory) { _requireMinted(tokenId); string memory baseURI = _baseURI(); OwnedToken memory ownedToken = ownedTokenMap[tokenId]; uint cardId = ownedToken.cardId; return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, cardId.toString())) : ""; } function _baseURI() internal pure override returns (string memory) { return "https://globalnetwork.finance/api/image/"; } /** * calculate the probability of farm percentage */ function _getRandomFarmPercentage() private returns (uint256) { uint256 rand = _random(1000); // 0 - 999 if (rand >= 0 && rand <= 699) { // 0 -> 699 = 70% probability return 5; } if (rand > 699 && rand <= 900) { // 699 -> 900 = 20% probability return 6; } if (rand > 900 && rand <= 991) { // 901 -> 991 = 9% probability return 7; } if (rand > 991 && rand <= 996) { // 992 -> 996 = 0.5% probability return 8; } if (rand > 996 && rand <= 998) { // 997 -> 998 = 0.2% probability return 10; } if (rand == 999) { // 999 = 0.1% probability return 15; } return 8; } /** * calculate reward per seconds base on its own percentage */ function getFarmValue(uint tokenId) public view returns (uint256) { OwnedToken storage _tokenMetadata = ownedTokenMap[tokenId]; Card memory card = cardMap[_tokenMetadata.cardId]; uint dayInSec = 86400; if (block.timestamp > _tokenMetadata.mintedAt + dayInSec * 450) return 0; uint percentage = _tokenMetadata.percentage; uint price = _tokenMetadata.mintingPrice; uint lastFarmedAt = _tokenMetadata.lastFarmedAt; uint baseReward = (price * percentage) / 1000; uint rewardPerSec = baseReward / dayInSec; uint farmValue = (block.timestamp - lastFarmedAt) * rewardPerSec; return (farmValue * card.halfingPercentage) / 100; } function buy(uint cardId) public notInBlacklist { bool is_registered; Rank rank; (is_registered, , rank, , , , , ) = valhalla.accountMap(msg.sender); address feeReceiver = valhalla.feeReceiverAddress(); address feeReceiver2 = valhalla.feeReceiverAddress2(); bool isRankRewardClaimable = valhalla.isRankRewardClaimable(); Card storage _card = cardMap[cardId]; require(isRankRewardClaimable == false, "Market closed"); require(is_registered, "Registration required"); require(_card.price > 0, "Invalid card"); require(_card.isMintable, "Card is not mintable"); uint32[7] memory maxBuy = [ 100_000, 200_000, 1000_000, 5_000_000, 20_000_000, 100_000_000, 500_000_000 ]; for (uint i = 0; i < 7; i++) { if (uint(rank) == i) { require( _card.price + totalValueMap[msg.sender] <= maxBuy[i] * 10 ** gnetERC20.decimals(), "Max Buy Error" ); } } uint distributeGenesis = (_card.price * 5) / 100; gnetERC20.transferFrom( msg.sender, address(this), _card.price - distributeGenesis ); _storeGlobalPool((_card.price * 17) / 100); genesisPoolResolver(distributeGenesis); uint tax = (_card.price * 20) / 100; uint halfTax = tax / 2; // rewardMap[feeReceiver] += halfTax; // rewardMap[feeReceiver2] += halfTax; gnetERC20.transfer(feeReceiver, halfTax); gnetERC20.transfer(feeReceiver2, halfTax); gnetERC20.burn((_card.price * 58) / 100); uint256 tokenId = _tokenIdCounter.current(); OwnedToken storage _purchasedToken = ownedTokenMap[tokenId]; _purchasedToken.cardId = cardId; _purchasedToken.mintingPrice = _card.price; _purchasedToken.mintedAt = block.timestamp; _purchasedToken.lastFarmedAt = block.timestamp; _purchasedToken.percentage = _getRandomFarmPercentage(); _tokenIdCounter.increment(); _safeMint(msg.sender, tokenId); emit Buy(msg.sender, tokenId); } function withdrawAllGenesisPool() public onlyOwner { PoolType storage genesisPool = poolMap[GENESIS_POOL_KEY]; gnetERC20.transfer(msg.sender, genesisPool.claimable); genesisPool.claimable = 0; genesisPool.valueLeft = 0; } function _getReferrerFarmMatchingPercentage( uint level, address referrer ) private view returns (uint) { Rank rank; (, , rank, , , , , ) = valhalla.accountMap(referrer); if ( level >= 1 && level <= 5 && totalValueMap[referrer] >= 5000 * 10 ** gnetERC20.decimals() ) { return 5; } if ( level >= 6 && level <= 10 && rank >= Rank.Common && totalValueMap[referrer] >= 25_000 * 10 ** gnetERC20.decimals() ) { return 1; } if ( level >= 11 && level <= 20 && rank >= Rank.Rare && totalValueMap[referrer] >= 100_000 * 10 ** gnetERC20.decimals() ) { return 1; } if ( level >= 21 && level <= 40 && rank >= Rank.SuperRare && totalValueMap[referrer] >= 500_000 * 10 ** gnetERC20.decimals() ) { return 1; } if ( level >= 41 && level <= 60 && rank >= Rank.Epic && totalValueMap[referrer] >= 2_500_000 * 10 ** gnetERC20.decimals() ) { return 1; } if ( level >= 61 && level <= 80 && rank >= Rank.Legend && totalValueMap[referrer] >= 10_000_000 * 10 ** gnetERC20.decimals() ) { return 1; } if ( level >= 81 && level <= 100 && rank == Rank.SuperLegend && totalValueMap[referrer] >= 50_000_000 * 10 ** gnetERC20.decimals() ) { return 1; } return 0; } function setNFTGenesis(NFTGenesis _nftGenesis) public onlyOwner { nftGenesis = _nftGenesis; } function genesisPoolResolver(uint _genesisValue) private { uint storedGenesisValue = 0; uint amountNftTypes = nftGenesis.amountNftTypes(); address receiver = nftGenesis.receiverAddress(); for (uint256 i = 0; i < amountNftTypes; i++) { ( , uint genesisPercentage, uint totalMinted, uint maxMinted ) = nftGenesis.cardMap(i); uint valueDistributed = ((_genesisValue * genesisPercentage) / 100 / maxMinted) * totalMinted; storedGenesisValue += valueDistributed; if (valueDistributed == 0) continue; nftGenesis.distributeGenesisRewards(i, valueDistributed); } gnetERC20.transferFrom( msg.sender, address(nftGenesis), storedGenesisValue ); gnetERC20.transferFrom( msg.sender, receiver, _genesisValue - storedGenesisValue ); } function farm(uint tokenId) public notInBlacklist { require(ownerOf(tokenId) == msg.sender, "caller is not owner"); require( ownedTokenMap[tokenId].isBlackListed == false, "token blacklisted" ); uint reward = getFarmValue(tokenId); require(reward > 0, "No reward to farm"); uint tax = (reward * 10) / 100; uint halTax = tax / 2; gnetERC20.mintForFarm(halTax, valhalla.feeReceiverAddress()); gnetERC20.mintForFarm(halTax, valhalla.feeReceiverAddress2()); gnetERC20.mintForFarm(reward - tax, msg.sender); Rank rank; address referrer; (, , rank, referrer, , , , ) = valhalla.accountMap(msg.sender); for (uint i = 1; i <= 100; i++) { if (referrer == address(0)) break; uint matchingPercentage = _getReferrerFarmMatchingPercentage( i, referrer ); if (matchingPercentage > 0) { uint uplineReward = (reward * matchingPercentage) / 100; gnetERC20.mintForFarm(uplineReward, address(this)); rewardMap[referrer] += uplineReward; } (, , rank, referrer, , , , ) = valhalla.accountMap(referrer); } ownedTokenMap[tokenId].lastFarmedAt = block.timestamp; emit Farm(tokenId, reward); } function startClaimingRankReward() external { require(msg.sender == address(valhalla), "Only valhalla can call"); PoolType storage globalPool = poolMap[GLOBAL_POOL_KEY]; globalPool.valueLeft = globalPool.claimable; } function stopClaimingRankReward() external { require(msg.sender == address(valhalla), "Only valhalla can call"); PoolType storage globalPool = poolMap[GLOBAL_POOL_KEY]; globalPool.claimable = globalPool.valueLeft; globalPool.valueLeft = 0; } function getMyRankReward( address _accountAddress ) public view returns (uint) { Rank rank; (, , rank, , , , , ) = valhalla.accountMap(_accountAddress); PoolType storage globalPool = poolMap[GLOBAL_POOL_KEY]; bool isRankRewardClaimable = valhalla.isRankRewardClaimable(); if (globalPool.valueLeft == 0) return 0; uint rankRewardClaimableAt = valhalla.rankRewardClaimableAt(); if (isRankRewardClaimable == false) return 0; if (rankRewardClaimedAtMap[_accountAddress] >= rankRewardClaimableAt) return 0; if (rank == Rank.NoRank) return 0; uint commonRankDistribution = 0; uint rareRankDistribution = 0; uint superRareRankDistribution = 0; uint epicRankDistribution = 0; uint legendRankDistribution = 0; uint superLegendRankDistribution = 0; ( commonRankDistribution, rareRankDistribution, superRareRankDistribution, epicRankDistribution, legendRankDistribution, superLegendRankDistribution ) = valhalla.rankDistribution(); uint distribution = 0; uint base = 0; if (rank == Rank.Common) { distribution = commonRankDistribution; base = (globalPool.claimable * 3) / 100; } if (rank == Rank.Rare) { distribution = rareRankDistribution; base = (globalPool.claimable * 7) / 100; } if (rank == Rank.SuperRare) { distribution = superRareRankDistribution; base = (globalPool.claimable * 12) / 100; } if (rank == Rank.Epic) { distribution = epicRankDistribution; base = (globalPool.claimable * 18) / 100; } if (rank == Rank.Legend) { distribution = legendRankDistribution; base = (globalPool.claimable * 26) / 100; } if (rank == Rank.SuperLegend) { distribution = superLegendRankDistribution; base = (globalPool.claimable * 34) / 100; } return base / distribution; } function claimRankReward() public notInBlacklist { Rank rank; (, , rank, , , , , ) = valhalla.accountMap(msg.sender); PoolType storage globalPool = poolMap[GLOBAL_POOL_KEY]; uint myReward = getMyRankReward(msg.sender); uint nftValue = totalValueMap[msg.sender]; uint32[7] memory eligValue = [ 0, 50_000, 200_000, 1_000_000, 5_000_000, 25_000_000, 100_000_000 ]; require(myReward > 0, "No Reward can be claimed"); for (uint i = 0; i < 7; i++) { if (uint(rank) == i && rank != Rank.NoRank) { require( nftValue >= eligValue[i] * 10 ** gnetERC20.decimals(), "Not Eligible" ); } } uint tax = (myReward * 10) / 100; uint halfTax = tax / 2; gnetERC20.transfer(valhalla.feeReceiverAddress(), halfTax); gnetERC20.transfer(valhalla.feeReceiverAddress2(), halfTax); gnetERC20.transfer(msg.sender, myReward - tax); rankRewardClaimedAtMap[msg.sender] = block.timestamp; globalPool.valueLeft -= myReward; emit ClaimRankReward(msg.sender, myReward); } function claimReward() public notInBlacklist { uint _reward = rewardMap[msg.sender]; address feeReceiver = valhalla.feeReceiverAddress(); address feeReceiver2 = valhalla.feeReceiverAddress2(); require(_reward > 0, "No reward to be claimed"); // apply 10% tax uint tax = (_reward * 10) / 100; uint halfTax = tax / 2; gnetERC20.transfer(feeReceiver, halfTax); gnetERC20.transfer(feeReceiver2, halfTax); gnetERC20.transfer(msg.sender, _reward - tax); rewardMap[msg.sender] = 0; emit ClaimReward(msg.sender, _reward - tax); } function blacklistToken(uint tokenId) public onlyAdmin { OwnedToken storage token = ownedTokenMap[tokenId]; token.isBlackListed = true; } function _afterTokenTransfer( address from, address to, uint256 tokenId, uint256 batchSize ) internal virtual override { super._afterTokenTransfer(from, to, tokenId, batchSize); uint256 price = ownedTokenMap[tokenId].mintingPrice; if (from == address(0)) { totalValueMap[to] += price; } else if (to == address(0)) { totalValueMap[from] -= price; } else { totalValueMap[from] -= price; totalValueMap[to] += price; } } modifier onlyAdmin() { require( valhalla.hasRole(valhalla.DEFAULT_ADMIN_ROLE(), msg.sender), "Only Admin can perform action" ); _; } modifier onlyStaff() { require( valhalla.hasRole(valhalla.DEFAULT_ADMIN_ROLE(), msg.sender) || valhalla.hasRole(valhalla.STAFF_ROLE(), msg.sender), "Only Admin can perform action" ); _; } modifier notInBlacklist() { require( valhalla.blacklistedAddressMap(msg.sender) != true, "Address blacklisted" ); _; } modifier onlyGenesis() { require(msg.sender == address(nftGenesis), "Transaction Prohibited"); _; } }