// Contract: OneEyedSloth
// Target function: function transferDaoNFT(address to, uint256 tokenId) external onlyOwner
 global index out of bounds");
        return _allTokens[index];
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);

        if (from == address(0)) {
            _addTokenToAllTokensEnumeration(tokenId);
        } else if (from != to) {
            _removeTokenFromOwnerEnumeration(from, tokenId);
        }
        if (to == address(0)) {
            _removeTokenFromAllTokensEnumeration(tokenId);
        } else if (to != from) {
            _addTokenToOwnerEnumeration(to, tokenId);
        }
    }

    /**
     * @dev Private function to add a token to this extension's ownership-tracking data structures.
     * @param to address representing the new owner of the given token ID
     * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
     */
    function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
        uint256 length = ERC721.balanceOf(to);
        _ownedTokens[to][length] = tokenId;
        _ownedTokensIndex[tokenId] = length;
    }

    /**
     * @dev Private function to add a token to this extension's token tracking data structures.
     * @param tokenId uint256 ID of the token to be added to the tokens list
     */
    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
        _allTokensIndex[tokenId] = _allTokens.length;
        _allTokens.push(tokenId);
    }

    /**
     * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
     * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
     * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
     * This has O(1) time complexity, but alters the order of the _ownedTokens array.
     * @param from address representing the previous owner of the given token ID
     * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
     */
    function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
        // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
        uint256 tokenIndex = _ownedTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary
        if (tokenIndex != lastTokenIndex) {
            uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];

            _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }

        // This also deletes the contents at the last position of the array
        delete _ownedTokensIndex[tokenId];
        delete _ownedTokens[from][lastTokenIndex];
    }

    /**
     * @dev Private function to remove a token from this extension's token tracking data structures.
     * This has O(1) time complexity, but alters the order of the _allTokens array.
     * @param tokenId uint256 ID of the token to be removed from the tokens list
     */
    function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
        // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = _allTokens.length - 1;
        uint256 tokenIndex = _allTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
        // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
        // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
        uint256 lastTokenId = _allTokens[lastTokenIndex];

        _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
        _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

        // This also deletes the contents at the last position of the array
        delete _allTokensIndex[tokenId];
        _allTokens.pop();
    }
}


/**
 * @title Counters
 * @author Matt Condon (@shrugs)
 * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
 * of elements in a mapping, issuing ERC721 ids, or counting request ids.
 *
 * Include with `using Counters for Counters.Counter;`
 */
library Counters {
    struct Counter {
        // This variable should never be directly accessed by users of the library: interactions must be restricted to
        // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
        // this feature: see https://github.com/ethereum/solidity/issues/4637
        uint256 _value; // default: 0
    }

    function current(Counter storage counter) internal view returns (uint256) {
        return counter._value;
    }

    function increment(Counter storage counter) internal {
        unchecked {
            counter._value += 1;
        }
    }

    function decrement(Counter storage counter) internal {
        uint256 value = counter._value;
        require(value > 0, "Counter: decrement overflow");
        unchecked {
            counter._value = value - 1;
        }
    }

    function reset(Counter storage counter) internal {
        counter._value = 0;
    }
}

