APE Price: $0.56 (+3.00%)

Contract Diff Checker

Contract Name:
MonkeyArcanaSimple

Contract Source Code:

File 1 of 1 : MonkeyArcanaSimple

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol


// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: @openzeppelin/contracts/utils/introspection/ERC165.sol


// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;


/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: @openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol


// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.20;


/**
 * @dev Interface that must be implemented by smart contracts in order to receive
 * ERC-1155 token transfers.
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC-1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC-1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}

// File: @openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol


// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/utils/ERC1155Holder.sol)

pragma solidity ^0.8.20;



/**
 * @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC-1155 tokens.
 *
 * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
 * stuck.
 */
abstract contract ERC1155Holder is ERC165, IERC1155Receiver {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
    }

    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes memory
    ) public virtual override returns (bytes4) {
        return this.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address,
        address,
        uint256[] memory,
        uint256[] memory,
        bytes memory
    ) public virtual override returns (bytes4) {
        return this.onERC1155BatchReceived.selector;
    }
}

// File: @openzeppelin/contracts/token/ERC1155/IERC1155.sol


// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.20;


/**
 * @dev Required interface of an ERC-1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[ERC].
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the value of tokens of token type `id` owned by `account`.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the zero address.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`.
     *
     * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
     * to an untrusted contract, when invoking {onERC1155Received} on the receiver.
     * Ensure to follow the checks-effects-interactions pattern and consider employing
     * reentrancy guards when interacting with untrusted contracts.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `value` amount.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
     * to an untrusted contract, when invoking {onERC1155BatchReceived} on the receiver.
     * Ensure to follow the checks-effects-interactions pattern and consider employing
     * reentrancy guards when interacting with untrusted contracts.
     *
     * Emits either a {TransferSingle} or a {TransferBatch} event, depending on the length of the array arguments.
     *
     * Requirements:
     *
     * - `ids` and `values` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) 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: mysterymachine.sol


pragma solidity ^0.8.28;




/**
 * @title MonkeyArcanaSimple
 * @dev Contract for distributing Monkey Arcana ERC-1155 tokens that were sent to it
 */
