Pending Treats that are not resolved will remain stuck in the contract, preventing the option for other users to mint that specific tokenId. Also, the user will not be able to get a refund of the initial ETH amount sent in case the contract owner decides to do something with the NFT.
Set a timeframe in which users have to resolve the trick, if they fail to do so, refund the amount of ETH they originally sent.
contract SpookySwap is ERC721URIStorage, Ownable(msg.sender), ReentrancyGuard {
+ mapping(uint256 => uint256) public pendingNFTsTimestamp; // tokenId => timestamp of the pending purchase getting initialized
+ uint256 public constant TRICK_RESOLUTION_PERIOD = 7 days; // Example of a 7-day timeframe
***
***
function trickOrTreat(string memory _treatName) public payable nonReentrant {
Treat memory treat = treatList[_treatName];
require(treat.cost > 0, "Treat cost not set.");
uint256 costMultiplierNumerator = 1;
uint256 costMultiplierDenominator = 1;
// Generate a pseudo-random number between 1 and 1000
uint256 random =
uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, nextTokenId, block.prevrandao))) % 1000 + 1;
if (random == 1) {
// 1/1000 chance of half price (treat)
costMultiplierNumerator = 1;
costMultiplierDenominator = 2;
} else if (random == 2) {
// 1/1000 chance of double price (trick)
costMultiplierNumerator = 2;
costMultiplierDenominator = 1;
}
// Else, normal price (multiplier remains 1/1)
uint256 requiredCost = (treat.cost * costMultiplierNumerator) / costMultiplierDenominator;
if (costMultiplierNumerator == 2 && costMultiplierDenominator == 1) {
// Double price case (trick)
if (msg.value >= requiredCost) {
// User sent enough ETH
mintTreat(msg.sender, treat);
} else {
// User didn't send enough ETH
// Mint NFT to contract and store pending purchase
uint256 tokenId = nextTokenId;
_mint(address(this), tokenId);
_setTokenURI(tokenId, treat.metadataURI);
nextTokenId += 1;
pendingNFTs[tokenId] = msg.sender;
pendingNFTsAmountPaid[tokenId] = msg.value;
tokenIdToTreatName[tokenId] = _treatName;
+ pendingNFTsTimestamp[tokenId] = block.timestamp;
emit Swapped(msg.sender, _treatName, tokenId);
// User needs to call fellForTrick() to finish the transaction
}
} else {
// Normal price or half price
require(msg.value >= requiredCost, "Insufficient ETH sent for treat");
mintTreat(msg.sender, treat);
}
// Refund excess ETH if any
// q does something happen if msg.sender is not set as payable?
if (msg.value > requiredCost) {
uint256 refund = msg.value - requiredCost;
(bool refundSuccess,) = msg.sender.call{value: refund}("");
require(refundSuccess, "Refund failed");
}
}
***
***
function resolveTrick(uint256 tokenId) public payable nonReentrant {
require(pendingNFTs[tokenId] == msg.sender, "Not authorized to complete purchase");
+ require(pendingNFTsTimestamp[tokenId] > 0, "NFT not pending");
string memory treatName = tokenIdToTreatName[tokenId];
Treat memory treat = treatList[treatName];
uint256 requiredCost = treat.cost * 2; // Double price
uint256 amountPaid = pendingNFTsAmountPaid[tokenId];
uint256 totalPaid = amountPaid + msg.value;
+ if (block.timestamp >= pendingNFTsTimestamp[tokenId] + TRICK_RESOLUTION_PERIOD) {
+ // Refund the user if the resolution period expired
+ delete pendingNFTs[tokenId];
+ delete pendingNFTsAmountPaid[tokenId];
+ delete tokenIdToTreatName[tokenId];
+ (bool success, ) = payable(msg.sender).call{value: amountPaid}("");
+ require(success, "Refund failed");
+ } else {
require(totalPaid >= requiredCost, "Insufficient ETH sent to complete purchase");
// Transfer the NFT to the buyer
_transfer(address(this), msg.sender, tokenId);
delete pendingNFTs[tokenId];
delete pendingNFTsAmountPaid[tokenId];
delete tokenIdToTreatName[tokenId];
// Refund excess ETH if any
if (totalPaid > requiredCost) {
uint256 refund = totalPaid - requiredCost;
(bool refundSuccess,) = msg.sender.call{value: refund}("");
require(refundSuccess, "Refund failed");
}
}
}
}