contract DaoContract is IERC721Receiver {
    using SafeMath for uint256;
    address public feeReceiver1; // business wallet
    address public owner;
    OneEyedSloth public nftAddress;
    bool public enableRefund = false;
    bool public enablePaySplit = false;
    uint256 public maxRefund = 0.2 ether;

    constructor(address _feeReceiver1, address _nftAddress) {
        owner = msg.sender;
        feeReceiver1 = _feeReceiver1;
        nftAddress = OneEyedSloth(_nftAddress);
    }

    modifier onlyToken() {
        require(msg.sender == owner, "Only token contract is allowed to call this function.");
        _;
    }

    receive() external payable {distribute();}

    event ClaimedRefund(address indexed claimer, uint256 tokenId, uint256 refundAmount);
    function claimRefund(uint256 tokenId) public {
        require(enableRefund, "refund is not enabled yet.");
        uint256 balance = address(this).balance;
        uint256 bps = balance.div(100000).mul(7);
        uint256 amountRefundable;
        require(bps != 0, "refund halted atm");        
        require(nftAddress.getApproved(tokenId) == address(this) || nftAddress.isApprovedForAll(msg.sender, address(this)), "not approved to spend the nft id");
        nftAddress.safeTransferFrom(msg.sender, address(this), tokenId);
        if(bps >= maxRefund) {amountRefundable = maxRefund;}
        if(bps < maxRefund) {amountRefundable = bps;}
        payable(msg.sender).transfer(amountRefundable);
        emit ClaimedRefund(msg.sender, tokenId, amountRefundable);
    }

    function claimAllNFTRefund() external {
        require(nftAddress.isApprovedForAll(msg.sender, address(this)), "nfts are not approved to spend");
        uint256[] memory tokenIds = nftAddress.tokensOfOwner(msg.sender);
        for(uint256 i; i < tokenIds.length; i++) {
            claimRefund(tokenIds[i]);
        }
    }

    event Distribution(address indexed receiver1, uint256 fee1);
    function distribute() internal {
        uint256 balance = msg.value;
        if(balance > 0) {
            uint256 fee1 = balance.div(2);
            sendEthToWallets(fee1);
            emit Distribution(feeReceiver1, fee1);
        }
    }

    function sendEthToWallets(uint256 _fee1) internal {
       (bool success1, ) = payable(feeReceiver1).call{value: _fee1}("");
       require(success1, "distribution failed");
    }

    event UpdateFeeReceivers(address indexed _feeReceiver1);
    function updateFeeReceivers(address _feeReceiver1) external onlyToken {
        feeReceiver1 = _feeReceiver1;
        emit UpdateFeeReceivers(_feeReceiver1);
    }

    event EthTransferred(address indexed wallet, uint256 amount);
    function transferEth(address wallet, uint256 amount) external onlyToken {
        (bool success, ) = payable(wallet).call{value: amount}("");
        require(success, "unable to transfer");
        emit EthTransferred(wallet, amount);
    }

    event NftUpdated(address indexed nftAddress_);
    function updateNftAddress(address nftAddress_) external onlyToken {
        nftAddress = OneEyedSloth(nftAddress_);
        emit NftUpdated(nftAddress_);
    }

    event UpdateEnableRefund(bool enableRefund_);
    function updateEnableRefund(bool enableRefund_) external onlyToken {
        enableRefund = enableRefund_;
        emit UpdateEnableRefund(enableRefund_);
    }

    event TransferAllNfts(address indexed to);
    function transferAllNFts(address to) external onlyToken {
        uint256[] memory tokenIds = nftAddress.tokensOfOwner(address(this));
        for(uint256 i; i < tokenIds.length; i++){
            nftAddress.transferFrom(address(this), to, tokenIds[i]);
        }
        emit TransferAllNfts(to);
    }

    event Transfer(address indexed to, uint256 tokenId);
    function transfer(address to, uint256 tokenId) external onlyToken {
        nftAddress.safeTransferFrom(address(this), to, tokenId);
        emit Transfer(to, tokenId);
    }

    event UpdateMaxRefund(uint256 maxRefund_);
    function updateMaxRefund(uint256 maxRefund_) external onlyToken {
        maxRefund = maxRefund_;
        emit UpdateMaxRefund(maxRefund_);
    }

    event UpdateEnablePaySplit(bool enable_);
    function updateEnablePaySplit(bool enable_) external onlyToken {
        require(!enableRefund, "can not enable unless refund disabled.");
        enablePaySplit = enable_;
        emit UpdateEnablePaySplit(enable_);
    }

    /**
     * Always returns `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
        return this.onERC721Received.selector;
    }
}

contract OneEyedSloth is ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    using Strings for uint256;
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdTracker;

    uint256 public constant MAX_SUPPLY = 10000;
    uint256 public PRICE = 0.2 ether;

    string private baseTokenURI;
    string private contractMetaURI;
    uint256 public startblock;
    uint256 public maxMint = 3;
    bool public hideImage = true;
    string private hideImageUrl = "ipfs://bafkreialgrhqkaykm2iaj7qmk4ii4mz5ttedexvuertjxoonxohtx3sozu";

    //whitelist mint
    bool public enableWhitelist = true;
    mapping(address => bool) public whitelists;

    DaoContract public daoContract;

    modifier checkMintTime() {
        require(block.timestamp >= startblock || msg.sender == owner(), "not the minting time yet");
        if(enableWhitelist && msg.sender != owner()){
            require(whitelists[msg.sender], "whitelist mint round");
        }
        _;
    }

    modifier checkMaxMint(uint256 _count) {
        uint256 _maxMint = maxMint;
        if(enableWhitelist) {
            _maxMint = 1;
        }
        if(msg.sender != owner()) {
            uint256 balance = balanceOf(msg.sender);
            require(balance.add(_count) <= _maxMint, "balance exceeds maximum mint");
        }
        _;
    }

    constructor() 
    ERC721("One Eyed Sloth", "OES")
    {
        address _feeReceiver1 = 0xAE5A8f8BFD68d42DA78cB0E98eFBFb95BEff3e26;
        startblock = 1677866400;
        whitelists[msg.sender] = true;
        daoContract = new DaoContract(_feeReceiver1, address(this));
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    function contractURI() external view returns (string memory) {
        return contractMetaURI;
    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory){
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        if(hideImage) return hideImageUrl;

        string memory baseURI = _baseURI();
        return
            bytes(baseURI).length > 0
                ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json"))
                : "";
    }

    event SetBaseURI(string _baseURI);
    function setBaseURI(string memory _baseTokenURI) public onlyOwner {
        baseTokenURI = _baseTokenURI;
        emit SetBaseURI(_baseTokenURI);
    }

    function mintNFTs(uint256 _count) external payable checkMintTime checkMaxMint(_count){
        uint256 totalMinted = _tokenIdTracker.current();
        require(totalMinted.add(_count) <= MAX_SUPPLY, "OES: Not enough NFTs!");
        if(msg.sender != owner()){
            require(_count > 0 ,"OES: cannot mint specified number of NFTs.");
            require(msg.value >= PRICE.mul(_count),"OES: not enough ether received");
            (bool success, ) = payable(daoContract).call{value: msg.value}("");
            require(success, "transfer failed");
        }
        
        for (uint256 i = 0; i < _count; i++) {
            _mintSingleNFT();
        }
    }

    function _mintSingleNFT() private {
        _tokenIdTracker.increment();
        uint256 newTokenID = _tokenIdTracker.current();
        _safeMint(msg.sender, newTokenID);
    }

    function tokensOfOwner(address _owner) external view returns (uint256[] memory){
        uint256 tokenCount = balanceOf(_owner);
        uint256[] memory tokensId = new uint256[](tokenCount);
        for (uint256 i = 0; i < tokenCount; i++) {
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokensId;
    }

    event ETHClaimed(address claimer, uint256 amount);
    function withdrawEth() external payable onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0, "OES: no ether left to withdraw");
        (bool success, ) = payable(msg.sender).call{value: balance}("");
        require(success, "OES: transfer failed.");
        emit ETHClaimed(msg.sender, balance);
    }

    function transfer(uint256 tokenId, address to) external {
        safeTransferFrom(msg.sender, to, tokenId);
    }

    event UpdateMintPrice(uint256 _price);
    function updatePrice(uint256 _newPrice) external onlyOwner {
        PRICE = _newPrice;
        emit UpdateMintPrice(_newPrice);
    }

    function updateWallets(address _feeReceiver1) external onlyOwner {
        daoContract.updateFeeReceivers(_feeReceiver1);
    }

    function updateEnableRefund(bool enableRefund_) external onlyOwner {
        daoContract.updateEnableRefund(enableRefund_);
    }

    function updateNftAddress() external onlyOwner {
        daoContract.updateNftAddress(address(this));
    }

    event UpdateStartblock(uint256 newStartTime);
    function updateStartblock(uint256 newStartTime) external onlyOwner {
        startblock = newStartTime;
        emit UpdateStartblock(newStartTime);
    }

    event UpdateMaxMint(uint256 _maxMint);
    function updateMaxMint(uint256 _maxMint) external onlyOwner {
        maxMint = _maxMint;
        emit UpdateMaxMint(_maxMint);
    }

    event UpdateCollectionURI(string newContractURI);
    function setContractURI(string memory newContractURI) external onlyOwner {
        contractMetaURI = newContractURI;
        emit UpdateCollectionURI(newContractURI);
    }

    event UpdateHideImage(bool _show);
    function updateHideImage(bool _show) external onlyOwner {
        hideImage = _show;
        emit UpdateHideImage(_show);
    }

    event UpdateHideImageURL(string url_);
    function updateHideImageUrl(string memory url_) external onlyOwner {
        hideImageUrl = url_;
        emit UpdateHideImageURL(url_);
    }