// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract NFTBettingGame is ERC721, ERC721URIStorage, Ownable {
uint256 public constant MAX_NFTS = 5;
uint256 public constant BETTING_FEE = 0.01 ether;
uint256 public totalBets;
uint256 public pot;
address payable public winner;
bool public bettingClosed = false;
uint256 public tokenIdList;
uint256 public totalSupply;
uint256 public constant TREASURY_FEE_PERCENTAGE = 5;
uint public treasuryPool;
uint256 private _totalBetAmount;
string public _uri;
mapping(address => uint256) public betAmount;
mapping(address => bool) public hasMinted;
mapping(uint256 => mapping(address => uint256)) public betAmounts;
mapping(address => uint256[]) public winners;
using Counters for Counters.Counter;
Counters.Counter private currentTokenId;
string public baseTokenURI;
constructor(string memory uri) ERC721("NFTBETTING", "BET") {
_uri = uri;
// baseTokenURI = "" ;
}
function mintTo(address recipient) public returns (uint256) {
currentTokenId.increment();
uint256 newItemId = currentTokenId.current();
_safeMint(recipient, newItemId);
return newItemId;
}
function _baseURI() internal view virtual override returns (string memory) {
return baseTokenURI;
}
function setBaseTokenURI(string memory _baseTokenURI) public {
baseTokenURI = _baseTokenURI;
}
function mintNFT() public payable {
require(
msg.value >= BETTING_FEE,
"Not enough ether to participate in the game"
);
require(totalSupply < MAX_NFTS, "Maximum NFTs have been minted");
require(hasMinted[msg.sender] == false, "You already minted an NFT");
uint256 tokenId = currentTokenId.current();
currentTokenId.increment();
totalBets++;
pot += msg.value;
betAmount[msg.sender] = msg.value;
tokenIdList.push(totalSupply);
totalSupply++;
hasMinted[msg.sender] = true;
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, _uri);
}
function placeBet(uint256 tokenId) public payable {
require(bettingClosed == false, "Betting is closed");
require(totalSupply == MAX_NFTS, "All NFTs have been minted");
require(winner == address(0), "Winner has already been chosen");
require(ownerOf(tokenId) != address(0), "Invalid NFT token ID");
uint256 betAmount = msg.value;
require(betAmount > 0, "Bet amount must be greater than 0");
pot += betAmount;
betAmounts[tokenId][msg.sender] += betAmount;
}
function closeBetting() public onlyOwner {
require(totalSupply == MAX_NFTS, "Not all NFTs have been minted");
require(bettingClosed == false, "Betting is already closed");
bettingClosed = true;
}
function contains(
uint256[] memory array,
uint256 value
) private pure returns (bool) {
for (uint256 i = 0; i < array.length; i++) {
if (array[i] == value) {
return true;
}
}
return false;
}
function chooseWinner()
public
onlyOwner
returns (address payable[3] memory)
{
require(totalSupply == MAX_NFTS, "Not all NFTs have been minted");
require(bettingClosed == true, "Betting is still open");
require(winner == address(0), "Winner has already been picked");
address payable[3] memory topWinners;
uint256[3] memory topWinnersAmount;
uint256 totalBetAmount = 0;
for (uint256 i = 0; i < tokenIdList.length; i++) {
address tokenOwner = ownerOf(tokenIdList[i]);
if (tokenOwner == address(0)) {
for (uint256 j = 0; j < 3; j++) {
uint256 amount = betAmounts[tokenIdList[i]][msg.sender];
if (amount > topWinnersAmount[j]) {
for (uint256 k = 2; k > j; k--) {
topWinners[k] = topWinners[k - 1];
topWinnersAmount[k] = topWinnersAmount[k - 1];
}
topWinners[j] = payable(msg.sender);
topWinnersAmount[j] = amount;
break;
}
}
totalBetAmount += betAmounts[tokenIdList[i]][msg.sender];
}
}
uint256 rewardPool = (pot * 8) / 3;
uint256 remainingPool = rewardPool;
for (uint256 i = 0; i < 3; i++) {
if (topWinnersAmount[i] == 0) {
break;
}
uint256 reward = (rewardPool * topWinnersAmount[i]) /
totalBetAmount;
_transfer(address(this), topWinners[i], reward);
remainingPool -= reward;
}
uint256 treasuryAmount = (remainingPool * 2) / 3;
uint256 winnerReward = remainingPool - treasuryAmount;
if (tokenIdList.length > 0) {
winner = payable(
ownerOf(tokenIdList[random() % tokenIdList.length])
);
winner.transfer(winnerReward);
}
treasuryPool += treasuryAmount + remainingPool;
return topWinners;
}
function random() private view returns (uint256) {
return
uint256(
keccak256(
abi.encodePacked(
block.difficulty,
block.timestamp,
tokenIdList
)
)
);
}
function refundBets() public {
require(bettingClosed == true, "Betting is still open");
require(winner != address(0), "Winner has not been picked yet");
require(msg.sender != winner, "Winner cannot refund their bet");
uint256 amount = betAmount[msg.sender];
require(amount > 0, "You did not participate in the betting");
betAmount[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
}