Skip to content

Instantly share code, notes, and snippets.

@jt-metatheory
Created December 4, 2021 02:12
Show Gist options
  • Select an option

  • Save jt-metatheory/c49452664f1f33fa54cf2d2e35732f7e to your computer and use it in GitHub Desktop.

Select an option

Save jt-metatheory/c49452664f1f33fa54cf2d2e35732f7e to your computer and use it in GitHub Desktop.

Revisions

  1. jt-metatheory created this gist Dec 4, 2021.
    268 changes: 268 additions & 0 deletions DuskBreaker.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,268 @@
    // SPDX-License-Identifier: MIT

    pragma solidity ^0.8.0;

    import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";
    import "@openzeppelin/contracts/utils/Counters.sol";
    import "@openzeppelin/contracts/utils/Strings.sol";
    import "@openzeppelin/contracts/utils/math/SafeMath.sol";
    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

    contract DuskBreakers is ERC721Enumerable, Ownable, ReentrancyGuard {
    using Strings for string;
    using SafeMath for uint256;
    using Counters for Counters.Counter;

    /*
    * Enforce the existence of only 10 Breakers.
    */
    uint256 public constant BREAKER_SUPPLY = 10000;
    uint256 public constant breakerPrice = 0.06 ether;
    string public baseTokenURI;
    bool private PAUSE = true;
    bool private fcfsMint = false;

    // Ensure Fairness
    string public BREAKER_PROVENANCE = "";
    uint256 public startingIndexBlock;
    uint256 public startingIndex;

    // tokenID storage
    Counters.Counter private _tokenIdCounter;

    // Presale, Play2Mint
    PresaleConfig public presaleConfig;
    Play2MintConfig public play2MintConfig;

    // Presales and Play2Mints mappings
    mapping(address => uint256) private _presaleList;
    mapping(address => uint256) private _play2MintList;

    // Presale and Play2Mint Structs
    struct PresaleConfig {
    uint256 mintMax;
    }
    struct Play2MintConfig {
    uint256 mintMax;
    }

    // Breaker Contract Events
    event ChangePresaleConfig(
    uint256 _mintMax
    );

    event ChangePlay2MintConfig(
    uint256 _mintMax
    );

    // Events
    event PauseEvent(bool pause);
    //event welcomeToTheDusk(uint256 indexed id);

    // Safety Modifiers
    function isUnpausedAndSupplied() public view returns (bool){
    return (hasSupply(1)) && (!PAUSE);
    }

    modifier saleIsOpen {
    require(hasSupply(1), "Breakers Sold Out!");
    require(!PAUSE, "Sales not open");
    _;
    }

    function setPause(bool _pause) public onlyOwner{
    PAUSE = _pause;
    emit PauseEvent(PAUSE);
    }

    // Open Floodgates for FCFS
    event FCFSEvent(bool pause);
    function setFCFS(bool _fcfsMint) public onlyOwner{
    fcfsMint = _fcfsMint;
    emit FCFSEvent(fcfsMint);
    }

    // Token URI overrides
    function _baseURI() internal view virtual override returns (string memory) {
    return baseTokenURI;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
    baseTokenURI = baseURI;
    }

    // token counters
    function nextToken() public view returns (uint256) {
    return _tokenIdCounter.current();
    }

    constructor(string memory name, string memory symbol) ERC721(name, symbol){

    // First we allow presales to mint during Play2Mint
    // Then, we allow Play2Mint 1 day
    // Finally we open for anyone to mint

    //preseale starts on contract creation
    uint256 _mintMax = 2;

    presaleConfig = PresaleConfig(_mintMax);
    emit ChangePresaleConfig(_mintMax);
    // Play2Mint allow minting config
    uint256 _p2MmintMax = 3;

    play2MintConfig = Play2MintConfig(_p2MmintMax);
    emit ChangePlay2MintConfig(_p2MmintMax);

    setBaseURI("https://duskbreakers.gg/api/breakers/");

    }

    /*
    * Fairness Section....
    * Pre mint, we set the provenance hash to the hash of hashes of the sha256
    * in order of original image generation
    * We then have an offest based on the last block sold, modulo the total supply
    * Thus you know we genereated them in the order we said and anyone including us had no way
    * to know what the starting index will be
    */
    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
    BREAKER_PROVENANCE = provenanceHash;
    }

    // It has gone wrong and the presale didn't fully mint so this allows us to reveal
    function emergencySetStartingIndexBlock() public onlyOwner {
    require(startingIndex == 0, "Starting index is already set");
    startingIndexBlock = block.number;
    }

    function setStartingIndex() public {
    require(startingIndex == 0, "Starting index is already set");
    require(startingIndexBlock != 0, "Starting index block must be set");

    startingIndex = uint(blockhash(startingIndexBlock)) % BREAKER_SUPPLY;
    // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
    if (block.number.sub(startingIndexBlock) > 255) {
    startingIndex = uint(blockhash(block.number - 1)) % BREAKER_SUPPLY;
    }
    // Prevent default sequence
    if (startingIndex == 0) {
    startingIndex = startingIndex.add(1);
    }
    }
    //
    function addTreasury(address _address)
    external
    onlyOwner
    {
    require(
    _address != address(0),
    "Breaker Treasury: Can't add a zero address"
    );
    if (_presaleList[_address] == 0) {
    _presaleList[_address] = 420; // It seems appropriate
    }
    }

    // Presale and Play2Mint whitelists
    function addToPresaleList(address[] calldata _addresses)
    external
    onlyOwner
    {
    for (uint256 ind = 0; ind < _addresses.length; ind++) {
    require(
    _addresses[ind] != address(0),
    "Breaker Presale: Can't add a zero address"
    );
    if (_presaleList[_addresses[ind]] == 0) {
    _presaleList[_addresses[ind]] = presaleConfig.mintMax;
    }
    }
    }

    function addToPlay2Mint(address[] calldata _addresses)
    external
    onlyOwner
    {
    for (uint256 ind = 0; ind < _addresses.length; ind++) {
    require(
    _addresses[ind] != address(0),
    "DuskBreaker P2M: Can't add a zero address"
    );
    if (_play2MintList[_addresses[ind]] == 0) {
    _play2MintList[_addresses[ind]] = play2MintConfig.mintMax;
    }
    }
    }

    function mintBreaker(uint256 numberOfTokens) public payable saleIsOpen nonReentrant() {

    require(hasSupply(numberOfTokens), "Purchase exceeds max total Breakers");
    require(canMint(numberOfTokens), "Mint Access Not Granted");
    require(breakerPrice.mul(numberOfTokens) <= msg.value, "ETH sent in transaction too low");

    for(uint i = 0; i < numberOfTokens; i++) {
    uint mintIndex = nextToken();
    if (mintIndex < BREAKER_SUPPLY) {
    _safeMint(msg.sender, mintIndex);
    //emit welcomeToTheDusk(mintIndex);
    _tokenIdCounter.increment();
    // remove presales sales first then play2mint sales next
    if (_presaleList[msg.sender] > 0) {
    _presaleList[msg.sender] = _presaleList[msg.sender].sub(1);
    } else if (_play2MintList[msg.sender] > 0) {
    _play2MintList[msg.sender] = _play2MintList[msg.sender].sub(1);
    }
    }
    }

    // Did we mint the last block? If so, ready the starting index based on this block number
    if (startingIndexBlock == 0 && (nextToken() == BREAKER_SUPPLY)) {
    startingIndexBlock = block.number;
    }
    }

    // Safeguards

    function hasSupply(uint256 numberOfTokens) public view returns (bool) {
    uint256 creatureSupply = nextToken();
    return creatureSupply.add(numberOfTokens) <= BREAKER_SUPPLY;
    }

    function canMint(uint256 numberOfTokens) public view returns (bool) {
    // skip guard so web3 has easy time
    if(! isUnpausedAndSupplied()) {
    return false;
    }
    // Presales can mint their max
    if(_presaleList[msg.sender] > 0 &&
    numberOfTokens <= _presaleList[msg.sender]
    ) {
    return true;
    }
    // same for play2mint people, but if you are both whitelist and p2m, you can mint the sum
    if(_play2MintList[msg.sender] > 0 &&
    numberOfTokens <= _play2MintList[msg.sender]
    ) {
    return true;
    }
    // after Play2Mint, anyone can mint
    if(fcfsMint) {
    return true;
    }

    return false;
    }

    // Web3 Economics
    function withdrawAll() public onlyOwner {
    uint256 balance = address(this).balance;
    require(balance > 0);
    _widthdraw(msg.sender, address(this).balance);
    }

    function _widthdraw(address _address, uint256 _amount) private {
    (bool success, ) = _address.call{value: _amount}("");
    require(success, "Transfer failed.");
    }
    }