Skip to content

Instantly share code, notes, and snippets.

@freddiecabrera
Last active December 7, 2021 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save freddiecabrera/0c9ca1ee72040d5797262ff9a0bb2e87 to your computer and use it in GitHub Desktop.
Save freddiecabrera/0c9ca1ee72040d5797262ff9a0bb2e87 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.7+commit.e28d00a7.js&optimize=false&runs=200&gist=
// contracts/Citizens.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract Citizens is ERC721, Ownable {
using Strings for uint256;
// Counter
using Counters for Counters.Counter;
Counters.Counter private _tokenSupply;
// Constant variables
// ------------------------------------------------------------------------
uint256 public constant TOTAL_SUPPLY = 8800; // Total amount of Citizens
uint256 public constant RESERVED_SUPPLY = 300; // Amount of Citizens reserved for the contract
uint256 public constant MAX_SUPPLY = TOTAL_SUPPLY - RESERVED_SUPPLY; // Maximum amount of Citizens
uint256 public constant PRESALE_SUPPLY = 3000; // Presale supply
uint256 public constant MAX_PER_TX = 20; // Max amount of Citizens per tx (public sale)
uint256 public constant PRICE = 0.04 ether;
// Team addresses
// ------------------------------------------------------------------------
address private constant _a1 = 0x62BDc056706570e1a93e432861f6AEbdf655d970;
address private constant _a2 = 0x533A3437720D83A00c42F9a9127525C0577bD9b7;
address private constant _a3 = 0xA582ad581f44Bf431f5f020242ab91a25170E200;
// State variables
// ------------------------------------------------------------------------
bool public isPresaleActive = false;
bool public isPublicSaleActive = false;
bool public revealed = false;
// Presale arrays
// ------------------------------------------------------------------------
mapping(address => bool) private _presaleEligible;
mapping(address => uint256) private _presaleClaimed;
// URI variables
// ------------------------------------------------------------------------
string private _contractURI;
string public notRevealedURI;
string baseURI;
// Events
// ------------------------------------------------------------------------
event BaseTokenURIChanged(string baseTokenURI);
event ContractURIChanged(string contractURI);
// Constructor
// ------------------------------------------------------------------------
constructor(string memory _initBaseURI, string memory _initNotRevealedUri)
ERC721("ONE WRLD TEST", "CITIZEN ID")
{
setBaseURI(_initBaseURI);
setNotRevealedURI(_initNotRevealedUri);
}
// Modifiers
// ------------------------------------------------------------------------
modifier onlyPresale() {
require(isPresaleActive, "PRESALE_NOT_ACTIVE");
_;
}
modifier onlyPublicSale() {
require(isPublicSaleActive, "PUBLIC_SALE_NOT_ACTIVE");
_;
}
// Anti-bot functions
// ------------------------------------------------------------------------
function isContractCall(address addr) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(addr)
}
return size > 0;
}
// Presale functions
// ------------------------------------------------------------------------
function addToPresaleList(address[] calldata addresses) external onlyOwner {
for (uint256 i = 0; i < addresses.length; i++) {
require(addresses[i] != address(0), "NULL_ADDRESS");
require(!_presaleEligible[addresses[i]], "DUPLICATE_ENTRY");
_presaleEligible[addresses[i]] = true;
_presaleClaimed[addresses[i]] = 0;
}
}
function removeFromPresaleList(address[] calldata addresses)
external
onlyOwner
{
for (uint256 i = 0; i < addresses.length; i++) {
require(addresses[i] != address(0), "NULL_ADDRESS");
require(_presaleEligible[addresses[i]], "NOT_IN_PRESALE");
_presaleEligible[addresses[i]] = false;
}
}
function isEligibleForPresale(address addr) external view returns (bool) {
require(addr != address(0), "NULL_ADDRESS");
return _presaleEligible[addr];
}
function hasClaimedPresale(address addr) external view returns (bool) {
require(addr != address(0), "NULL_ADDRESS");
return _presaleClaimed[addr] == 1;
}
function togglePresaleStatus() external onlyOwner {
isPresaleActive = !isPresaleActive;
}
function togglePublicSaleStatus() external onlyOwner {
isPublicSaleActive = !isPublicSaleActive;
}
// Mint functions
// ------------------------------------------------------------------------
function claimReservedCitizen(uint256 quantity, address addr)
external
onlyOwner
{
require(_tokenSupply.current() >= MAX_SUPPLY, "MUST_REACH_MAX_SUPPLY");
require(_tokenSupply.current() < TOTAL_SUPPLY, "SOLD_OUT");
require(
_tokenSupply.current() + quantity <= TOTAL_SUPPLY,
"EXCEEDS_TOTAL_SUPPLY"
);
for (uint256 i = 0; i < quantity; i++) {
_tokenSupply.increment();
_safeMint(addr, _tokenSupply.current());
}
}
function claimPresaleCitizen() external payable onlyPresale {
uint256 quantity = 1;
require(_presaleEligible[msg.sender], "NOT_ELIGIBLE_FOR_PRESALE");
require(_presaleClaimed[msg.sender] < 1, "ALREADY_CLAIMED");
require(_tokenSupply.current() < PRESALE_SUPPLY, "PRESALE_SOLD_OUT");
require(
_tokenSupply.current() + quantity <= PRESALE_SUPPLY,
"EXCEEDS_PRESALE_SUPPLY"
);
if (msg.sender != owner()) {
require(PRICE * quantity == msg.value, "INVALID_ETH_AMOUNT");
}
for (uint256 i = 0; i < quantity; i++) {
_presaleClaimed[msg.sender] += 1;
_tokenSupply.increment();
_safeMint(msg.sender, _tokenSupply.current());
}
}
function mint(uint256 quantity) external payable onlyPublicSale {
require(tx.origin == msg.sender, "GO_AWAY_BOT_ORIGIN");
require(!isContractCall(msg.sender), "GO_AWAY_BOT_CONTRACT");
require(_tokenSupply.current() < MAX_SUPPLY, "SOLD_OUT");
require(quantity > 0, "QUANTITY_CANNOT_BE_ZERO");
require(quantity <= MAX_PER_TX, "EXCEEDS_MAX_MINT");
require(
_tokenSupply.current() + quantity <= MAX_SUPPLY,
"EXCEEDS_MAX_SUPPLY"
);
if (msg.sender != owner()) {
require(PRICE * quantity == msg.value, "INVALID_ETH_AMOUNT");
}
for (uint256 i = 0; i < quantity; i++) {
_tokenSupply.increment();
_safeMint(msg.sender, _tokenSupply.current());
}
}
function tokensMinted() public view returns (uint256) {
return _tokenSupply.current();
}
// Base URI Functions
// ------------------------------------------------------------------------
function setContractURI(string calldata URI) external onlyOwner {
_contractURI = URI;
emit ContractURIChanged(URI);
}
function contractURI() public view returns (string memory) {
return _contractURI;
}
function _baseURI() internal view override returns (string memory) {
return baseURI;
}
function tokenURI(uint256 tokenId)
public
view
virtual
override
returns (string memory)
{
require(
_exists(tokenId),
"ERC721Metadata: URI query for nonexistent token"
);
if (revealed == false) {
return notRevealedURI;
}
string memory currentBaseURI = _baseURI();
return
bytes(currentBaseURI).length > 0
? string(
abi.encodePacked(
currentBaseURI,
tokenId.toString(),
".json"
)
)
: "";
}
//only owner
function reveal() public onlyOwner {
revealed = true;
}
function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
notRevealedURI = _notRevealedURI;
}
function setBaseURI(string memory _newBaseURI) public onlyOwner {
baseURI = _newBaseURI;
}
// Withdrawal functions
// ------------------------------------------------------------------------
function withdrawAll() external onlyOwner {
uint256 _a1amount = (address(this).balance * 20) / 100;
uint256 _a2amount = (address(this).balance * 40) / 100;
uint256 _a3amount = (address(this).balance * 40) / 100;
require(payable(_a1).send(_a1amount), "FAILED_TO_SEND_TO_A1");
require(payable(_a2).send(_a2amount), "FAILED_TO_SEND_TO_A2");
require(payable(_a3).send(_a3amount), "FAILED_TO_SEND_TO_A3");
}
}
@maximepeabody
Copy link

Hi there, saw you post this on DD, so I thought I would do a quick review. Overall looks good. One thing that might be an improvement is that I don't think the reveal() function and revealed var are necessary. Instead you could simply look at if baseURI is empty or not.

@parseb
Copy link

parseb commented Dec 1, 2021

pragma solidity ^0.8.4; - settle on a fixed pragma.
would be great if I could comment on actual lines.

@dinahjohnson
Copy link

Agree with the previous comments and noticed a possible typo (inconsequential to functionality & security just giving a heads up) line 179 should it be "EXCEEDS_MAX_MINT"? Great job btw!

@freddiecabrera
Copy link
Author

@cybergirldinah, @parseb, and @maximepeabody thank you all so much for taking the time to review the contract. I made the updates suggested 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment