Contract Name:
CryptoBounks
Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.33;
contract CryptoBounks {
// Errors
error NotOwner();
error NoScripts();
error SoldOut();
error FreeMintSoldOut();
error AlreadyFreeMinted();
error InsufficientPayment();
error ZeroQuantity();
error NonexistentToken();
error NotApproved();
error InvalidTransfer();
// Events
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
// Constants
uint256 public constant MAX_SUPPLY = 9999;
uint256 public constant COST = 0.00015 ether;
string public constant NAME = "Crypto Bounks";
string public constant SYMBOL = "Crypto Bounks";
// State - packed into fewer slots
address private _owner;
uint96 private _currentIndex;
string private _baseURI = "bafybeibiyjwlnfjj6pcriackfz27h42n5njbjtanwxlsqikictqhpwc3vy";
uint128 public MAX_FREE = 9999;
uint128 public MAX_FREE_PER_WALLET = 1;
// ERC721A-style: only store ownership at batch start
mapping(uint256 => address) private _owners;
mapping(address => uint256) private _balances;
mapping(uint256 => address) private _tokenApprovals;
mapping(address => mapping(address => bool)) private _operatorApprovals;
mapping(address => uint256) public minted;
modifier onlyOwner() {
if (msg.sender != _owner) revert NotOwner();
_;
}
modifier noContracts() {
if (tx.origin != msg.sender) revert NoScripts();
_;
}
constructor() {
_owner = msg.sender;
}
// ============ MINT FUNCTIONS ============
function freemint() external noContracts {
uint256 amount = MAX_FREE_PER_WALLET;
uint256 current = _currentIndex;
if (current + amount > MAX_FREE) revert FreeMintSoldOut();
if (current + amount > MAX_SUPPLY) revert SoldOut();
if (minted[msg.sender] != 0) revert AlreadyFreeMinted();
minted[msg.sender] = amount;
_mint(msg.sender, amount);
}
function mint(uint256 amount) external payable {
if (_currentIndex + amount > MAX_SUPPLY) revert SoldOut();
if (msg.value < amount * COST) revert InsufficientPayment();
_mint(msg.sender, amount);
}
function teamMint(uint256 amount) external onlyOwner {
if (_currentIndex + amount > MAX_SUPPLY) revert SoldOut();
_mint(msg.sender, amount);
}
/// @dev ERC721A-style: only write owner at start of batch
function _mint(address to, uint256 quantity) internal {
if (quantity == 0) revert ZeroQuantity();
uint256 startId = _currentIndex;
// Single SSTORE for ownership (ERC721A optimization)
_owners[startId] = to;
_balances[to] += quantity;
// Emit events (required by ERC721 spec)
for (uint256 i; i < quantity;) {
emit Transfer(address(0), to, startId + i);
unchecked { ++i; }
}
_currentIndex = uint96(startId + quantity);
}
// ============ VIEW FUNCTIONS ============
function totalSupply() public view returns (uint256) {
return _currentIndex;
}
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
/// @dev ERC721A-style: walk backwards to find owner
function ownerOf(uint256 tokenId) public view returns (address) {
if (tokenId >= _currentIndex) revert NonexistentToken();
// Walk backwards to find the owner
for (uint256 i = tokenId; ; ) {
address tokenOwner = _owners[i];
if (tokenOwner != address(0)) {
return tokenOwner;
}
unchecked { --i; }
}
}
function owner() public view returns (address) {
return _owner;
}
function name() public pure returns (string memory) {
return NAME;
}
function symbol() public pure returns (string memory) {
return SYMBOL;
}
function tokenURI(uint256 tokenId) public view returns (string memory) {
if (tokenId >= _currentIndex) revert NonexistentToken();
return string(abi.encodePacked("ipfs://", _baseURI, "/", _toString(tokenId), ".json"));
}
// ============ APPROVALS ============
function approve(address to, uint256 tokenId) public {
address tokenOwner = ownerOf(tokenId);
if (msg.sender != tokenOwner && !_operatorApprovals[tokenOwner][msg.sender])
revert NotApproved();
_tokenApprovals[tokenId] = to;
emit Approval(tokenOwner, to, tokenId);
}
function setApprovalForAll(address operator, bool approved) public {
_operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function getApproved(uint256 tokenId) public view returns (address) {
if (tokenId >= _currentIndex) revert NonexistentToken();
return _tokenApprovals[tokenId];
}
function isApprovedForAll(address account, address operator) public view returns (bool) {
return _operatorApprovals[account][operator];
}
// ============ TRANSFERS ============
function transferFrom(address from, address to, uint256 tokenId) public {
_transfer(from, to, tokenId);
}
function safeTransferFrom(address from, address to, uint256 tokenId) public {
_transfer(from, to, tokenId);
}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata) public {
_transfer(from, to, tokenId);
}
function _transfer(address from, address to, uint256 tokenId) internal {
if (ownerOf(tokenId) != from) revert InvalidTransfer();
if (msg.sender != from && !_operatorApprovals[from][msg.sender] && _tokenApprovals[tokenId] != msg.sender)
revert NotApproved();
delete _tokenApprovals[tokenId];
unchecked {
--_balances[from];
++_balances[to];
}
// Write new owner
_owners[tokenId] = to;
// Initialize next slot if needed (ERC721A pattern)
uint256 nextId = tokenId + 1;
if (nextId < _currentIndex && _owners[nextId] == address(0)) {
_owners[nextId] = from;
}
emit Transfer(from, to, tokenId);
}
// ============ ERC165 & ERC2981 ============
function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
return interfaceId == 0x01ffc9a7 || // ERC165
interfaceId == 0x80ac58cd || // ERC721
interfaceId == 0x5b5e139f || // ERC721Metadata
interfaceId == 0x2a55205a; // ERC2981
}
function royaltyInfo(uint256, uint256 salePrice) external view returns (address, uint256) {
return (_owner, (salePrice * 500) / 10000);
}
// ============ ADMIN ============
function setData(string calldata base, uint128 maxFree, uint128 maxFreePerWallet) external onlyOwner {
_baseURI = base;
MAX_FREE = maxFree;
MAX_FREE_PER_WALLET = maxFreePerWallet;
}
function withdraw() external onlyOwner {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success);
}
// ============ UTILS ============
function _toString(uint256 value) internal pure returns (string memory) {
if (value == 0) return "0";
uint256 temp = value;
uint256 digits;
while (temp != 0) { digits++; temp /= 10; }
bytes memory buffer = new bytes(digits);
while (value != 0) {
buffer[--digits] = bytes1(uint8(48 + value % 10));
value /= 10;
}
return string(buffer);
}
}