diff --git a/content/course/interchain-messaging/13-access-chainlink-vrf-services/01-introduction.mdx b/content/course/interchain-messaging/13-access-chainlink-vrf-services/01-introduction.mdx new file mode 100644 index 0000000..bdb76d1 --- /dev/null +++ b/content/course/interchain-messaging/13-access-chainlink-vrf-services/01-introduction.mdx @@ -0,0 +1,19 @@ +--- +title: Introduction +description: Learn how to use ICM to access Chainlink VRF services on L1 networks that do not have direct Chainlink support. +updated: 2024-10-21 +authors: [0xstt] +icon: Book +--- + +As decentralized applications (dApps) expand across multiple blockchains, some Layer 1 (L1) networks lack direct support from essential services like **Chainlink VRF (Verifiable Random Functions)**. This presents a significant challenge for developers who rely on verifiable randomness for use cases such as gaming, lotteries, NFT minting, and other decentralized functions that require unbiased, unpredictable random numbers. + +The challenge arises because not every L1 network has integrated with Chainlink, meaning developers on those chains are left without native access to VRF services. Without verifiable randomness, critical aspects of dApps, such as fairness and security, can be compromised. + +### Why ICM is Necessary + +To address this gap, **Interchain Messaging (ICM)** provides a solution by allowing L1 networks that don’t have direct Chainlink support to still access these services. Through ICM, any blockchain can request VRF outputs from a Chainlink-supported network (e.g., Fuji) and receive the results securely on their own L1. + +This cross-chain solution unlocks the ability to use Chainlink VRF on unsupported networks, bypassing the need for native integration and ensuring that dApp developers can continue building secure and fair decentralized applications. + +In the following sections, we will explore how to use ICM to access Chainlink VRF services across different chains by deploying two key smart contracts: one on the Chainlink-supported network and another on the target L1. \ No newline at end of file diff --git a/content/course/interchain-messaging/13-access-chainlink-vrf-services/02-understanding-the-flow.mdx b/content/course/interchain-messaging/13-access-chainlink-vrf-services/02-understanding-the-flow.mdx new file mode 100644 index 0000000..9193169 --- /dev/null +++ b/content/course/interchain-messaging/13-access-chainlink-vrf-services/02-understanding-the-flow.mdx @@ -0,0 +1,68 @@ +--- +title: Understanding the Flow +description: Learn how requests for random words flow across chains using ICM. +updated: 2024-10-21 +authors: [0xstt] +icon: BookOpen +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +This section explains how random words are requested and fulfilled using Chainlink VRF across two different blockchains through **Interchain Messaging (ICM)**. The entire process involves several key components: the decentralized application (DApp), `CrossChainVRFConsumer`, `CrossChainVRFWrapper`, and `TeleporterMessenger`. + +Let’s walk through the flow step by step: + + + + +### DApp Submits Request for Random Words + +The DApp, running on an L1 without direct Chainlink support, initiates a request for verifiable randomness by interacting with the `CrossChainVRFConsumer` contract deployed on its chain. + + + + +### `CrossChainVRFConsumer` Sends Cross-Chain Message + +Once the DApp submits the request, the `CrossChainVRFConsumer` prepares a message containing the necessary VRF request parameters (such as `keyHash`, `request confirmations`, `gas limit`, etc.). This message is sent across chains via the `TeleporterMessenger` to the `CrossChainVRFWrapper` on the Chainlink-supported network (e.g., Fuji). + + + + +### `TeleporterMessenger` Receives Message & Calls `CrossChainVRFWrapper` +1. On the Chainlink-supported network, the `TeleporterMessenger` receives the cross-chain message sent by the `CrossChainVRFConsumer`. It passes the message to the `CrossChainVRFWrapper`, which is authorized to handle VRF requests. +2. The `CrossChainVRFWrapper` contract, deployed on the supported network, sends the request to **Chainlink VRF** for random words, using the parameters received in the message (e.g., `subscription ID`, `callback gas limit`, etc.). + + + + + +### Chainlink VRF Fulfills Random Words Request + +1. The **Chainlink VRF** fulfills the request and returns the random words to the `CrossChainVRFWrapper` contract by invoking its callback function. +2. Once the random words are received, the `CrossChainVRFWrapper` encodes the fulfilled random words and sends them back as a cross-chain message to the `CrossChainVRFConsumer` on the original L1. + + + + + +### `TeleporterMessenger` Returns Random Words to `CrossChainVRFConsumer` + +The `TeleporterMessenger` on the original L1 receives the message containing the random words and passes it to the `CrossChainVRFConsumer`. + + + + + +### `CrossChainVRFConsumer` Returns Random Words to the DApp + +Finally, the `CrossChainVRFConsumer` processes the random words and sends them to the DApp that originally requested them, completing the flow. + + + + + +![](/course-images/interchain-messaging/cross-chain-vrf.png) + +This end-to-end process demonstrates how decentralized applications on unsupported L1 networks can request verifiable randomness from Chainlink VRF, leveraging **ICM** to handle cross-chain communication. + diff --git a/content/course/interchain-messaging/13-access-chainlink-vrf-services/03-orchestrating-vrf-requests.mdx b/content/course/interchain-messaging/13-access-chainlink-vrf-services/03-orchestrating-vrf-requests.mdx new file mode 100644 index 0000000..de93e2e --- /dev/null +++ b/content/course/interchain-messaging/13-access-chainlink-vrf-services/03-orchestrating-vrf-requests.mdx @@ -0,0 +1,126 @@ +--- +title: Orchestrating VRF Requests Over Multiple Chains (Wrapper) +description: Learn how the CrossChainVRFWrapper contract on a Chainlink-supported L1 handles cross-chain requests for VRF. +updated: 2024-10-21 +authors: [0xstt] +icon: BookOpen +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +The `CrossChainVRFWrapper` contract plays a critical role in handling requests for randomness from an unsupported L1. It is deployed on a **Chainlink-supported network** (like Avalanche Fuji) and serves as the intermediary that interacts with Chainlink VRF to request random words. + +Here’s how it functions as the provider for VRF services: + +## Receives Cross-Chain Messages + + + + +When the `CrossChainVRFConsumer` on the unsupported L1 initiates a request for randomness, it sends a cross-chain message via the `TeleporterMessenger` to the `CrossChainVRFWrapper` on the supported network. + + + + +The `CrossChainVRFWrapper` verifies that the request came from an authorized address. This is essential to ensure that only verified consumers can request randomness. + + + + +Upon receiving a valid request, the **VRFWrapper** calls **Chainlink VRF** and sends the request with the required parameters, such as the number of random words, request confirmations, and gas limits. + + + + +The `CrossChainVRFWrapper` keeps track of all pending requests using a mapping, associating each request ID with its destination (the L1 where the `CrossChainVRFConsumer` resides). This ensures that the random words are returned to the correct destination once fulfilled. + + + + + + +```solidity +function receiveTeleporterMessage( + bytes32 originChainID, + address originSenderAddress, + bytes calldata message +) external { + require(msg.sender == address(teleporterMessenger), "Caller is not the TeleporterMessenger"); + // Verify that the origin sender address is authorized + require(authorizedSubscriptions[originSenderAddress].isAuthorized, "Origin sender is not authorized"); + uint256 subscriptionId = authorizedSubscriptions[originSenderAddress].subscriptionId; + // Verify that the subscription ID belongs to the correct owner + (,,,, address[] memory consumers) = s_vrfCoordinator.getSubscription(subscriptionId); + // Check wrapper contract is a consumer of the subscription + bool isConsumer = false; + for (uint256 i = 0; i < consumers.length; i++) { + if (consumers[i] == address(this)) { + isConsumer = true; + break; + } + } + require(isConsumer, "Contract is not a consumer of this subscription"); + // Decode message to get the VRF parameters + CrossChainRequest memory vrfMessage = abi.decode(message, (CrossChainRequest)); + // Request random words + VRFV2PlusClient.RandomWordsRequest memory req = VRFV2PlusClient.RandomWordsRequest({ + keyHash: vrfMessage.keyHash, + subId: subscriptionId, + requestConfirmations: vrfMessage.requestConfirmations, + callbackGasLimit: vrfMessage.callbackGasLimit, + numWords: vrfMessage.numWords, + extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: vrfMessage.nativePayment})) + }); + uint256 requestId = s_vrfCoordinator.requestRandomWords(req); + pendingRequests[requestId] = CrossChainReceiver({ + destinationBlockchainId: originChainID, + destinationAddress: originSenderAddress + }); +} +``` + + + +## Handles Callback from Chainlink VRF + + + + +When **Chainlink VRF** fulfills the randomness request, the `CrossChainVRFWrapper` receives the random words through a callback function. This ensures that the request has been successfully processed. + + + + +After receiving the random words, the `CrossChainVRFWrapper` encodes the result and sends it back as a cross-chain message to the `CrossChainVRFConsumer` on the unsupported L1. This is done using the `TeleporterMessenger`. + + + + + + +```solidity +function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override { + require(pendingRequests[requestId].destinationAddress != address(0), "Invalid request ID"); + // Create CrossChainResponse struct + CrossChainResponse memory crossChainResponse = CrossChainResponse({ + requestId: requestId, + randomWords: randomWords + }); + bytes memory encodedMessage = abi.encode(crossChainResponse); + // Send cross chain message using ITeleporterMessenger interface + TeleporterMessageInput memory messageInput = TeleporterMessageInput({ + destinationBlockchainID: pendingRequests[requestId].destinationBlockchainId, + destinationAddress: pendingRequests[requestId].destinationAddress, + feeInfo: TeleporterFeeInfo({ feeTokenAddress: address(0), amount: 0 }), + requiredGasLimit: 100000, + allowedRelayerAddresses: new address[](0), + message: encodedMessage + }); + teleporterMessenger.sendCrossChainMessage(messageInput); + delete pendingRequests[requestId]; +} +``` + + + +In summary, the `CrossChainVRFWrapper` contract acts as the **bridge** between the unsupported L1 and Chainlink’s VRF services, ensuring that random words are requested, fulfilled, and delivered back across chains efficiently. diff --git a/content/course/interchain-messaging/13-access-chainlink-vrf-services/04-bring-vrf-to-unsupported-l1.mdx b/content/course/interchain-messaging/13-access-chainlink-vrf-services/04-bring-vrf-to-unsupported-l1.mdx new file mode 100644 index 0000000..0a0ee79 --- /dev/null +++ b/content/course/interchain-messaging/13-access-chainlink-vrf-services/04-bring-vrf-to-unsupported-l1.mdx @@ -0,0 +1,101 @@ +--- +title: Bringing Chainlink VRF to Unsupported L1s (Consumer) +description: Learn how to request VRF from an unsupported L1 using CrossChainVRFConsumer. +updated: 2024-10-21 +authors: [0xstt] +icon: BookOpen +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +The `CrossChainVRFConsumer` contract enables DApps on an unsupported L1 to request random words from Chainlink VRF using a cross-chain communication mechanism. Since Chainlink does not natively support all blockchains, this setup allows developers to access Chainlink's VRF service even on networks that don’t have direct support. + + + + +## Requesting Random Words + +The `CrossChainVRFConsumer` contract sends a cross-chain message to the `CrossChainVRFWrapper` on a Chainlink-supported L1, requesting random words. This request is sent using `TeleporterMessenger`, which handles cross-chain communication. + + + +```solidity +function requestRandomWords( + bytes32 keyHash, + uint16 requestConfirmations, + uint32 callbackGasLimit, + uint32 numWords, + bool nativePayment, + uint32 requiredGasLimit +) external { + // Create CrossChainRequest struct + CrossChainRequest memory crossChainRequest = CrossChainRequest({ + keyHash: keyHash, + requestConfirmations: requestConfirmations, + callbackGasLimit: callbackGasLimit, + numWords: numWords, + nativePayment: nativePayment + }); + // Send Teleporter message + bytes memory encodedMessage = abi.encode(crossChainRequest); + TeleporterMessageInput memory messageInput = TeleporterMessageInput({ + destinationBlockchainID: DATASOURCE_BLOCKCHAIN_ID, + destinationAddress: vrfRequesterContract, + feeInfo: TeleporterFeeInfo({ feeTokenAddress: address(0), amount: 0 }), + requiredGasLimit: requiredGasLimit, + allowedRelayerAddresses: new address[](0), + message: encodedMessage + }); + teleporterMessenger.sendCrossChainMessage(messageInput); +} +``` + + + + + + + +## Processing the Request +Once the request is received by the `CrossChainVRFWrapper`, it interacts with the Chainlink VRF Coordinator to request the random words on behalf of the consumer on the unsupported L1. + + + + + +## Receiving Random Words + +Once Chainlink fulfills the request, the `CrossChainVRFWrapper` sends the random words back to the `CrossChainVRFConsumer` via a cross-chain message, enabling the DApp on the unsupported L1 to use them. + + + +```solidity +function receiveTeleporterMessage( + bytes32 originChainID, + address originSenderAddress, + bytes calldata message +) external { + require(originChainID == DATASOURCE_BLOCKCHAIN_ID, "Invalid originChainID"); + require(msg.sender == address(teleporterMessenger), "Caller is not the TeleporterMessenger"); + require(originSenderAddress == vrfRequesterContract, "Invalid sender"); + + // Decode the message to get the request ID and random words + CrossChainResponse memory response = abi.decode(message, (CrossChainResponse)); + + // Fulfill the request by calling the internal function + fulfillRandomWords(response.requestId, response.randomWords); +} + +function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal { + // Logic to handle the fulfillment of random words + // Implement your custom logic here + + // Emit event for received random words + emit RandomWordsReceived(requestId); +} +``` + + + + + diff --git a/content/course/interchain-messaging/13-access-chainlink-vrf-services/05-deploy-vrf-wrapper.mdx b/content/course/interchain-messaging/13-access-chainlink-vrf-services/05-deploy-vrf-wrapper.mdx new file mode 100644 index 0000000..49f0f94 --- /dev/null +++ b/content/course/interchain-messaging/13-access-chainlink-vrf-services/05-deploy-vrf-wrapper.mdx @@ -0,0 +1,66 @@ +--- +title: Deploy Wrapper +description: Learn how to deploy the CrossChainVRFWrapper contract to handle cross-chain VRF requests. +updated: 2024-10-21 +authors: [0xstt] +icon: Terminal +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +Once you have set up your Chainlink VRF subscription and have your LINK tokens ready, the next step is to deploy the **CrossChainVRFWrapper** contract. This contract will act as the bridge between your unsupported L1 and the Chainlink VRF network on a supported L1, enabling cross-chain requests for random words. + + + + +## Prerequisites +Before deployment, make sure you have: +- A valid **Chainlink VRF Subscription ID** (see previous section for details). +- The `TeleporterMessenger` contract address on your supported L1 (e.g., Avalanche Fuji). + + + +## Deploy the Contract +Using the `forge create` command, deploy the `CrossChainVRFWrapper` contract to the supported L1 (e.g., Avalanche Fuji). + +```bash +forge create --rpc-url --private-key src/CrossChainVRFWrapper.sol:CrossChainVRFWrapper --constructor-args +``` + +Replace the following: + +- ``: The RPC URL for the L1. +- ``: The private key for the account used to deploy the contract. +- ``: The address of the deployed `TeleporterMessenger` contract. + +After deployment, save the `Deployed to` address in an environment variable for future use. + +```bash +export VRF_WRAPPER=
+``` + + + + +## Verify the Deployment +After deploying the contract, verify that the `CrossChainVRFWrapper` has been successfully deployed by checking its address on a block explorer. + + + + +## Configure Authorized Subscriptions +Once deployed, the `CrossChainVRFWrapper` contract needs to be configured with authorized subscriptions to process requests for random words. + +- Call the `addAuthorizedAddress` function to authorize a specific address with a given subscription ID. +- This ensures that only authorized addresses can request random words via the wrapper. + +```bash +cast send --rpc-url --private-key $VRF_WRAPPER "addAuthorizedAddress(address caller, uint256 subscriptionId)" +``` + +Replace the following: + +- ``: The address that will be authorized to request random words. +- ``: The ID of your Chainlink VRF subscription. + + \ No newline at end of file diff --git a/content/course/interchain-messaging/13-access-chainlink-vrf-services/06-deploy-vrf-consumer.mdx b/content/course/interchain-messaging/13-access-chainlink-vrf-services/06-deploy-vrf-consumer.mdx new file mode 100644 index 0000000..85e08f1 --- /dev/null +++ b/content/course/interchain-messaging/13-access-chainlink-vrf-services/06-deploy-vrf-consumer.mdx @@ -0,0 +1,44 @@ +--- +title: Deploy Consumer +description: Learn how to deploy the CrossChainVRFConsumer contract on any L1 that does not support Chainlink VRF. +updated: 2024-10-21 +authors: [0xstt] +icon: Terminal +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +Now that the `CrossChainVRFWrapper` is deployed on a Chainlink-supported L1, it’s time to deploy the `CrossChainVRFConsumer` contract on the L1 where Chainlink VRF is not supported. This contract will handle requests for random words and interact with the **TeleporterMessenger** to communicate with the supported L1. + + + +## Prerequisites +Make sure you have: +- The `TeleporterMessenger` contract address on the unsupported L1. +- The deployed `CrossChainVRFWrapper` on the supported L1. `($VRF_WRAPPER)` + + +## Deploy the Contract +Use the following command to deploy the `CrossChainVRFConsumer` contract on your unsupported L1. + +```bash +forge create --rpc-url --private-key src/CrossChainVRFConsumer.sol:CrossChainVRFConsumer --constructor-args $VRF_WRAPPER +``` + +Replace the following: + +- ``: The RPC URL for the L1. +- ``: The private key for the account used to deploy the contract. +- ``: The address of the `TeleporterMessenger` contract on your unsupported L1. + +After deployment, save the `Deployed to` address in an environment variable for future use. + +```bash +export VRF_CONSUMER=
+``` + + +## Verify the Deployment +Once the `CrossChainVRFConsumer` contract is deployed, verify the contract’s address and confirm that it has been successfully deployed on your L1 using a block explorer. + + \ No newline at end of file diff --git a/content/course/interchain-messaging/13-access-chainlink-vrf-services/07-create-vrf-subscription.mdx b/content/course/interchain-messaging/13-access-chainlink-vrf-services/07-create-vrf-subscription.mdx new file mode 100644 index 0000000..5c525e6 --- /dev/null +++ b/content/course/interchain-messaging/13-access-chainlink-vrf-services/07-create-vrf-subscription.mdx @@ -0,0 +1,63 @@ +--- +title: Create Chainlink VRF Subscription +description: Learn how to create a Chainlink VRF subscription to enable cross-chain randomness requests. +updated: 2024-10-21 +authors: [0xstt] +icon: BookOpen +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +Before you can request random words using Chainlink VRF, you need to set up a **Chainlink VRF subscription**. This subscription allows you to fund requests for randomness and manage the list of consumers that are authorized to use the VRF service. + + + + +## Access Chainlink's VRF Subscription Manager + +To create a subscription, go to the Chainlink VRF Subscription Manager on the network where you plan to request VRF (e.g., Avalanche Fuji). You can access the manager here: [VRF | Subscription Management for Fuji](https://vrf.chain.link/fuji). +![](/common-images/chainlink-vrf/visit-subscription-management.png) + + + + +## Create a New Subscription + +Once on the subscription manager, create a new subscription. The subscription will have a unique ID, which you'll need to reference in your `CrossChainVRFWrapper` contract. This ID is used to track your random word requests and the balance associated with them. +![](/common-images/chainlink-vrf/create-subscription.png) + + + + +## Fund the Subscription + +After creating the subscription, you need to fund it with LINK tokens. These tokens are used to pay for the randomness requests made through Chainlink VRF. Make sure your subscription has enough funds to cover your requests. +You can get testnet LINK tokens from the Fuji Faucet: [Chainlink Faucet for Fuji](https://faucets.chain.link/fuji) + +![](/common-images/chainlink-vrf/faucet.png) + +![](/common-images/chainlink-vrf/add-funds.png) + + + + +## Add Consumers + +After funding your subscription, add the `CrossChainVRFWrapper` contract `($VRF_WRAPPER)` as a consumer. This step authorizes the contract to make randomness requests on behalf of your subscription. You can add other consumers, such as other contracts or addresses, depending on your use case. +![](/common-images/chainlink-vrf/add-consumer.png) + + + + +## Save Subscription ID + +After completing these steps, save your subscription ID. You will need this ID when configuring the `CrossChainVRFWrapper` contract to request random words. + +```bash +export VRF_SUBSCRIPTION_ID= +``` + + + + +--- diff --git a/content/course/interchain-messaging/13-access-chainlink-vrf-services/08-request-random-words.mdx b/content/course/interchain-messaging/13-access-chainlink-vrf-services/08-request-random-words.mdx new file mode 100644 index 0000000..17db292 --- /dev/null +++ b/content/course/interchain-messaging/13-access-chainlink-vrf-services/08-request-random-words.mdx @@ -0,0 +1,47 @@ +--- +title: Request Random Words +description: Learn how to request random words using CrossChainVRFConsumer. +updated: 2024-10-21 +authors: [0xstt] +icon: Terminal +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +In this section, you will learn how to request random words from Chainlink VRF using both the `CrossChainVRFConsumer` contracts. + + + + +## Authorize an Address to Request Random Words + +After deploying the `CrossChainVRFWrapper` contract, the first step is to authorize an address to make requests for random words. This ensures that only specific addresses linked to a subscription can request VRF. + +- Use the `addAuthorizedAddress` function to authorize a specific address with a given subscription ID. This step allows the address to make random word requests through the wrapper. + +```bash +cast send --rpc-url --private-key $VRF_WRAPPER "addAuthorizedAddress(address caller, uint256 subscriptionId)" $VRF_CONSUMER $VRF_SUBSCRIPTION_ID +``` + + + + +## Request Random Words from `CrossChainVRFConsumer` + +Once the address is authorized, the next step is to send a request for random words from the `CrossChainVRFConsumer` contract on the unsupported L1. This request is then sent to the `CrossChainVRFWrapper` via a cross-chain message. + +```bash +cast send --rpc-url --private-key $VRF_CONSUMER "requestRandomWords(bytes32 keyHash, uint16 requestConfirmations, uint32 callbackGasLimit, uint32 numWords, bool nativePayment, uint32 requiredGasLimit)" +``` + +Replace the placeholders with: + +- ``: The VRF key hash used for random word generation. +- ``: Number of confirmations required for the request. +- ``: The gas limit for the VRF callback function. +- ``: The number of random words requested. +- ``: Indicates whether the payment will be made in the native token. +- ``: The gas limit required for the cross-chain message to be processed. + + + \ No newline at end of file diff --git a/content/course/interchain-messaging/meta.json b/content/course/interchain-messaging/meta.json index 66273d7..9d7ea48 100644 --- a/content/course/interchain-messaging/meta.json +++ b/content/course/interchain-messaging/meta.json @@ -25,6 +25,9 @@ "...11-restricting-the-relayer", "---Incentivizing a Relayer---", "...12-incentivizing-a-relayer", + "---Access Chainlink Services---", + "---Chainlink VRF---", + "...13-access-chainlink-vrf-services", "---Conclusion---", "certificate" ] diff --git a/public/common-images/chainlink-vrf/add-consumer.png b/public/common-images/chainlink-vrf/add-consumer.png new file mode 100644 index 0000000..483cdfe Binary files /dev/null and b/public/common-images/chainlink-vrf/add-consumer.png differ diff --git a/public/common-images/chainlink-vrf/add-funds.png b/public/common-images/chainlink-vrf/add-funds.png new file mode 100644 index 0000000..e87b209 Binary files /dev/null and b/public/common-images/chainlink-vrf/add-funds.png differ diff --git a/public/common-images/chainlink-vrf/create-subscription.png b/public/common-images/chainlink-vrf/create-subscription.png new file mode 100644 index 0000000..59c270b Binary files /dev/null and b/public/common-images/chainlink-vrf/create-subscription.png differ diff --git a/public/common-images/chainlink-vrf/faucet.png b/public/common-images/chainlink-vrf/faucet.png new file mode 100644 index 0000000..c443418 Binary files /dev/null and b/public/common-images/chainlink-vrf/faucet.png differ diff --git a/public/common-images/chainlink-vrf/visit-subscription-management.png b/public/common-images/chainlink-vrf/visit-subscription-management.png new file mode 100644 index 0000000..d02de1d Binary files /dev/null and b/public/common-images/chainlink-vrf/visit-subscription-management.png differ diff --git a/public/course-images/interchain-messaging/cross-chain-vrf.png b/public/course-images/interchain-messaging/cross-chain-vrf.png new file mode 100644 index 0000000..59a91d6 Binary files /dev/null and b/public/course-images/interchain-messaging/cross-chain-vrf.png differ