The lootbox is a utility contract that allow users to open it to receive a random reward.
It supports ERC20, ERC721, and ERC1155 tokens as rewards. The rewards are distributed from a pool of tokens that are trasferred to the contract on deploy.
The lootbox contract uses Chainlink VRF to generate a random number that is used to determine the reward.
- Requirements
- Getting Started
- Setup
- Test
- Deploy
- Open
- Randomness
- Claim Rewards
- Withdraw Funds
- Configuration
- Format
- Lint
- git
- You'll know you did it right if you can run
git --version
and you see a response likegit version x.x.x
- You'll know you did it right if you can run
- Nodejs 16.0.0 or higher
- You'll know you've installed nodejs right if you can run:
node --version
and get an output like:v16.x.x
- You'll know you've installed nodejs right if you can run:
Clone the repo and install all dependencies.
git clone https://github.com/smartcontractkit/quickstarts-lootbox.git
cd quickstarts-lootbox
npm install
Alternatively, you can use yarn to install dependencies.
yarn install
Copy the .env.example
file to .env
and fill in the values.
cp .env.example .env
Parameter | Description | Example |
---|---|---|
NETWORK_RPC_URL |
The RPC URL for the network you want to deploy to. | https://sepolia.infura.io/v3/your-api-key |
PRIVATE_KEY |
The private key of the account you want to deploy from. | 0xabc123abc123abc123abc123abc123... |
ETHERSCAN_API |
The API key for Etherscan needed for contract verification. | ABC123ABC123ABC123ABC123ABC123ABC1 |
Parameter | Description | Example |
---|---|---|
LOOTBOX_FEE_PER_OPEN |
The fee per open in ETH | 0.1 |
LOOTBOX_AMOUNT_DISTRIBUTED_PER_OPEN |
The amount of reward units distributed per open | 1 |
LOOTBOX_OPEN_START_TIMESTAMP |
The start timestamp in UNIX time for the public open. Leave blank to start immediately. | 1630000000 |
VRF_SUBSCRIPTION_ID |
A funded Chainlink VRF subscription ID. If you leave this blank, a new subscription will be created and funded on deploy. | 123 |
There's an example configuration file in scripts/data/tokens.json
which includes a list of all suppoted token types:
- ERC20
{
"tokenType": "ERC20",
"assetContract": "0x0000000000000000000000000000000000000001",
"totalAmount": "100000000000000000000",
"amountPerUnit": "10000000"
}
- ERC721
{
"tokenType": "ERC721",
"assetContract": "0x0000000000000000000000000000000000000002",
"tokenIds": ["1", "2"]
}
- ERC1155
{
"tokenType": "ERC1155",
"assetContract": "0x0000000000000000000000000000000000000003",
"tokenId": "0",
"totalAmount": "100",
"amountPerUnit": "10"
}
The amounts per unit are used to calculate the lootbox supply of rewards. For example, if you want to distribute 100 tokens of a specific ERC20 token, you would set the totalAmount
to 100000000000000000000
and the amountPerUnit
to 1000000000000000000
. This would result in a lootbox supply of 100 units.
Add all the tokens you want to distribute as rewards to the array in the list of tokens by following the format above.
Note: The deployer account must own all the tokens you want to distribute as rewards.
The merkle tree for the private openning stage is generated from the address list in scripts/data/whitelist.json
file. Edit the file and add all the addresses you want to list.
Leave the file empty if you don't want to do a private mint and the contract will be initialized in public openning mode.
To run the unit tests, run the following command.
npm run test
If you want to see gas usage, run the following command.
REPORT_GAS=true npm run test
For coverage reports, run the following command.
npm run coverage
Besides deploying the contract, the deploy script will also:
-
Approve each token configured in
scripts/data/tokens.json
for the deployed contract address.Note: This is a mandatory step because the contract must store the tokens in its own balance. So make sure the deployer account owns all the tokens you want to distribute as rewards.
-
Generate a merkle tree for the whitelist.
-
Create and fund a VRF subscription if one is not provided.
Note: Make sure the deployer account has enough LINK to fund the subscription. The initial funding amount is configured in
network-config.js
. For testnets, you can use the LINK faucet. -
Add the deployed contract address as a consumer to the VRF subscription.
Note: If you provided a subscription ID, make sure the deployer account is the owner of the subscription. Otherwise, comment out the
addVrfConsumer
function in the deploy script and add the contract address manually. -
Verify the contract on Etherscan. If you want to skip this step, comment out the
verify
function in the deploy script.
To run the deploy script, run the following command and replace <network>
with the network you want to deploy to.
npx hardhat run scripts/deploy.js --network <network>
Note: The network must be configured in hardhat.config.js
.
Once the contract is deployed and the start timestamp is reached, users can start opening the lootbox. The amount of rewards they receive depends on the amount of units they open by specifying the amountToOpen
parameter and paying the corresponding fee.
There are two modes for opening the lootbox which can be toggled by the owner account.
In this mode, the lootbox is only open to whitelisted addresses by calling the privateOpen
function and providing merkle proof for the address. To generate it, see merkletreejs.
The whitelist is set on contract deployment and can be changed by calling the setWhitelistRoot
function from the owner account. The private mode can be enabled or disabled at any time by calling the setPrivateOpen
function from the owner account.
When the private mode is disabled, anyone can open the lootbox by calling the publicOpen
function. It will do the same thing as the privateOpen
function but without the merkle proof.
The contract uses Chainlink VRF to generate randomness which is used to determine the rewards the user receives.
Because the randomness is generated off-chain, the contract will not be able to transfer the rewards immediately. Instead, it will store the request and once the randomness is received, the user or anyone else can call the claimRewards
function to transfer the rewards to the user.
The lootbox creator can also call the claimRewards
function and improve the user experience by transferring the rewards to the user immediately. This can be further automated by using Chainlink Automation.
The rewards for an open request can be claimed by calling the claimRewards
function and passing the opener address as the parameter. This will transfer the rewards to the opener address.
The claim function can only be called after the randomness is fulfilled. It can be checked by calling the canClaimRewards
function and passing the opener address as the parameter.
Note: One address can only have one open request at a time. If you try to open the lootbox again before the previous request is fulfilled, the transaction will revert with PendingOpenRequest
error.
At any time, the owner can withdraw funds from the collected fees by calling the withdraw
function. By doing so the contract balance will be transferred to the owner account.
Upon deployment, some of the contract parameters can be changed by calling the following functions from the owner account.
Function | Description | Parameters |
---|---|---|
setWhitelistRoot |
Set new merkle root for the whitelist. | whitelistRoot |
setPrivateOpen |
Enable/disable public openning mode. | privateOpenEnabled |
For formatting, we use prettier.
To check the formatting, run the following command.
npm run prettier:check
To fix the formatting, run the following command.
npm run prettier:write
For linting, we use eslint.
To run the linter, run the following command.
npm run lint
⚠️ Disclaimer: "This tutorial represents an educational example to use a Chainlink system, product, or service and is provided to demonstrate how to interact with Chainlink’s systems, products, and services to integrate them into your own. This template is provided “AS IS” and “AS AVAILABLE” without warranties of any kind, it has not been audited, and it may be missing key checks or error handling to make the usage of the system, product or service more clear. Do not use the code in this example in a production environment without completing your own audits and application of best practices. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs that are generated due to errors in code."