contract MonkeyArcanaSimple is ERC1155Holder, Ownable {
    // The ERC-1155 contract address that contains the tokens
    address public nftContract;
    
    // Array to keep track of all available token IDs
    uint256[] private _availableTokenIds;
    
    // Set of valid token IDs that can be used
    mapping(uint256 => bool) public validTokenIds;
    
    // Flag to restrict token transfers to owner only
    bool public ownerOnlyTransfers = true;
    
    // Event emitted when a card is drawn
    event CardDrawn(address indexed user, uint256 indexed tokenId, uint256 timestamp);
    
    // Event emitted when funds are withdrawn
    event FundsWithdrawn(address indexed owner, uint256 amount);
    
    // Event emitted when a token ID is added or removed
    event TokenIdStatusChanged(uint256 indexed tokenId, bool isValid);
    
    // Event emitted when transfer restriction is changed
    event TransferRestrictionChanged(bool ownerOnly);

    /**
     * @dev Constructor sets the NFT contract address and initial owner
     * @param _nftContract The address of the ERC-1155 contract containing the tokens
     * @param initialOwner The address of the initial owner
     */
    constructor(address _nftContract, address initialOwner) Ownable(initialOwner) {
        require(_nftContract != address(0), "MonkeyArcanaSimple: NFT contract cannot be zero address");
        nftContract = _nftContract;
        
        // Initialize with token IDs 0-29 as valid
        for (uint256 i = 0; i <= 29; i++) {
            validTokenIds[i] = true;
        }
    }
    
    /**
     * @dev Override onERC1155Received to restrict who can send tokens
     */
    function onERC1155Received(
        address,
        address from,
        uint256 id,
        uint256,
        bytes memory
    ) public virtual override returns (bytes4) {
        // If owner-only transfers are enabled, only the owner can send tokens
        if (ownerOnlyTransfers) {
            require(from == owner(), "MonkeyArcanaSimple: Only owner can send tokens");
        }
        
        // Only accept valid token IDs
        require(validTokenIds[id], "MonkeyArcanaSimple: Token ID is not valid");
        
        // Return the function selector
        return this.onERC1155Received.selector;
    }
    
    /**
     * @dev Override onERC1155BatchReceived to restrict who can send tokens
     */
    function onERC1155BatchReceived(
        address,
        address from,
        uint256[] memory ids,
        uint256[] memory,
        bytes memory
    ) public virtual override returns (bytes4) {
        // If owner-only transfers are enabled, only the owner can send tokens
        if (ownerOnlyTransfers) {
            require(from == owner(), "MonkeyArcanaSimple: Only owner can send tokens");
        }
        
        // Only accept valid token IDs
        for (uint256 i = 0; i < ids.length; i++) {
            require(validTokenIds[ids[i]], "MonkeyArcanaSimple: Token ID is not valid");
        }
        
        // Return the function selector
        return this.onERC1155BatchReceived.selector;
    }

    /**
     * @dev Draw a card by paying 1 $APE
     * @return tokenId The ID of the drawn card
     */
    function drawCard() external payable returns (uint256) {
        require(msg.value >= 1 ether, "MonkeyArcanaSimple: Must pay 1 $APE to draw a card");
        
        // Update available tokens before drawing
        updateAvailableTokens();
        
        require(_availableTokenIds.length > 0, "MonkeyArcanaSimple: No cards available");

        // Get a random token ID from available tokens
        uint256 randomIndex = _getRandomNumber() % _availableTokenIds.length;
        uint256 tokenId = _availableTokenIds[randomIndex];

        // Transfer the token to the user
        IERC1155(nftContract).safeTransferFrom(
            address(this),
            msg.sender,
            tokenId,
            1,
            ""
        );

        // Update available tokens after drawing
        updateAvailableTokens();

        // Emit event
        emit CardDrawn(msg.sender, tokenId, block.timestamp);

        return tokenId;
    }

    /**
     * @dev Update the list of available token IDs based on actual balances
     */
    function updateAvailableTokens() public {
        // Clear the current list
        delete _availableTokenIds;
        
        // Check all valid token IDs
        uint256[] memory tokenIdsToCheck = getValidTokenIds();
        
        for (uint256 i = 0; i < tokenIdsToCheck.length; i++) {
            uint256 tokenId = tokenIdsToCheck[i];
            uint256 balance = IERC1155(nftContract).balanceOf(address(this), tokenId);
            if (balance > 0) {
                _availableTokenIds.push(tokenId);
            }
        }
    }
    
    /**
     * @dev Get all valid token IDs
     * @return Array of valid token IDs
     */
    function getValidTokenIds() public view returns (uint256[] memory) {
        // Count valid token IDs
        uint256 count = 0;
        for (uint256 i = 0; i < 1000; i++) { // Arbitrary upper limit
            if (validTokenIds[i]) {
                count++;
            }
        }
        
        // Create array of valid token IDs
        uint256[] memory result = new uint256[](count);
        uint256 index = 0;
        for (uint256 i = 0; i < 1000; i++) {
            if (validTokenIds[i]) {
                result[index] = i;
                index++;
            }
        }
        
        return result;
    }

    /**
     * @dev Add a valid token ID (only owner)
     * @param tokenId The token ID to add
     */
    function addValidTokenId(uint256 tokenId) external onlyOwner {
        validTokenIds[tokenId] = true;
        emit TokenIdStatusChanged(tokenId, true);
    }
    
    /**
     * @dev Remove a valid token ID (only owner)
     * @param tokenId The token ID to remove
     */
    function removeValidTokenId(uint256 tokenId) external onlyOwner {
        validTokenIds[tokenId] = false;
        emit TokenIdStatusChanged(tokenId, false);
    }
    
    /**
     * @dev Set whether only the owner can send tokens to this contract
     * @param ownerOnly True if only the owner can send tokens
     */
    function setOwnerOnlyTransfers(bool ownerOnly) external onlyOwner {
        ownerOnlyTransfers = ownerOnly;
        emit TransferRestrictionChanged(ownerOnly);
    }

    /**
     * @dev Withdraw funds from the contract (only owner)
     * @param amount The amount of $APE to withdraw
     */
    function withdrawFunds(uint256 amount) external onlyOwner {
        require(amount > 0, "MonkeyArcanaSimple: Amount must be greater than 0");
        require(address(this).balance >= amount, "MonkeyArcanaSimple: Not enough funds available");

        // Transfer funds to owner
        (bool success, ) = payable(owner()).call{value: amount}("");
        require(success, "MonkeyArcanaSimple: Transfer failed");

        // Emit event
        emit FundsWithdrawn(msg.sender, amount);
    }

    /**
     * @dev Get available cards
     * @return Array of available token IDs
     */
    function getAvailableCards() external view returns (uint256[] memory) {
        return _availableTokenIds;
    }

    /**
     * @dev Update the NFT contract address (only owner)
     * @param _nftContract The new NFT contract address
     */
    function updateNFTContract(address _nftContract) external onlyOwner {
        require(_nftContract != address(0), "MonkeyArcanaSimple: NFT contract cannot be zero address");
        nftContract = _nftContract;
    }

    /**
     * @dev Get a random number using multiple block attributes
     * @return A pseudo-random number
     */
    function _getRandomNumber() private view returns (uint256) {
        return uint256(keccak256(abi.encodePacked(
            block.timestamp,
            block.number,
            block.coinbase,
            msg.sender
        )));
    }
    
    /**
     * @dev Withdraw any NFT from the contract (only owner)
     * @param tokenId The ID of the token to withdraw
     * @param amount The amount to withdraw
     */
    function withdrawNFT(uint256 tokenId, uint256 amount) external onlyOwner {
        uint256 balance = IERC1155(nftContract).balanceOf(address(this), tokenId);
        require(balance >= amount, "MonkeyArcanaSimple: Not enough tokens");
        
        IERC1155(nftContract).safeTransferFrom(
            address(this),
            msg.sender,
            tokenId,
            amount,
            ""
        );
        
        // Update available tokens
        updateAvailableTokens();
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):