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();
}
}