// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {IBubbleNFT} from "./interfaces/IBubbleNFT.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {VRFCoordinatorV2Interface} from "@chainlink/contracts/v0.8/vrf/interfaces/VRFCoordinatorV2Interface.sol";
import {VRFConsumerBaseV2} from "@chainlink/contracts/v0.8/vrf/VRFConsumerBaseV2.sol";
/**
* @title BubbleMarketplace
* @author Marco López
* @notice BubbleMarketplace implementation for selling BubbleNFTs in a sequential cycle.
*/
contract BubbleMarketplace is Pausable, VRFConsumerBaseV2 {
// Chainlinnk VRF variables
uint16 private constant _REQUEST_CONFIRMATIONS = 3;
uint32 private constant _NUM_WORDS = 1;
VRFCoordinatorV2Interface private immutable _VRF_COORDINATOR;
bytes32 private immutable _GAS_LANE;
uint64 private immutable _SUBSCRIPTION_ID;
uint32 private immutable _CALLBACK_GAS_LIMIT;
// Number of NFTs in the collection
uint256 public constant NUM_NFTS = 10;
// Percentage of fees to be collected by the marketplace (10%)
uint256 public constant FEE_PERCENTAGE = 10;
// Percentage of price increase for the next NFT (10%)
uint256 public constant PRICE_INCREMENT_PERCENTAGE = 10;
// Number of NFTs sold between lottery calls
uint256 public constant LOTTERY_TRIGGER_INTERVAL = 5;
// Initial delay for the first lottery call
uint256 public constant INITIAL_LOTTERY_DELAY = 90 days;
// Delay for the second mandatory lottery call
uint256 public constant SECOND_LOTTERY_DELAY = 130 days;
// Delay for the third mandatory lottery call
uint256 public constant THIRD_LOTTERY_DELAY = 170 days;
// Maximum duration for the marketplace operation
uint256 public constant MAX_MARKETPLACE_OPERATION_DAYS = 210 days;
// Duration of the marketplace lock
uint256 public constant LOCK_DURATION = 481 days;
// This address will receive the fees. Also, is the initial owner of the NFTs.
address payable public immutable ARTISTS;
// Bubble NFT contract
IBubbleNFT public immutable NFT_CONTRACT;
// Timestamp of the deployment
uint256 public immutable DEPLOYMENT_TIMESTAMP;
// ID of the NFT currently on sale
uint256 public sellingNftId;
// Number of NFTs sold
uint256 public nftsSold;
// Price of the current NFT on sale
uint256 public currentPrice;
// Flag to check if the first mandatory lottery has been done
bool public firstMandatoryLotteryDone;
// Flag to check if the second mandatory lottery has been done
bool public secondMandatoryLotteryDone;
// Timestamp of the lock
uint256 public lockTimestamp;
// Balance of the owners
mapping(address => uint256) public balances;
// Emitted when an NFT is purchased
event NFTPurchased(address indexed buyer, uint256 indexed nftId, uint256 indexed price);
// Emitted when a lottery call is requested
event LotteryRequested(uint256 indexed requestId, uint256 indexed timestamp);
// Emitted when the marketplace is locked
event MarketplaceLocked(uint256 indexed timestamp);
// Emitted when the NFTs are released
event NFTsReleased(uint256 indexed timestamp);
// Emitted when the random number is received
event RandomWordsReceived(uint256 indexed randomWords);
/**
* @dev Constructor to set the NFT contract address, the fee recipient address and the Chainlink VRF variables.
* @param nftAddress Address of the NFT contract.
* @param feeRecipientAddress Address of the fee recipient.
* @param initialPrice Price for the first NFT.
* @param vrfCoordinator Address of the Chainlink VRF Coordinator.
* @param gasLane The gas lane for the VRF Coordinator.
* @param subscriptionId The subscription ID for the VRF Coordinator.
* @param callbackGasLimit The gas limit for the VRF Coordinator callback.
*/
constructor(
address nftAddress,
address payable feeRecipientAddress,
uint256 initialPrice,
address vrfCoordinator,
bytes32 gasLane,
uint64 subscriptionId,
uint32 callbackGasLimit
) VRFConsumerBaseV2(vrfCoordinator) {
_VRF_COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
_GAS_LANE = gasLane;
_SUBSCRIPTION_ID = subscriptionId;
_CALLBACK_GAS_LIMIT = callbackGasLimit;
NFT_CONTRACT = IBubbleNFT(nftAddress);
ARTISTS = feeRecipientAddress;
// Set the price for the first NFT
currentPrice = initialPrice;
DEPLOYMENT_TIMESTAMP = block.timestamp;
}
/**
* @dev Function to purchase the current NFT in the sequential cycle.
*/
function purchase() external payable whenNotPaused {
// Check if the buyer has sent enough funds to purchase the NFT and update the price
require(msg.value >= currentPrice, "Insufficient funds to purchase NFT");
// Calculate fees and owner value
uint256 fees = (currentPrice * (FEE_PERCENTAGE*10**18)) / 10**20;
uint256 ownerValue = currentPrice - fees;
// Update the price for the next NFT (10% increase)
currentPrice = currentPrice + (currentPrice * (PRICE_INCREMENT_PERCENTAGE*10**18)) / 10**20;
// Get the ID of the NFT to be sold and increment the counter
uint256 nftId = sellingNftId;
sellingNftId = (sellingNftId+1) % (NUM_NFTS);
// Increment the count of NFTs sold
nftsSold++;
// Update the balance of the owner
address nftOwner = NFT_CONTRACT.ownerOf(nftId);
balances[nftOwner] += ownerValue;
// Transfer fees to the designated address (ARTISTS)
(bool success, ) = ARTISTS.call{value: fees}("");
require(success, "Failed to send Ether");
// Transfer the NFT to the buyer
NFT_CONTRACT.approve(address(this), nftId); // The marketplace has privileges to make approvals and transfers during the regulated state
NFT_CONTRACT.transferFrom(nftOwner, msg.sender, nftId);
emit NFTPurchased(msg.sender, nftId, msg.value);
// Check if it's time for a lottery call to potentially lock the marketplace
if( (block.timestamp >= DEPLOYMENT_TIMESTAMP + INITIAL_LOTTERY_DELAY) && (nftsSold % LOTTERY_TRIGGER_INTERVAL == 0) ) {
_performLottery();
}
}
/**
* @dev Function to withdraw the balance of the owner.
*/
function withdraw() external {
uint256 amount = balances[msg.sender];
require(amount > 0, "Insufficient balance");
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Failed to send Ether");
}
/**
* @dev This function allows performing one lottery call to potentially lock the marketplace after the first mandatory lottery delay (130 days), another one after the second mandatory lottery delay (170 days) and eventually lock the marketplace after the maximum operation time has passed (210 days).
* @notice Can be called by anyone.
*/
function tryLock() external whenNotPaused {
if(!firstMandatoryLotteryDone && block.timestamp >= DEPLOYMENT_TIMESTAMP + SECOND_LOTTERY_DELAY){
firstMandatoryLotteryDone = true;
_performLottery();
}else if(!secondMandatoryLotteryDone && block.timestamp >= DEPLOYMENT_TIMESTAMP + THIRD_LOTTERY_DELAY){
secondMandatoryLotteryDone = true;
_performLottery();
}else if(block.timestamp >= DEPLOYMENT_TIMESTAMP + MAX_MARKETPLACE_OPERATION_DAYS){
_pause();
lockTimestamp = block.timestamp;
emit MarketplaceLocked(lockTimestamp);
}else{
revert("Not the right time to lock the marketplace");
}
}
/**
* @dev Function to release the NFTs after the lockDuration has passed.
* @notice Can be called by anyone.
*/
function release() external whenPaused {
require(block.timestamp >= lockTimestamp + LOCK_DURATION, "Lock duration not yet elapsed");
// Release the restriction on the NFT contract
NFT_CONTRACT.releaseRestriction();
emit NFTsReleased(block.timestamp);
}
/**
* @dev Function to get the available balance of an account.
* @param account Address of the account.
*/
function getBalance(address account) external view returns (uint256) {
return balances[account];
}
/**
* @dev Function to get the timestamp of the deployment.
*/
function getDeploymentTimestamp() external view returns (uint256) {
return DEPLOYMENT_TIMESTAMP;
}
/**
* @dev Function to perform the lottery call and potentially lock the marketplace.
* @notice It performs a Chainlink VRF call to get a random number, which is returned in the fulfillRandomWords function called by the VRF Coordinator.
*/
function _performLottery() internal {
uint256 requestId = _VRF_COORDINATOR.requestRandomWords(
_GAS_LANE,
_SUBSCRIPTION_ID,
_REQUEST_CONFIRMATIONS,
_CALLBACK_GAS_LIMIT,
_NUM_WORDS
);
emit LotteryRequested(requestId, block.timestamp);
}
/**
* @dev This function is called by the VRF Coordinator to return the random number generated
* @notice This function pauses the marketplace with a probability of 10%.
* @param randomWords The random number returned by the VRF Coordinator.
*/
function fulfillRandomWords(uint256 /*requestId*/, uint256[] memory randomWords) internal override {
emit RandomWordsReceived(randomWords[0]);
if(randomWords[0] % 10 == 0){
_pause();
lockTimestamp = block.timestamp;
emit MarketplaceLocked(lockTimestamp);
}
}
}
// SPDX-License-Identifier: MIT
// 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;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* @title IBubbleNFT
* @author Marco López
* @notice BubbleNFT interface of ERC-721 Non-Fungible Token with additional features.
*/
interface IBubbleNFT {
/**
* @dev Function to release the transfer restriction, can only be called by the marketplace.
*/
function releaseRestriction() external;
/**
* @notice
*/
function approve(address to, uint256 tokenId) external;
/**
* @notice
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @notice
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @notice
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) external;
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/** ****************************************************************************
* @notice Interface for contracts using VRF randomness
* *****************************************************************************
* @dev PURPOSE
*
* @dev Reggie the Random Oracle (not his real job) wants to provide randomness
* @dev to Vera the verifier in such a way that Vera can be sure he's not
* @dev making his output up to suit himself. Reggie provides Vera a public key
* @dev to which he knows the secret key. Each time Vera provides a seed to
* @dev Reggie, he gives back a value which is computed completely
* @dev deterministically from the seed and the secret key.
*
* @dev Reggie provides a proof by which Vera can verify that the output was
* @dev correctly computed once Reggie tells it to her, but without that proof,
* @dev the output is indistinguishable to her from a uniform random sample
* @dev from the output space.
*
* @dev The purpose of this contract is to make it easy for unrelated contracts
* @dev to talk to Vera the verifier about the work Reggie is doing, to provide
* @dev simple access to a verifiable source of randomness. It ensures 2 things:
* @dev 1. The fulfillment came from the VRFCoordinator
* @dev 2. The consumer contract implements fulfillRandomWords.
* *****************************************************************************
* @dev USAGE
*
* @dev Calling contracts must inherit from VRFConsumerBase, and can
* @dev initialize VRFConsumerBase's attributes in their constructor as
* @dev shown:
*
* @dev contract VRFConsumer {
* @dev constructor(<other arguments>, address _vrfCoordinator, address _link)
* @dev VRFConsumerBase(_vrfCoordinator) public {
* @dev <initialization with other arguments goes here>
* @dev }
* @dev }
*
* @dev The oracle will have given you an ID for the VRF keypair they have
* @dev committed to (let's call it keyHash). Create subscription, fund it
* @dev and your consumer contract as a consumer of it (see VRFCoordinatorInterface
* @dev subscription management functions).
* @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations,
* @dev callbackGasLimit, numWords),
* @dev see (VRFCoordinatorInterface for a description of the arguments).
*
* @dev Once the VRFCoordinator has received and validated the oracle's response
* @dev to your request, it will call your contract's fulfillRandomWords method.
*
* @dev The randomness argument to fulfillRandomWords is a set of random words
* @dev generated from your requestId and the blockHash of the request.
*
* @dev If your contract could have concurrent requests open, you can use the
* @dev requestId returned from requestRandomWords to track which response is associated
* @dev with which randomness request.
* @dev See "SECURITY CONSIDERATIONS" for principles to keep in mind,
* @dev if your contract could have multiple requests in flight simultaneously.
*
* @dev Colliding `requestId`s are cryptographically impossible as long as seeds
* @dev differ.
*
* *****************************************************************************
* @dev SECURITY CONSIDERATIONS
*
* @dev A method with the ability to call your fulfillRandomness method directly
* @dev could spoof a VRF response with any random value, so it's critical that
* @dev it cannot be directly called by anything other than this base contract
* @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method).
*
* @dev For your users to trust that your contract's random behavior is free
* @dev from malicious interference, it's best if you can write it so that all
* @dev behaviors implied by a VRF response are executed *during* your
* @dev fulfillRandomness method. If your contract must store the response (or
* @dev anything derived from it) and use it later, you must ensure that any
* @dev user-significant behavior which depends on that stored value cannot be
* @dev manipulated by a subsequent VRF request.
*
* @dev Similarly, both miners and the VRF oracle itself have some influence
* @dev over the order in which VRF responses appear on the blockchain, so if
* @dev your contract could have multiple VRF requests in flight simultaneously,
* @dev you must ensure that the order in which the VRF responses arrive cannot
* @dev be used to manipulate your contract's user-significant behavior.
*
* @dev Since the block hash of the block which contains the requestRandomness
* @dev call is mixed into the input to the VRF *last*, a sufficiently powerful
* @dev miner could, in principle, fork the blockchain to evict the block
* @dev containing the request, forcing the request to be included in a
* @dev different block with a different hash, and therefore a different input
* @dev to the VRF. However, such an attack would incur a substantial economic
* @dev cost. This cost scales with the number of blocks the VRF oracle waits
* @dev until it calls responds to a request. It is for this reason that
* @dev that you can signal to an oracle you'd like them to wait longer before
* @dev responding to the request (however this is not enforced in the contract
* @dev and so remains effective only in the case of unmodified oracle software).
*/
abstract contract VRFConsumerBaseV2 {
error OnlyCoordinatorCanFulfill(address have, address want);
// solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i
address private immutable vrfCoordinator;
/**
* @param _vrfCoordinator address of VRFCoordinator contract
*/
constructor(address _vrfCoordinator) {
vrfCoordinator = _vrfCoordinator;
}
/**
* @notice fulfillRandomness handles the VRF response. Your contract must
* @notice implement it. See "SECURITY CONSIDERATIONS" above for important
* @notice principles to keep in mind when implementing your fulfillRandomness
* @notice method.
*
* @dev VRFConsumerBaseV2 expects its subcontracts to have a method with this
* @dev signature, and will call it once it has verified the proof
* @dev associated with the randomness. (It is triggered via a call to
* @dev rawFulfillRandomness, below.)
*
* @param requestId The Id initially returned by requestRandomness
* @param randomWords the VRF output expanded to the requested number of words
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual;
// rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
// proof. rawFulfillRandomness then calls fulfillRandomness, after validating
// the origin of the call
function rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) external {
if (msg.sender != vrfCoordinator) {
revert OnlyCoordinatorCanFulfill(msg.sender, vrfCoordinator);
}
fulfillRandomWords(requestId, randomWords);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface VRFCoordinatorV2Interface {
/**
* @notice Get configuration relevant for making requests
* @return minimumRequestConfirmations global min for request confirmations
* @return maxGasLimit global max for request gas limit
* @return s_provingKeyHashes list of registered key hashes
*/
function getRequestConfig() external view returns (uint16, uint32, bytes32[] memory);
/**
* @notice Request a set of random words.
* @param keyHash - Corresponds to a particular oracle job which uses
* that key for generating the VRF proof. Different keyHash's have different gas price
* ceilings, so you can select a specific one to bound your maximum per request cost.
* @param subId - The ID of the VRF subscription. Must be funded
* with the minimum subscription balance required for the selected keyHash.
* @param minimumRequestConfirmations - How many blocks you'd like the
* oracle to wait before responding to the request. See SECURITY CONSIDERATIONS
* for why you may want to request more. The acceptable range is
* [minimumRequestBlockConfirmations, 200].
* @param callbackGasLimit - How much gas you'd like to receive in your
* fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords
* may be slightly less than this amount because of gas used calling the function
* (argument decoding etc.), so you may need to request slightly more than you expect
* to have inside fulfillRandomWords. The acceptable range is
* [0, maxGasLimit]
* @param numWords - The number of uint256 random values you'd like to receive
* in your fulfillRandomWords callback. Note these numbers are expanded in a
* secure way by the VRFCoordinator from a single random value supplied by the oracle.
* @return requestId - A unique identifier of the request. Can be used to match
* a request to a response in fulfillRandomWords.
*/
function requestRandomWords(
bytes32 keyHash,
uint64 subId,
uint16 minimumRequestConfirmations,
uint32 callbackGasLimit,
uint32 numWords
) external returns (uint256 requestId);
/**
* @notice Create a VRF subscription.
* @return subId - A unique subscription id.
* @dev You can manage the consumer set dynamically with addConsumer/removeConsumer.
* @dev Note to fund the subscription, use transferAndCall. For example
* @dev LINKTOKEN.transferAndCall(
* @dev address(COORDINATOR),
* @dev amount,
* @dev abi.encode(subId));
*/
function createSubscription() external returns (uint64 subId);
/**
* @notice Get a VRF subscription.
* @param subId - ID of the subscription
* @return balance - LINK balance of the subscription in juels.
* @return reqCount - number of requests for this subscription, determines fee tier.
* @return owner - owner of the subscription.
* @return consumers - list of consumer address which are able to use this subscription.
*/
function getSubscription(
uint64 subId
) external view returns (uint96 balance, uint64 reqCount, address owner, address[] memory consumers);
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @param newOwner - proposed new owner of the subscription
*/
function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external;
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @dev will revert if original owner of subId has
* not requested that msg.sender become the new owner.
*/
function acceptSubscriptionOwnerTransfer(uint64 subId) external;
/**
* @notice Add a consumer to a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - New consumer which can use the subscription
*/
function addConsumer(uint64 subId, address consumer) external;
/**
* @notice Remove a consumer from a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - Consumer to remove from the subscription
*/
function removeConsumer(uint64 subId, address consumer) external;
/**
* @notice Cancel a subscription
* @param subId - ID of the subscription
* @param to - Where to send the remaining LINK to
*/
function cancelSubscription(uint64 subId, address to) external;
/*
* @notice Check to see if there exists a request commitment consumers
* for all consumers and keyhashes for a given sub.
* @param subId - ID of the subscription
* @return true if there exists at least one unfulfilled request for the subscription, false
* otherwise.
*/
function pendingRequestExists(uint64 subId) external view returns (bool);
}
{
"compilationTarget": {
"src/BubbleMarketplace.sol": "BubbleMarketplace"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@chainlink/contracts/=lib/chainlink/contracts/src/",
":@forge-std/=lib/forge-std/src/",
":@foundry-devops/=lib/foundry-devops/src/",
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":@solmate/=lib/solmate/src/",
":chainlink/=lib/chainlink/contracts/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":foundry-devops/=lib/foundry-devops/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":solmate/=lib/solmate/src/"
]
}
[{"inputs":[{"internalType":"address","name":"nftAddress","type":"address"},{"internalType":"address payable","name":"feeRecipientAddress","type":"address"},{"internalType":"uint256","name":"initialPrice","type":"uint256"},{"internalType":"address","name":"vrfCoordinator","type":"address"},{"internalType":"bytes32","name":"gasLane","type":"bytes32"},{"internalType":"uint64","name":"subscriptionId","type":"uint64"},{"internalType":"uint32","name":"callbackGasLimit","type":"uint32"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[{"internalType":"address","name":"have","type":"address"},{"internalType":"address","name":"want","type":"address"}],"name":"OnlyCoordinatorCanFulfill","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"LotteryRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"MarketplaceLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":true,"internalType":"uint256","name":"nftId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"price","type":"uint256"}],"name":"NFTPurchased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"NFTsReleased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"randomWords","type":"uint256"}],"name":"RandomWordsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"ARTISTS","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPLOYMENT_TIMESTAMP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PERCENTAGE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INITIAL_LOTTERY_DELAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOCK_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOTTERY_TRIGGER_INTERVAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_MARKETPLACE_OPERATION_DAYS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NFT_CONTRACT","outputs":[{"internalType":"contract IBubbleNFT","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NUM_NFTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRICE_INCREMENT_PERCENTAGE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECOND_LOTTERY_DELAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"THIRD_LOTTERY_DELAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"firstMandatoryLotteryDone","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDeploymentTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nftsSold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"purchase","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256[]","name":"randomWords","type":"uint256[]"}],"name":"rawFulfillRandomWords","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"release","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"secondMandatoryLotteryDone","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sellingNftId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tryLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]