Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the CF example contract #32

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 106 additions & 32 deletions contracts/cryptofighters/CryptoFightersV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,50 @@
pragma solidity ^0.8.0;

import "erc721a/contracts/ERC721A.sol";
import "./IERC721R.sol";
import "./CryptoFightersPotion.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract CryptoFightersV2 is ERC721A, Ownable, ReentrancyGuard {
uint256 public maxMintSupply = 8000;
uint256 public constant mintPrice = 0.1 ether;
uint256 public constant mintPriceWithPotion = 0.05 ether;
contract CryptoFightersV2 is
IERC721R,
ERC721A,
ERC2981,
Ownable,
ReentrancyGuard
{
uint256 public maxMintSupply = 8000; // Max mintable supply from presale, public sale, and owner
uint256 public constant mintPrice = 0.08 ether; // Mint price for presale and public sale
uint256 public constant mintPriceWithPotion = 0.04 ether; // Mint price for upgrading v1 fighter with potion
uint256 public maxUserMintAmount = 5; // Max mintable amount per user, includes presale and public sale

// Sale Status
bool public publicSaleActive;
bool public presaleActive;
uint256 public amountMinted;
uint256 public refundEndTime;

address public refundAddress;
uint256 public maxUserMintAmount;
mapping(address => uint256) public userMintedAmount;
bytes32 public merkleRoot;
uint256 public refundEndTime; // Time from which refunds will no longer be valid
address public refundAddress; // Address which refunded NFTs will be sent to

mapping(uint256 => bool) public hasV1FighterBeenUpgraded;
mapping(uint256 => uint256) public v2ToV1Mapping;
bytes32 public merkleRoot; // Merkle root for presale participants

mapping(uint256 => bool) public hasRefunded; // users can search if the NFT has been refunded
mapping(uint256 => bool) public hasRevokedRefund; // users can revoke refund capability for e.g staking, airdrops
mapping(uint256 => bool) public isOwnerMint; // if the NFT was freely minted by owner

mapping(uint256 => bool) public hasV1FighterBeenUpgraded; // mapping storing v1 fighters that have been upgraded
mapping(uint256 => uint256) public v2ToV1Mapping; // mapping connecting v2 fighters to v1 fighters

string private baseURI;
IERC721 private immutable cryptoFightersV1;
CryptoFightersPotion private immutable cryptoFightersPotion;

modifier notContract() {
require(!Address.isContract(msg.sender), "No contracts");
_;
}

constructor(address _cryptoFightersV1, address _cryptoFightersPotion)
ERC721A("CryptoFightersAlliance", "CFA")
{
Expand Down Expand Up @@ -62,47 +78,44 @@ contract CryptoFightersV2 is ERC721A, Ownable, ReentrancyGuard {
function mintV2FightersPresale(uint256 quantity, bytes32[] calldata proof)
external
payable
nonReentrant
{
require(presaleActive, "Presale is not active");
require(msg.value == quantity * mintPrice, "Value");
require(
_isAllowlisted(msg.sender, proof, merkleRoot),
"Not whitelisted"
"Not allowlisted"
);
require(
userMintedAmount[msg.sender] + quantity <= maxUserMintAmount,
_numberMinted(msg.sender) + quantity <= maxUserMintAmount,
"Max amount"
);
require(amountMinted + quantity <= maxMintSupply, "Max mint supply");

amountMinted += quantity;
userMintedAmount[msg.sender] += quantity;
require(_totalMinted() + quantity <= maxMintSupply, "Max mint supply");

_safeMint(msg.sender, quantity);
}

function mintV2FightersPublicSale(uint256 quantity)
external
payable
nonReentrant
notContract
{
require(publicSaleActive, "Public sale is not active");
require(msg.value == quantity * mintPrice, "Value");
require(
userMintedAmount[msg.sender] + quantity <= maxUserMintAmount,
_numberMinted(msg.sender) + quantity <= maxUserMintAmount,
"Max amount"
);
require(amountMinted + quantity <= maxMintSupply, "Max mint supply");
require(_totalMinted() + quantity <= maxMintSupply, "Max mint supply");

amountMinted += quantity;
userMintedAmount[msg.sender] += quantity;
_safeMint(msg.sender, quantity);
}

function ownerMint(uint256 quantity) external onlyOwner nonReentrant {
require(amountMinted + quantity <= maxMintSupply, "Max mint supply");
_safeMint(msg.sender, quantity);
function ownerMint(uint256 quantity, address to) external onlyOwner {
require(_totalMinted() + quantity <= maxMintSupply, "Max mint supply");
_safeMint(to, quantity);
for (uint256 i = _currentIndex - quantity; i < _currentIndex; i++) {
isOwnerMint[i] = true;
}
}

function refund(uint256[] calldata tokenIds) external nonReentrant {
Expand All @@ -111,31 +124,92 @@ contract CryptoFightersV2 is ERC721A, Ownable, ReentrancyGuard {
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 tokenId = tokenIds[i];
require(msg.sender == ownerOf(tokenId), "Not owner");
require(!hasRefunded[tokenId], "Already refunded");
require(
!isOwnerMint[tokenId],
"Freely minted NFTs cannot be refunded"
);
hasRefunded[tokenId] = true;
transferFrom(msg.sender, refundAddress, tokenId);

if (v2ToV1Mapping[tokenId] != 0) {
refundAmount += mintPriceWithPotion;
} else {
refundAmount += mintPrice;
}
uint256 tokenAmount = v2ToV1Mapping[tokenId] == 0
? mintPrice
: mintPriceWithPotion;

refundAmount += tokenAmount;
emit Refund(msg.sender, tokenId, tokenAmount);
}

Address.sendValue(payable(msg.sender), refundAmount);
}

function revokeRefund(uint256[] calldata tokenIds) external {
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 tokenId = tokenIds[i];
require(msg.sender == ownerOf(tokenId), "Not owner");
hasRevokedRefund[tokenId] = true;
}
}

function getRefundPrice(uint256 tokenId) public view returns (uint256) {
if (v2ToV1Mapping[tokenId] != 0) {
return mintPriceWithPotion;
} else {
return mintPrice;
}
}

function canBeRefunded(uint256 tokenId) public view returns (bool) {
return
!hasRefunded[tokenId] &&
!isOwnerMint[tokenId] &&
!hasRevokedRefund[tokenId] &&
isRefundGuaranteeActive();
}

function getRefundGuaranteeEndTime() public view returns (uint256) {
return refundEndTime;
}

function isRefundGuaranteeActive() public view returns (bool) {
return (block.timestamp <= refundEndTime);
}

function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC2981, ERC721A)
returns (bool)
{
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
interfaceId == type(IERC2981).interfaceId ||
super.supportsInterface(interfaceId);
}

function withdraw() external onlyOwner {
require(block.timestamp > refundEndTime, "Refund period not over");
uint256 balance = address(this).balance;
Address.sendValue(payable(owner()), balance);
}

function setDefaultRoyalty(address receiver, uint96 feeNumerator)
external
onlyOwner
{
_setDefaultRoyalty(receiver, feeNumerator);
}

function setTokenRoyalty(
uint256 tokenId,
address receiver,
uint96 feeNumerator
) external onlyOwner {
_setTokenRoyalty(tokenId, receiver, feeNumerator);
}

function _baseURI() internal view override returns (string memory) {
return baseURI;
}
Expand Down
20 changes: 20 additions & 0 deletions contracts/cryptofighters/IERC721R.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
// Creator: Exo Digital Labs

pragma solidity ^0.8.4;

interface IERC721R {
event Refund(
address indexed _sender,
uint256 indexed _tokenId,
uint256 _amount
);

function refund(uint256[] calldata tokenIds) external;

function getRefundPrice(uint256 tokenId) external view returns (uint256);

function getRefundGuaranteeEndTime() external view returns (uint256);

function isRefundGuaranteeActive() external view returns (bool);
}