文件 1 的 15:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 2 的 15:ERC165.sol
pragma solidity ^0.8.0;
import "./IERC165.sol";
abstract contract ERC165 is IERC165 {
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
文件 3 的 15:IAdventure.sol
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface IAdventure is IERC165 {
function questsLockTokens() external view returns (bool);
function onQuestEntered(address adventurer, uint256 tokenId, uint256 questId) external;
function onQuestExited(address adventurer, uint256 tokenId, uint256 questId, uint256 questStartTimestamp) external;
}
文件 4 的 15:IAdventureApproval.sol
pragma solidity 0.8.9;
interface IAdventureApproval {
function setAdventuresApprovedForAll(address operator, bool approved) external;
function areAdventuresApprovedForAll(address owner, address operator) external view returns (bool);
function isAdventureWhitelisted(address account) external view returns (bool);
}
文件 5 的 15:IAdventurous.sol
pragma solidity ^0.8.4;
import "./Quest.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface IAdventurous is IERC165 {
event AdventureApprovalForAll(address indexed tokenOwner, address indexed operator, bool approved);
event QuestUpdated(uint256 indexed tokenId, address indexed tokenOwner, address indexed adventure, uint256 questId, bool active, bool booted);
function adventureTransferFrom(address from, address to, uint256 tokenId) external;
function adventureSafeTransferFrom(address from, address to, uint256 tokenId) external;
function adventureBurn(uint256 tokenId) external;
function enterQuest(uint256 tokenId, uint256 questId) external;
function exitQuest(uint256 tokenId, uint256 questId) external;
function getQuestCount(uint256 tokenId, address adventure) external view returns (uint256);
function getTimeOnQuest(uint256 tokenId, address adventure, uint256 questId) external view returns (uint256);
function isParticipatingInQuest(uint256 tokenId, address adventure, uint256 questId) external view returns (bool participatingInQuest, uint256 startTimestamp, uint256 index);
function getActiveQuests(uint256 tokenId, address adventure) external view returns (Quest[] memory activeQuests);
}
文件 6 的 15:IAdventurousERC721.sol
pragma solidity ^0.8.4;
import "./IAdventurous.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface IAdventurousERC721 is IERC721, IAdventurous {
}
文件 7 的 15:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 8 的 15:IERC721.sol
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool _approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
文件 9 的 15:IMintableVillain.sol
pragma solidity 0.8.9;
interface IMintableVillain {
function unmaskVillainsBatch(address to, uint256[] calldata villainTokenIds, uint256[] calldata potionTokenIds) external;
}
文件 10 的 15:IMinterWhitelist.sol
pragma solidity 0.8.9;
interface IMinterWhitelist {
function whitelistedMinters(address account) external view returns (bool);
}
文件 11 的 15:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 12 的 15:Pausable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Pausable is Context {
event Paused(address account);
event Unpaused(address account);
bool private _paused;
constructor() {
_paused = false;
}
modifier whenNotPaused() {
_requireNotPaused();
_;
}
modifier whenPaused() {
_requirePaused();
_;
}
function paused() public view virtual returns (bool) {
return _paused;
}
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
文件 13 的 15:Quest.sol
pragma solidity ^0.8.4;
struct Quest {
bool isActive;
uint32 questId;
uint64 startTimestamp;
uint32 arrayIndex;
}
文件 14 的 15:VillainCustodian.sol
pragma solidity 0.8.9;
import "./IAdventureApproval.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract VillainCustodian {
constructor(address villainUnmaskingAdventure, address maskedVillainAddress, address superVillainPotionAddress, address villainPotionAddress) {
IERC721(maskedVillainAddress).setApprovalForAll(villainUnmaskingAdventure, true);
IERC721(superVillainPotionAddress).setApprovalForAll(villainUnmaskingAdventure, true);
IERC721(villainPotionAddress).setApprovalForAll(villainUnmaskingAdventure, true);
IAdventureApproval(maskedVillainAddress).setAdventuresApprovedForAll(villainUnmaskingAdventure, true);
IAdventureApproval(superVillainPotionAddress).setAdventuresApprovedForAll(villainUnmaskingAdventure, true);
IAdventureApproval(villainPotionAddress).setAdventuresApprovedForAll(villainUnmaskingAdventure, true);
}
}
文件 15 的 15:VillainUnmaskingAdventure.sol
pragma solidity ^0.8.9;
import "./IMintableVillain.sol";
import "./IMinterWhitelist.sol";
import "./VillainCustodian.sol";
import "@limit-break/adventures/IAdventure.sol";
import "@limit-break/adventures/IAdventurousERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
error CallbackNotImplemented();
error CallerDidNotCreateClaimId();
error CallerNotOwnerOfSuperVillainPotion();
error CallerNotOwnerOfMaskedVillain();
error CallerNotOwnerOfVillainPotion();
error CannotIncludeMoreThanOnePotion();
error CannotSpecifyZeroAddressForSuperVillainContract();
error CannotSpecifyZeroAddressForVillainContract();
error CannotSpecifyZeroAddressForSuperVillainPotionContract();
error CannotSpecifyZeroAddressForMaskedVillainContract();
error CannotSpecifyZeroAddressForVillainPotionContract();
error CompleteQuestToUnmaskVillain();
error InputArrayLengthMismatch();
error MustIncludeMaskedVillain();
error NewUnmaskingDurationMustBeLessThanCurrentDuration();
error NoUnmaskingQuestFoundForSpecifiedClaimId();
error PotionGreaterThanAllocatedSpace();
error QuantityMustBeGreaterThanZero();
error QuestCompleteVillainMustBeUnmasked();
contract VillainUnmaskingAdventure is Ownable, Pausable, ERC165, IAdventure {
struct UnmaskingQuest {
uint64 startTimestamp;
uint16 villainTokenId;
uint16 potionBitmap;
address adventurer;
}
IAdventurousERC721 immutable public villainPotionContract;
IAdventurousERC721 immutable public superVillainPotionContract;
IAdventurousERC721 immutable public maskedVillainContract;
IMintableVillain immutable public villainContract;
IMintableVillain immutable public superVillainContract;
VillainCustodian immutable public custodian;
uint256 public lastClaimId;
uint256 public unmaskingDuration = 7 days;
mapping (uint256 => UnmaskingQuest) public unmaskingQuestLookup;
uint constant private ONE = uint(1);
uint256 constant private MAX_15_BIT_VALUE = 32767;
event VillainMasked(address indexed adventurer, uint256 indexed claimId, bool isSuperVillain);
event UnmaskingDurationUpdated(uint256 oldUnmaskingDuration, uint256 newUnmaskingDuration);
event UnmaskingVillain(address indexed adventurer, uint256 indexed claimId, uint256 potionTokenId, uint256 villainTokenId, bool isSuperVillain);
event UnmaskedVillain(address indexed adventurer, uint256 indexed claimId, bool isSuperVillain);
constructor(address villainPotionAddress, address superVillainPotionAddress, address maskedVillainAddress, address villainAddress, address superVillainAddress) {
if(villainPotionAddress == address(0)) {
revert CannotSpecifyZeroAddressForVillainPotionContract();
}
if(superVillainPotionAddress == address(0)) {
revert CannotSpecifyZeroAddressForSuperVillainPotionContract();
}
if(maskedVillainAddress == address(0)) {
revert CannotSpecifyZeroAddressForMaskedVillainContract();
}
if(villainAddress == address(0)) {
revert CannotSpecifyZeroAddressForVillainContract();
}
if(superVillainAddress == address(0)) {
revert CannotSpecifyZeroAddressForSuperVillainContract();
}
villainPotionContract = IAdventurousERC721(villainPotionAddress);
superVillainPotionContract = IAdventurousERC721(superVillainPotionAddress);
maskedVillainContract = IAdventurousERC721(maskedVillainAddress);
villainContract = IMintableVillain(villainAddress);
superVillainContract = IMintableVillain(superVillainAddress);
custodian = new VillainCustodian(address(this), maskedVillainAddress, superVillainPotionAddress, villainPotionAddress);
}
function onQuestEntered(address , uint256 , uint256 ) external override pure {
revert CallbackNotImplemented();
}
function onQuestExited(address , uint256 , uint256 , uint256 ) external override pure {
revert CallbackNotImplemented();
}
function questsLockTokens() external override pure returns (bool) {
return false;
}
function pauseNewQuestEntries() external onlyOwner {
_pause();
}
function unpauseNewQuestEntries() external onlyOwner {
_unpause();
}
function setUnmaskingDuration(uint256 unmaskingDuration_) external onlyOwner {
uint256 currentUnmaskingDuration = unmaskingDuration;
if(unmaskingDuration_ >= currentUnmaskingDuration) {
revert NewUnmaskingDurationMustBeLessThanCurrentDuration();
}
emit UnmaskingDurationUpdated(currentUnmaskingDuration, unmaskingDuration_);
unmaskingDuration = unmaskingDuration_;
}
function maskVillainsBatch(uint256[] calldata claimIds) external {
if(claimIds.length == 0) {
revert QuantityMustBeGreaterThanZero();
}
for(uint256 i = 0; i < claimIds.length;) {
_maskVillain(claimIds[i]);
unchecked {
++i;
}
}
}
function startUnmaskingVillainsBatch(uint256[] calldata maskedVillainTokenIds, uint256[] calldata villainPotionTokenIds, uint256[] calldata superVillainPotionTokenIds) external whenNotPaused {
if(maskedVillainTokenIds.length == 0) {
revert QuantityMustBeGreaterThanZero();
}
if(maskedVillainTokenIds.length != villainPotionTokenIds.length || villainPotionTokenIds.length != superVillainPotionTokenIds.length) {
revert InputArrayLengthMismatch();
}
uint256 claimId;
unchecked {
claimId = lastClaimId;
lastClaimId = claimId + maskedVillainTokenIds.length;
++claimId;
}
for(uint256 i = 0; i < maskedVillainTokenIds.length;) {
_startUnmaskingVillain(claimId + i, maskedVillainTokenIds[i], villainPotionTokenIds[i], superVillainPotionTokenIds[i]);
unchecked {
++i;
}
}
}
function unmaskVillainsBatch(uint256[] calldata claimIds) external {
if(claimIds.length == 0) {
revert QuantityMustBeGreaterThanZero();
}
uint256[] memory tempVillainTokenIds = new uint256[](claimIds.length);
uint256[] memory tempSuperVillainTokenIds = new uint256[](claimIds.length);
uint256[] memory potionTokenIds = new uint256[](claimIds.length);
uint256 villainCounter = 0;
uint256 superVillainCounter = 0;
for(uint256 i = 0; i < claimIds.length;) {
(uint256 villainTokenId, uint256 potionTokenId, bool isSuperVillain) = _unmaskVillain(claimIds[i]);
unchecked {
if (isSuperVillain) {
tempSuperVillainTokenIds[i] = villainTokenId;
++superVillainCounter;
} else {
tempVillainTokenIds[i] = villainTokenId;
++villainCounter;
}
potionTokenIds[i] = potionTokenId;
++i;
}
}
uint256[] memory villainTokenIds = new uint256[](villainCounter);
uint256[] memory villainPotionTokenIds = new uint256[](villainCounter);
uint256[] memory superVillainTokenIds = new uint256[](superVillainCounter);
uint256[] memory superVillainPotionTokenIds = new uint256[](superVillainCounter);
uint256 villainPotionCounter = 0;
uint256 superVillainPotionCounter = 0;
unchecked {
for(uint256 i = 0; i < claimIds.length; ++i) {
uint256 villainTokenId = tempVillainTokenIds[i];
uint256 superVillainTokenId = tempSuperVillainTokenIds[i];
if(villainTokenId > 0) {
villainPotionTokenIds[villainPotionCounter] = potionTokenIds[i];
villainTokenIds[villainPotionCounter] = villainTokenId;
++villainPotionCounter;
} else {
superVillainPotionTokenIds[superVillainPotionCounter] = potionTokenIds[i];
superVillainTokenIds[superVillainPotionCounter] = superVillainTokenId;
++superVillainPotionCounter;
}
}
}
if(villainCounter > 0) {
villainContract.unmaskVillainsBatch(_msgSender(), villainTokenIds, villainPotionTokenIds);
}
if(superVillainCounter > 0) {
superVillainContract.unmaskVillainsBatch(_msgSender(), superVillainTokenIds, superVillainPotionTokenIds);
}
}
function getUnmaskingQuestDetailsBatch(uint256[] calldata claimIds) external view returns (UnmaskingQuest[] memory unmaskingQuests) {
unmaskingQuests = new UnmaskingQuest[](claimIds.length);
unchecked {
for(uint256 i = 0; i < claimIds.length; ++i) {
unmaskingQuests[i] = unmaskingQuestLookup[claimIds[i]];
}
}
}
function supportsInterface(bytes4 interfaceId) public view virtual override (ERC165, IERC165) returns (bool) {
return interfaceId == type(IAdventure).interfaceId || super.supportsInterface(interfaceId);
}
function _startUnmaskingVillain(uint256 claimId, uint256 maskedVillainTokenId, uint256 villainPotionTokenId, uint256 superVillainPotionTokenId) private {
if(villainPotionTokenId != 0 && superVillainPotionTokenId != 0) {
revert CannotIncludeMoreThanOnePotion();
}
if(maskedVillainTokenId == 0) {
revert MustIncludeMaskedVillain();
}
if(superVillainPotionTokenId > MAX_15_BIT_VALUE || villainPotionTokenId > MAX_15_BIT_VALUE) {
revert PotionGreaterThanAllocatedSpace();
}
address caller = _msgSender();
uint16 potionTokenId = villainPotionTokenId == 0 ? uint16(superVillainPotionTokenId) : uint16(villainPotionTokenId);
uint16 potionBitmap = potionTokenId;
if(superVillainPotionTokenId > 0) {
potionBitmap = potionBitmap | uint16(ONE << 15);
}
unmaskingQuestLookup[claimId].startTimestamp = uint64(block.timestamp);
unmaskingQuestLookup[claimId].villainTokenId = uint16(maskedVillainTokenId);
unmaskingQuestLookup[claimId].potionBitmap = potionBitmap;
unmaskingQuestLookup[claimId].adventurer = caller;
emit UnmaskingVillain(caller, claimId, potionTokenId, unmaskingQuestLookup[claimId].villainTokenId, superVillainPotionTokenId > 0);
if (maskedVillainContract.ownerOf(maskedVillainTokenId) != caller) {
revert CallerNotOwnerOfMaskedVillain();
}
maskedVillainContract.adventureTransferFrom(caller, address(custodian), maskedVillainTokenId);
if (villainPotionTokenId > 0) {
if (villainPotionContract.ownerOf(villainPotionTokenId) != caller) {
revert CallerNotOwnerOfVillainPotion();
}
villainPotionContract.adventureTransferFrom(caller, address(custodian), villainPotionTokenId);
}
if (superVillainPotionTokenId > 0) {
if (superVillainPotionContract.ownerOf(superVillainPotionTokenId) != caller) {
revert CallerNotOwnerOfSuperVillainPotion();
}
superVillainPotionContract.adventureTransferFrom(caller, address(custodian), superVillainPotionTokenId);
}
}
function _maskVillain(uint256 claimId) private {
(address adventurer, uint256 villainTokenId, uint256 potionTokenId, bool isSuperVillain, bool questCompleted) = _getAndClearUnmaskingQuestStatus(claimId);
bool allowUserToReturnMaskAfterQuestCompleted = false;
if(!IAdventureApproval(address(maskedVillainContract)).isAdventureWhitelisted(address(this)) ||
!IAdventureApproval(address(villainPotionContract)).isAdventureWhitelisted(address(this)) ||
!IAdventureApproval(address(superVillainPotionContract)).isAdventureWhitelisted(address(this)) ||
!IMinterWhitelist(address(villainContract)).whitelistedMinters(address(this)) ||
!IMinterWhitelist(address(superVillainContract)).whitelistedMinters(address(this))) {
allowUserToReturnMaskAfterQuestCompleted = true;
}
if(questCompleted && !allowUserToReturnMaskAfterQuestCompleted) {
revert QuestCompleteVillainMustBeUnmasked();
}
emit VillainMasked(adventurer, claimId, isSuperVillain);
maskedVillainContract.transferFrom(address(custodian), adventurer, villainTokenId);
if(potionTokenId > 0) {
if(isSuperVillain) {
superVillainPotionContract.transferFrom(address(custodian), adventurer, potionTokenId);
} else {
villainPotionContract.transferFrom(address(custodian), adventurer, potionTokenId);
}
}
}
function _unmaskVillain(uint256 claimId) private returns (uint256, uint256, bool) {
(address adventurer, uint256 villainTokenId, uint256 potionTokenId, bool isSuperVillain, bool questCompleted) = _getAndClearUnmaskingQuestStatus(claimId);
if(!questCompleted) {
revert CompleteQuestToUnmaskVillain();
}
emit UnmaskedVillain(adventurer, claimId, isSuperVillain);
if(isSuperVillain) {
superVillainPotionContract.adventureBurn(potionTokenId);
} else if(potionTokenId > 0) {
villainPotionContract.adventureBurn(potionTokenId);
}
maskedVillainContract.adventureBurn(villainTokenId);
return (villainTokenId, potionTokenId, isSuperVillain);
}
function _getAndClearUnmaskingQuestStatus(uint256 claimId) private returns (address adventurer, uint256 villainTokenId, uint256 potionTokenId, bool isSuperVillain, bool questCompleted) {
UnmaskingQuest memory unmaskingQuest = unmaskingQuestLookup[claimId];
uint256 startTimestamp = unmaskingQuest.startTimestamp;
adventurer = unmaskingQuest.adventurer;
villainTokenId = unmaskingQuest.villainTokenId;
uint16 potionBitmap = unmaskingQuest.potionBitmap;
isSuperVillain = potionBitmap >> 15 & 1 == 1;
potionTokenId = potionBitmap & ~(ONE << 15);
if (startTimestamp == 0) {
revert NoUnmaskingQuestFoundForSpecifiedClaimId();
}
if (adventurer != _msgSender()) {
revert CallerDidNotCreateClaimId();
}
unchecked {
questCompleted = block.timestamp - startTimestamp >= unmaskingDuration;
}
delete unmaskingQuestLookup[claimId];
return (adventurer, villainTokenId, potionTokenId, isSuperVillain, questCompleted);
}
}
{
"compilationTarget": {
"src/VillainUnmaskingAdventure.sol": "VillainUnmaskingAdventure"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@limit-break/=lib/limit-break/",
":@openzeppelin/=lib/openzeppelin-contracts/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":limit-break/=lib/limit-break/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/"
]
}
[{"inputs":[{"internalType":"address","name":"villainPotionAddress","type":"address"},{"internalType":"address","name":"superVillainPotionAddress","type":"address"},{"internalType":"address","name":"maskedVillainAddress","type":"address"},{"internalType":"address","name":"villainAddress","type":"address"},{"internalType":"address","name":"superVillainAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"CallbackNotImplemented","type":"error"},{"inputs":[],"name":"CallerDidNotCreateClaimId","type":"error"},{"inputs":[],"name":"CallerNotOwnerOfMaskedVillain","type":"error"},{"inputs":[],"name":"CallerNotOwnerOfSuperVillainPotion","type":"error"},{"inputs":[],"name":"CallerNotOwnerOfVillainPotion","type":"error"},{"inputs":[],"name":"CannotIncludeMoreThanOnePotion","type":"error"},{"inputs":[],"name":"CannotSpecifyZeroAddressForMaskedVillainContract","type":"error"},{"inputs":[],"name":"CannotSpecifyZeroAddressForSuperVillainContract","type":"error"},{"inputs":[],"name":"CannotSpecifyZeroAddressForSuperVillainPotionContract","type":"error"},{"inputs":[],"name":"CannotSpecifyZeroAddressForVillainContract","type":"error"},{"inputs":[],"name":"CannotSpecifyZeroAddressForVillainPotionContract","type":"error"},{"inputs":[],"name":"CompleteQuestToUnmaskVillain","type":"error"},{"inputs":[],"name":"InputArrayLengthMismatch","type":"error"},{"inputs":[],"name":"MustIncludeMaskedVillain","type":"error"},{"inputs":[],"name":"NewUnmaskingDurationMustBeLessThanCurrentDuration","type":"error"},{"inputs":[],"name":"NoUnmaskingQuestFoundForSpecifiedClaimId","type":"error"},{"inputs":[],"name":"PotionGreaterThanAllocatedSpace","type":"error"},{"inputs":[],"name":"QuantityMustBeGreaterThanZero","type":"error"},{"inputs":[],"name":"QuestCompleteVillainMustBeUnmasked","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"adventurer","type":"address"},{"indexed":true,"internalType":"uint256","name":"claimId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isSuperVillain","type":"bool"}],"name":"UnmaskedVillain","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldUnmaskingDuration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newUnmaskingDuration","type":"uint256"}],"name":"UnmaskingDurationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"adventurer","type":"address"},{"indexed":true,"internalType":"uint256","name":"claimId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"potionTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"villainTokenId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isSuperVillain","type":"bool"}],"name":"UnmaskingVillain","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"adventurer","type":"address"},{"indexed":true,"internalType":"uint256","name":"claimId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isSuperVillain","type":"bool"}],"name":"VillainMasked","type":"event"},{"inputs":[],"name":"custodian","outputs":[{"internalType":"contract VillainCustodian","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"claimIds","type":"uint256[]"}],"name":"getUnmaskingQuestDetailsBatch","outputs":[{"components":[{"internalType":"uint64","name":"startTimestamp","type":"uint64"},{"internalType":"uint16","name":"villainTokenId","type":"uint16"},{"internalType":"uint16","name":"potionBitmap","type":"uint16"},{"internalType":"address","name":"adventurer","type":"address"}],"internalType":"struct VillainUnmaskingAdventure.UnmaskingQuest[]","name":"unmaskingQuests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastClaimId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"claimIds","type":"uint256[]"}],"name":"maskVillainsBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maskedVillainContract","outputs":[{"internalType":"contract IAdventurousERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"onQuestEntered","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"onQuestExited","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pauseNewQuestEntries","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"questsLockTokens","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"unmaskingDuration_","type":"uint256"}],"name":"setUnmaskingDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"maskedVillainTokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"villainPotionTokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"superVillainPotionTokenIds","type":"uint256[]"}],"name":"startUnmaskingVillainsBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"superVillainContract","outputs":[{"internalType":"contract IMintableVillain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"superVillainPotionContract","outputs":[{"internalType":"contract IAdventurousERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"claimIds","type":"uint256[]"}],"name":"unmaskVillainsBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unmaskingDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"unmaskingQuestLookup","outputs":[{"internalType":"uint64","name":"startTimestamp","type":"uint64"},{"internalType":"uint16","name":"villainTokenId","type":"uint16"},{"internalType":"uint16","name":"potionBitmap","type":"uint16"},{"internalType":"address","name":"adventurer","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpauseNewQuestEntries","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"villainContract","outputs":[{"internalType":"contract IMintableVillain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"villainPotionContract","outputs":[{"internalType":"contract IAdventurousERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"}]