// File: @thirdweb-dev/contracts/extension/interface/IContractMetadata.sol
pragma solidity ^0.8.0;
/// @author thirdweb
/**
* Thirdweb's `ContractMetadata` is a contract extension for any base contracts. It lets you set a metadata URI
* for you contract.
*
* Additionally, `ContractMetadata` is necessary for NFT contracts that want royalties to get distributed on OpenSea.
*/
interface IContractMetadata {
/// @dev Returns the metadata URI of the contract.
function contractURI() external view returns (string memory);
/**
* @dev Sets contract URI for the storefront-level metadata of the contract.
* Only module admin can call this function.
*/
function setContractURI(string calldata _uri) external;
/// @dev Emitted when the contract URI is updated.
event ContractURIUpdated(string prevURI, string newURI);
}
// File: @thirdweb-dev/contracts/extension/ContractMetadata.sol
pragma solidity ^0.8.0;
/// @author thirdweb
/**
* @title Contract Metadata
* @notice Thirdweb's `ContractMetadata` is a contract extension for any base contracts. It lets you set a metadata URI
* for you contract.
* Additionally, `ContractMetadata` is necessary for NFT contracts that want royalties to get distributed on OpenSea.
*/
abstract contract ContractMetadata is IContractMetadata {
/// @dev The sender is not authorized to perform the action
error ContractMetadataUnauthorized();
/// @notice Returns the contract metadata URI.
string public override contractURI;
/**
* @notice Lets a contract admin set the URI for contract-level metadata.
* @dev Caller should be authorized to setup contractURI, e.g. contract admin.
* See {_canSetContractURI}.
* Emits {ContractURIUpdated Event}.
*
* @param _uri keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
*/
function setContractURI(string memory _uri) external override {
if (!_canSetContractURI()) {
revert ContractMetadataUnauthorized();
}
_setupContractURI(_uri);
}
/// @dev Lets a contract admin set the URI for contract-level metadata.
function _setupContractURI(string memory _uri) internal {
string memory prevURI = contractURI;
contractURI = _uri;
emit ContractURIUpdated(prevURI, _uri);
}
/// @dev Returns whether contract metadata can be set in the given execution context.
function _canSetContractURI() internal view virtual returns (bool);
}
// File: contracts/ICheebaNFTToken.sol
pragma experimental ABIEncoderV2;
pragma solidity ^0.8.0;
/**
* @dev Required interface of an ERC1155 compliant contract.
*/
interface ICheebaNFTToken {
function mint(address account, uint256 id, uint256 amount) external;
function mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts
) external;
function renounceMinter() external;
}
// File: @openzeppelin/contracts/utils/Context.sol
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// File: @openzeppelin/contracts/access/Ownable.sol
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// File: contracts/roles/Roles.sol
pragma solidity ^0.8.20;
/**
* @title Roles
* @dev Library for managing addresses assigned to a Role.
*/
library Roles {
struct Role {
mapping(address => bool) bearer;
}
/**
* @dev Give an account access to this role.
*/
function add(Role storage role, address account) internal {
require(!has(role, account), "Roles: account already has role");
role.bearer[account] = true;
}
/**
* @dev Remove an account's access to this role.
*/
function remove(Role storage role, address account) internal {
require(has(role, account), "Roles: account does not have role");
role.bearer[account] = false;
}
/**
* @dev Check if an account has this role.
* @return bool
*/
function has(
Role storage role,
address account
) internal view returns (bool) {
require(account != address(0), "Roles: account is the zero address");
return role.bearer[account];
}
}
// File: contracts/roles/MinterRole.sol
pragma solidity ^0.8.20;
abstract contract MinterRole is Context {
using Roles for Roles.Role;
event MinterAdded(address indexed account);
event MinterRemoved(address indexed account);
Roles.Role private _minters;
constructor() {
_addMinter(_msgSender());
}
modifier onlyMinter() {
require(
isMinter(_msgSender()),
"MinterRole: caller does not have the Minter role"
);
_;
}
function isMinter(address account) public view returns (bool) {
return _minters.has(account);
}
function addMinter(address account) public onlyMinter {
_addMinter(account);
}
function renounceMinter() public {
_removeMinter(_msgSender());
}
function _addMinter(address account) internal {
_minters.add(account);
emit MinterAdded(account);
}
function _removeMinter(address account) internal {
_minters.remove(account);
emit MinterRemoved(account);
}
}
// File: contracts/TradingCardsCrowdSale.sol
pragma solidity ^0.8.20;
contract TradingCardsCrowdSale is Ownable, MinterRole, ContractMetadata {
// Sale start times
uint256 public publicSaleStartTime;
// Sale prices in USD (Equivalent to ETH)
uint256 public publicSalePrice = 0.015 ether; // 0.015
// Sale limits per character
uint16 public publicSaleLimit = 399;
// Mapping to keep track of the amount minted against a particular ID
mapping(uint256 => uint256) private mintedAmount;
mapping(uint256 => uint256) private mintedAmountInPublicSale;
event AddedToAllowlistSale(address[] indexed addr);
event RemovedFromAllowlistSale(address[] indexed addr);
event BuyToken(
address callee,
address indexed buyer,
uint256[] tokenId,
uint256[] tokenQuantity
);
address public deployer;
// Token contract reference
ICheebaNFTToken private tokenAddress;
constructor(
ICheebaNFTToken _token,
uint256 _publicSaleStartTime
) Ownable(msg.sender) {
deployer = msg.sender;
tokenAddress = _token;
publicSaleStartTime = _publicSaleStartTime;
}
function buyTokens(
uint256[] memory tokenId,
uint256[] memory amount,
address buyerAddress
) external payable {
// Check if each tokenId is within the valid range (1 to 10) and validate amount is valid or not
for (uint256 i = 0; i < tokenId.length; i++) {
require(tokenId[i] >= 1 && tokenId[i] <= 10, "Invalid tokenId");
}
// Validate minting limit for the entire batch
require(validateMintLimit(tokenId, amount), "Minting limit exceeded");
uint256 price = getCurrentSalePrice();
uint256 totalPrice = price * calculateTotalAmount(amount);
require(msg.value >= totalPrice, "Insufficient funds");
if (tokenId.length == 1) {
// Single token minting
tokenAddress.mint(buyerAddress, tokenId[0], amount[0]);
mintedAmount[tokenId[0]] += amount[0];
incrementMintedTokens(tokenId[0], amount[0]);
} else {
// Batch minting
tokenAddress.mintBatch(buyerAddress, tokenId, amount);
for (uint256 i = 0; i < tokenId.length; i++) {
mintedAmount[tokenId[i]] += amount[i];
incrementMintedTokens(tokenId[i], amount[i]);
}
}
payable(owner()).transfer(totalPrice);
emit BuyToken(msg.sender, buyerAddress, tokenId, amount);
}
function setMintingLimits(
uint256[] memory tokenId,
uint256[] memory amount
) external onlyOwner {
// Check if each tokenId is within the valid range (1 to 10) and validate amount is valid or not
for (uint256 i = 0; i < tokenId.length; i++) {
require(tokenId[i] >= 1 && tokenId[i] <= 10, "Invalid tokenId");
}
// Validate minting limit for the entire batch
require(validateMintLimit(tokenId, amount), "Minting limit exceeded");
if (tokenId.length == 1) {
// Single token minting
mintedAmount[tokenId[0]] += amount[0];
incrementMintedTokens(tokenId[0], amount[0]);
} else {
// Batch minting
for (uint256 i = 0; i < tokenId.length; i++) {
mintedAmount[tokenId[i]] += amount[i];
incrementMintedTokens(tokenId[i], amount[i]);
}
}
}
// Function to get the current sale price according to the sale time
function getCurrentSalePrice() public view returns (uint256) {
return publicSalePrice;
}
// Function to get the amount minted against a particular ID
function getMintedAmount(uint256 tokenId) external view returns (uint256) {
return mintedAmount[tokenId];
}
function calculateTotalAmount(
uint256[] memory amounts
) private pure returns (uint256 totalAmount) {
for (uint256 i = 0; i < amounts.length; i++) {
totalAmount += amounts[i];
}
return totalAmount;
}
// Function to validate minting limit
function validateMintLimit(
uint256[] memory ids,
uint256[] memory amounts
) internal view returns (bool) {
require(ids.length == amounts.length, "Array length mismatch");
uint256 totalMinted = 0;
uint256 mintingLimit = publicSaleLimit;
for (uint256 i = 0; i < ids.length; i++) {
totalMinted += mintedAmount[ids[i]] + amounts[i];
if (totalMinted <= mintingLimit) {
return true;
}
}
return false;
}
// Function to get the current stage start time and sale stage
function getCurrentStageInfo()
external
view
returns (string memory saleStage)
{
saleStage = "Public Sale";
}
function incrementMintedTokens(uint256 tokenId, uint256 amount) internal {
mintedAmountInPublicSale[tokenId] += amount;
}
// Function to get the token minted amount in Public Sale
function getPublicSaleMintedAmount(
uint256 tokenId
) external view returns (uint256) {
return mintedAmountInPublicSale[tokenId];
}
// @dev enables token transfers, called when owner calls finalize()
function finalization() public onlyOwner {
tokenAddress.renounceMinter();
}
function _canSetContractURI()
internal
view
virtual
override
returns (bool)
{
return msg.sender == deployer;
}
}
{
"compilationTarget": {
"contracts/TradingCardsCrowdSale.sol": "TradingCardsCrowdSale"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"contract ICheebaNFTToken","name":"_token","type":"address"},{"internalType":"uint256","name":"_publicSaleStartTime","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ContractMetadataUnauthorized","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address[]","name":"addr","type":"address[]"}],"name":"AddedToAllowlistSale","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"callee","type":"address"},{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenId","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"tokenQuantity","type":"uint256[]"}],"name":"BuyToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"prevURI","type":"string"},{"indexed":false,"internalType":"string","name":"newURI","type":"string"}],"name":"ContractURIUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"MinterAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"MinterRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address[]","name":"addr","type":"address[]"}],"name":"RemovedFromAllowlistSale","type":"event"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"addMinter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenId","type":"uint256[]"},{"internalType":"uint256[]","name":"amount","type":"uint256[]"},{"internalType":"address","name":"buyerAddress","type":"address"}],"name":"buyTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"contractURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deployer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"finalization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCurrentSalePrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentStageInfo","outputs":[{"internalType":"string","name":"saleStage","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getMintedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getPublicSaleMintedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isMinter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"publicSaleLimit","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"publicSalePrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"publicSaleStartTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceMinter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_uri","type":"string"}],"name":"setContractURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenId","type":"uint256[]"},{"internalType":"uint256[]","name":"amount","type":"uint256[]"}],"name":"setMintingLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]