Skip to content

Commit

Permalink
Update quickstarts to VRF 2.5 (smartcontractkit#1923)
Browse files Browse the repository at this point in the history
* vrf-direct-funding-example

* vrf-mystery-box

* Apply suggestions from code review

* Small corrections

---------

Co-authored-by: Dwight Lyle <[email protected]>
  • Loading branch information
khadni and dwightjl authored Jun 6, 2024
1 parent c8f929e commit 32b7564
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 53 deletions.
67 changes: 38 additions & 29 deletions src/content/quickstarts/pass-cost-to-end-user.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ This gives you an existing [Foundry](https://github.com/foundry-rs/foundry) proj
export PRIVATE_KEY=
# Explorer API key used to verify contracts
export EXPLORER_KEY=
export ETHERSCAN_API_KEY=
```

1. In your `.env` file, fill in these values:
Expand Down Expand Up @@ -195,11 +195,11 @@ Test the contract as the end-user.

1. Open the [LINK Token Contract](https://docs.chain.link/resources/link-token-contracts) write functions for your network in an explorer. For example, on Ethereum Sepolia you can open the [0x779877A7B0D9E8603169DdbD7836e478b4624789](https://sepolia.etherscan.io/token/0x779877A7B0D9E8603169DdbD7836e478b4624789#writeContract) contract.

1. Connect your wallet to the scanning app so you can run write functions.
1. Connect your wallet to the block explorer (e.g. [Etherscan](https://sepolia.etherscan.io/) for Ethereum Sepolia) so you can run write functions.

<ClickToZoom
src="/images/quickstarts/pass-cost-to-end-user/connect-to-scanner.webp"
alt="Connect wallet to the scanner"
alt="Connect wallet to the block explorer"
style="max-width: 50%;"
/>

Expand All @@ -211,15 +211,15 @@ Test the contract as the end-user.
style="max-width: 50%;"
/>

1. Open your deployed contract in the scanner.
1. Open your deployed contract in the block explorer.

1. On the **Contract** tab, open the **Write Contract** functions list for your deployed contract.

1. Again, connect your wallet to the scanner so you can run write functions on the deployed contract.
1. Again, connect your wallet to the block explorer so you can run write functions on the deployed contract.

<ClickToZoom
src="/images/quickstarts/pass-cost-to-end-user/connect-to-scanner-deployed-contract.webp"
alt="Connect to the scanner while viewing your deployed contract"
alt="Connect to the block explorer while viewing your deployed contract"
style="max-width: 50%;"
/>

Expand Down Expand Up @@ -261,37 +261,46 @@ struct RequestStatus {
mapping(uint256 => RequestStatus) public s_requests;
```

The `requestRandomWords()` function uses a method in the VRF wrapper contract to calculate the request cost upfront, and then transfers enough LINK to the VRF wrapper to cover the request. After sending the request, it sets up the `RequestStatus` with the cost and other attributes to be updated after request fulfillment.
The `requestRandomWords` function calls the `calculateRequestPrice` function on the VRF wrapper contract to calculate the request cost upfront, and then transfers enough LINK to the VRF wrapper to cover the request. After sending the request, the `requestRandomWords` function stores the `RequestStatus` with the cost and other request attributes, and emits a `RequestSent` event.

```solidity
uint256 requestPrice = vrfWrapper.calculateRequestPrice(
requestConfig.callbackGasLimit
);
require(
linkTokenContract.transferFrom(
msg.sender,
address(this),
requestPrice
),
"Not enough LINK"
function requestRandomWords() external returns (uint256) {
// Calculate the price required for the VRF request based on the configured parameters.
uint256 requestPrice = calculateRequestPrice();
// Transfer the required LINK tokens from the caller's address to this contract.
transferLinkFromUser(requestPrice);
// Send the randomness request to the VRF Wrapper contract and retrieve the request ID and the paid price.
bytes memory extraArgs = VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: false}));
(uint256 requestId, uint256 reqPrice) = requestRandomness(
requestConfig.callbackGasLimit, requestConfig.requestConfirmations, requestConfig.numWords, extraArgs
);
...
// Record the request details.
s_requests[requestId] = RequestStatus({paid: reqPrice, randomWords: new uint256[](0), fulfilled: false});
requestIds.push(requestId);
lastRequestId = requestId;
s_requests[requestId] = RequestStatus({
paid: requestPrice,
randomWords: new uint256[](0),
fulfilled: false
});
// Emit an event indicating that a request has been sent.
emit RequestSent(requestId, requestConfig.numWords);
return requestId;
}
[...]
function calculateRequestPrice() internal view returns (uint256) {
return i_vrfV2PlusWrapper.calculateRequestPrice(requestConfig.callbackGasLimit, requestConfig.numWords);
}
```

After the VRF service fulfills your randomness request, it calls back your `fulfillRandomWords()` function, where you implement logic to handle the random values. It's recommended to keep the processing in your callback function minimal to save on gas costs. In this case, the example contract updates the `RequestStatus` in the request mapping, and emits an event indicating that the request has been fulfilled. Another part of your application can listen for this event and further process the random values separately from the callback function.
After the VRF service fulfills your randomness request, it calls back your `fulfillRandomWords` function, where you implement logic to handle the random values. It's recommended to keep the processing in your callback function minimal to save on gas costs. In this case, the example contract updates the `RequestStatus` in the request mapping, and emits an event indicating that the request has been fulfilled. Another part of your application can listen for this event and further process the random values separately from the callback function.

```solidity
function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal override {
require(s_requests[_requestId].paid > 0, "request not found");
s_requests[_requestId].fulfilled = true;
s_requests[_requestId].randomWords = _randomWords;
emit RequestFulfilled(_requestId, _randomWords, s_requests[_requestId].paid);
require(s_requests[_requestId].paid > 0, "request not found");
s_requests[_requestId].fulfilled = true;
s_requests[_requestId].randomWords = _randomWords;
emit RequestFulfilled(_requestId, _randomWords, s_requests[_requestId].paid);
}
```
54 changes: 30 additions & 24 deletions src/content/quickstarts/vrf-mystery-box.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,21 @@ import { Accordion, Aside, CodeSample } from "@components"

## Overview

This is a template for NFT collection with a mystery box mechanic powered by [Chainlink VRF](https://vrf.chain.link/).
This is a template for NFT collection with a mystery box mechanic powered by [Chainlink VRF V2.5](https://docs.chain.link/vrf).

Smart contracts are based on the gas efficient [ERC721Psi](https://github.com/estarriolvetch/ERC721Psi) and are fully tested. It's super easy to deploy and configure with most of the steps automated in the deploy script.
Smart contracts are based on the gas efficient [ERC721Psi](https://github.com/estarriolvetch/ERC721Psi). It's super easy to deploy and configure with most of the steps automated in the deploy script.

Some of the key features of this template include private minting stage with a merkle tree, rate limited batch minting, delayed reveal with randomization technique to save gas, provenance hash to verify the authenticity of the metadata, royalties for secondary sales, and configurable parameters.
The key features of this template include:
- a private minting stage with a merkle tree
- rate limited batch minting
- delayed reveal with randomization technique to save gas
- provenance hash to verify the authenticity of the metadata
- royalties for secondary sales
- configurable parameters

## Objective

You will use a HardHat project with the Chainlink Hardhat Plugin to deploy an existing MysteryBox application on Avalanche Fuji testnet.
You will use a Hardhat project to deploy an existing MysteryBox application on Ethereum Sepolia.

<Aside type="caution" title="Disclaimer">
This tutorial represents an example of using a Chainlink product or service and is provided to help you understand how
Expand All @@ -41,12 +47,12 @@ You will use a HardHat project with the Chainlink Hardhat Plugin to deploy an ex
Before you start this tutorial, complete the following items:

- Set up a cryptocurrency wallet such as [MetaMask](https://metamask.io/).
- Fund your wallet with AVAX and LINK from [faucets.chain.link](https://faucets.chain.link/fuji).
- Fund your wallet with testnet ETH and LINK from [faucets.chain.link](https://faucets.chain.link/sepolia).
- Install [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). Run `git --version` to check the installation.
- Install [Nodejs](https://nodejs.org/en/) version 16 or later.
- Optionally, you can [install Yarn](https://classic.yarnpkg.com/lang/en/docs/install/) and use it to run this example instead of using `npm`.
- [Create an SnowTrace API key](https://docs.snowtrace.io/getting-started/viewing-api-usage-statistics) if you do not already have one. This is used to verifiy your contracts onchain.
- Create an account with [Infura](https://www.infura.io/) or [Alchemy](https://www.alchemy.com/) to use as an RPC endpoint if you do not already have one.
- [Create an Etherscan API key](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics) if you do not already have one. This is used to verify your contracts onchain.
- Create an account with [Infura](https://www.infura.io/) or [Alchemy](https://www.alchemy.com/) to obtain an RPC endpoint if you do not already have one.

## Steps to implement

Expand Down Expand Up @@ -88,9 +94,9 @@ Set the parameters for the NFT contract. If you don't have a VRF subscription, d
| `NFT_UNREVEALED_URI` | The metadata URI for all tokens before reveal | `https://example.com` |
| `NFT_MAX_SUPPLY` | The maximum number of tokens that can be minted | `100` |
| `NFT_MAX_MINT_PER_USER` | The maximum number of tokens that can be minted per user address | `10` |
| `NFT_FEE` | The fee for minting a token in ETH | `0.1` |
| `NFT_FEE` | The fee for minting a token in ETH | `0.01` |
| `NFT_ROYALTY_BPS` | The royalty fee for selling a token in basis points | `500` |
| `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` |
| `VRF_SUBSCRIPTION_ID` | A funded Chainlink VRF V2.5 subscription ID. If you leave this blank, a new subscription will be created and funded on deploy. | `79850349243438349975305816782035019118399435445660033947721688676378382535454` |


</Accordion>
Expand All @@ -101,13 +107,13 @@ Set the parameters for the NFT contract. If you don't have a VRF subscription, d

| Parameter | Description | Example |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| `FUJI_URL` | The RPC URL for Avalanche Fuji testnet. You can use the default, or use your own URL. If you later decide to use Polygon Mumbai or another testnet, set the appropriate parameter from the template. | `https://avalanche-mainnet.infura.io/v3/your-api-key` |
| `PRIVATE_KEY` | The private key of the account you want to deploy from. | `0xabc123abc123abc123abc123abc123...` |
| `SCANNER_API_KEY` | The API key for SnowTrace used for contract verification. If you decide to deploy on Polygon Mumbai or another testnet, use that key instead. | `ABC123ABC123ABC123ABC123ABC123ABC1` |
| `SEPOLIA_RPC_URL` | The RPC URL for the Ethereum Sepolia network. | `https://eth-sepolia.g.alchemy.com/v2/...` |
| `PRIVATE_KEY` | The private key of the account you want to deploy from. | `abc123abc123abc123abc123abc123...` |
| `SCANNER_API_KEY` | The API key for Etherscan used for contract verification. | `ABC123ABC123ABC123ABC123ABC123ABC1` |

</Accordion>

<Accordion title="Test the contracts locally" number={3}>
<Accordion title="Test the contracts locally" number={4}>

To run the unit tests, run the following command:

Expand All @@ -129,7 +135,7 @@ npm run coverage

</Accordion>

<Accordion title="Deploy the example contract" number={3}>
<Accordion title="Deploy the example contract" number={5}>

Run the `npx hardhat run` command and replace `<network>` with the network that you want to deploy to. The network must be configured in [`hardhat.config.ts`](https://github.com/smartcontractkit/quickstarts-mysterybox/blob/main/hardhat.config.ts).

Expand All @@ -139,32 +145,32 @@ npx hardhat run scripts/deploy.ts --network <network>

In addition to deploying the contract, the deploy script also completes the following steps automatically:

1. Create and fund a VRF subscription if one is not provided. Make sure the deployer account has enough LINK to fund the subscription. The initial funding amount is configured in [`network-config.ts`](https://github.com/smartcontractkit/quickstarts-mysterybox/blob/main/network-config.ts). For testnets, you can use the [LINK faucet](https://faucets.chain.link/) to get LINK. If your subscription is underfunded, your VRF request will be [pending](/vrf/v2/subscription/ui#pending) for 24 hours. If this happens, check the [Subscription Manager](https://vrf.chain.link]) to see the additional balance needed.
1. Create and fund a VRF V2.5 subscription if one is not provided. Make sure the deployer account has enough LINK to fund the subscription. The initial funding amount is configured in [`network-config.ts`](https://github.com/smartcontractkit/quickstarts-mysterybox/blob/main/network-config.ts). For testnets, you can use the [LINK faucet](https://faucets.chain.link/) to get LINK. If your subscription is underfunded, your VRF request will be [pending](/vrf/v2/subscription/ui#pending) for 24 hours. If this happens, check the [Subscription Manager](https://vrf.chain.link) to see the additional balance needed.

1. Add the deployed contract address as a consumer to the VRF subscription. 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.
1. Add the deployed contract address as a consumer to the VRF subscription. If you provided a subscription ID, make sure the deployer account is the owner of the subscription. Otherwise, comment out the `addConsumerToSubscription` function in the deploy script and add the contract address manually.

1. Generate a [Merkle tree](https://ethereum.org/en/developers/tutorials/merkle-proofs-for-offline-data-integrity/#introduction) for the allowlist. The merkle tree is generated from the address list in `scripts/data/whitelist.json` file. Leave the file empty if you don't want to do a private mint.
1. Generate a [Merkle tree](https://ethereum.org/en/developers/tutorials/merkle-proofs-for-offline-data-integrity/#introduction) for the allowlist. The merkle tree is generated from the address list in `scripts/data/whitelist.json` file.

1. Verify the contract on SnowTrace. This is important to show users the source code for the contract so they can confirm how it works. It's also required to run the example yourself.
1. Verify the contract on Etherscan. This is important to show users the source code for the contract so they can confirm how it works. It's also required to run the example yourself.

After the deployment is complete, the terminal prints the link to view your contract on SnowTrace.
After the deployment is complete, the terminal prints the link to view your contract on Etherscan.

</Accordion>

<Accordion title="Run the example" number={4}>
<Accordion title="Run the example" number={6}>

After the contract is deployed, run the example using contract functions on SnowTrace.
After the contract is deployed, run the example using contract functions on Etherscan.

1. Open the SnowTrace link from your terminal to view your contract.
1. Open the Etherscan link from your terminal to view your contract.
1. On the **Contract** tab, click the **Write Contract** tab to view your contract's write functions.
1. Click **Connect to Web3** to connect your wallet to SnowTrace so you can run the functions.
1. Click **Connect to Web3** to connect your wallet to Etherscan so you can run the functions.
1. Enable public minting:
1. Open the `setPublicMint` function.
1. Enter a value of `true`.
1. Click **Write** to run the function. Approve the transaction in MetaMask.
1. Mint an NFT to test the contract:
1. Open the `publicMint` function.
1. Enter a `publicMint` value of `0.1` AVAX and an amount of `1`.
1. Enter a `publicMint` value of `0.01` ETH and an amount of `1`.
1. Click **Write** to run the function. Approve the transaction in MetaMask.
1. Reveal the box:
1. Open the `reveal` function.
Expand Down

0 comments on commit 32b7564

Please sign in to comment.