diff --git a/README.md b/README.md index 5025bcd..ec684a8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ A beginner's guide to implimenting chainlink products in your smart contracts. L - [🎲 VRF (Verifiable Random Function)](#-vrf-verifiable-random-function) - [🤖 Automation](#-automation) - ## Getting Started 1. Install dependencies @@ -33,44 +32,51 @@ yarn start ``` ## 📈 Price Feeds -Chainlink price feeds offer a decentralized data source that provides price information for a range of assets pairs depending on the network. The price feeds are powered by a decentralized network of independent, security-reviewed, and Sybil-resistant oracle nodes. The `AggregatorV3Interface` is fixed to the price feed address used during instantiation, but the `FeedRegistry` is more flexible although it is only avaible on mainnet. +Chainlink price feeds offer a decentralized data source that provides price information for a range of assets pairs depending on the network. The price feeds are powered by a decentralized network of independent, security-reviewed, and Sybil-resistant oracle nodes. The `AggregatorV3Interface` is fixed to the price feed address used during instantiation, but the `FeedRegistry` is more flexible although it is only avaible on mainnet. ### Steps + 1. Import `AggregatorV3Interface` into your smart contract 2. Declare a state variable of type `AggregatorV3Interface` -3. Choose network and a pair of assets to find the price feed address +3. Choose network and a pair of assets to find the price feed address 4. Instantiate the variable using the price feed address -5. Call `.latestRoundData()` and extract the answer - +5. Call `.latestRoundData()` and extract the answer ### Details + - The price returned by the price feed contract has a specified number of decimals that can be fetched from the price feed contract using the `decimals()` method -- The answer returned by the price feed contract is only updated if the price deviates beyond a specified threshold or if a certain amount of time has passed since the last update +- The answer returned by the price feed contract is only updated if the price deviates beyond a specified threshold or if a certain amount of time has passed since the last update - `FeedRegistry` is only available on Ethereum Mainnet, but `AggregatorV3Interface` is available on a variety of networks. ## 🎲 VRF (Verifiable Random Function) -Chainlink VRF allows a smart contract to access verifiably random numbers. Each request for a random number costs LINK and the reponse is delivered on chain after requestConfirmations number of blocks. The VRFConsumer example uses the Direct Funding method, but you may prefer the Subscription method depending on your use case. + +Chainlink VRF provides access verifiably random numbers on chain. Each request for a random number costs LINK and the reponse is delivered on chain after requestConfirmations number of blocks. The VRFConsumer example uses the Direct Funding method, but you may prefer the Subscription method depending on your use case. ### Steps + 1. Set up your contract to inherit VRFV2WrapperConsumerBase 2. Impliment a function that triggers request for random number by calling the requestRandomness function which is inhereted from VRFV2WrapperConsumerBase 3. You must override the fullFillrandomWords function ### Details + - The Direct Funding method requires your smart contract hold LINK tokens for payment - The fulfillRandomWords function is triggered by the VRF Coordinator contract - VRF response time is impacted by requestConfirmations which must be greater than the minimum amount set by the coordinator contract ## 🤖 Automation + Chainlink Automation calls a smart contract function if a specified set of criteria are met. The time-based trigger calls a target function on a target contract every specified interval. The custom logic trigger allows your contract to use on-chain state to determine when to call a target function. The log trigger allows your contract to use event log data as both a trigger and an input. ### Steps + 1. Decide which trigger fits best for your use case -2. Register a new upkeep with chainlink +2. Register a new upkeep with chainlink 3. Provide your target contract and target function ### Details + - The time-based trigger does not require an interface - The custom logic trigger requires your target contract be compatible with AutomationCompatibleInterface - The log trigger requires your target contract be compatible with IlogAutomation diff --git a/packages/hardhat/contracts/AggregatorV3Consumer.sol b/packages/hardhat/contracts/AggregatorV3Consumer.sol index d7376a0..f0f90e7 100644 --- a/packages/hardhat/contracts/AggregatorV3Consumer.sol +++ b/packages/hardhat/contracts/AggregatorV3Consumer.sol @@ -1,13 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; -// Import the AggregatorV3Interface from the "@chainlink/contracts" package import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -/** - * @dev the price feed address passed to constructor during deployment determines the asset pair - * @notice https://docs.chain.link/data-feeds/price-feeds/addresses?network=ethereum&page=1 +/** Simple contract for integrating a Chainlink price feed + * + * the price feed address passed to constructor during deployment determines the asset pair + * + * choose a price feed address: + * https://docs.chain.link/data-feeds/price-feeds/addresses?network=ethereum&page=1 */ + contract AggregatorV3Consumer { // Declare state variable of type AggregatorV3Interface AggregatorV3Interface internal immutable i_priceFeed; diff --git a/packages/hardhat/contracts/VRFConsumer.sol b/packages/hardhat/contracts/VRFConsumer.sol index dd7c33e..81621c7 100644 --- a/packages/hardhat/contracts/VRFConsumer.sol +++ b/packages/hardhat/contracts/VRFConsumer.sol @@ -9,7 +9,7 @@ import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol"; */ contract VRFConsumer is VRFV2WrapperConsumerBase, ConfirmedOwner { - // State variables + // State Variables address public linkAddress; uint32 callbackGasLimit = 100000; // limit for gas can be used when chainlink node calls fulfillRandomWords() uint16 requestConfirmations = 3; // blocks before chainlink node responds (must be greater than a minimum amout set by VRF coordinator contract) @@ -37,28 +37,33 @@ contract VRFConsumer is VRFV2WrapperConsumerBase, ConfirmedOwner { } /** This function triggers the request to chainlink node that generates the random number - * @dev "requestRandomness()" is inherited from VRFV2WrapperConsumerBase + * + * "requestRandomness()" is inherited from VRFV2WrapperConsumerBase + * * @return requestId each request has a unique ID */ + function spinWheel() public returns (uint256 requestId) { + // this request will revert if the contract does not have enough LINK to pay the fee requestId = requestRandomness( callbackGasLimit, requestConfirmations, numValues ); - // keep track of who sent the request s_spinners[requestId] = msg.sender; emit WheelSpun(requestId, msg.sender); } /** Chainlink oracle calls this function to deliver the random number + * * @param requestId The ID of the request * @param randomWords Array containing the random number(s) * * @dev use the random number to change state of your contract here - * @dev the random number is huge so use modulo to constrain the range + * @dev modulo is used to constrain the range of the random number */ + function fulfillRandomWords( uint256 requestId, uint256[] memory randomWords diff --git a/packages/nextjs/components/automation/Showcase.tsx b/packages/nextjs/components/automation/Showcase.tsx index faebe16..2041472 100644 --- a/packages/nextjs/components/automation/Showcase.tsx +++ b/packages/nextjs/components/automation/Showcase.tsx @@ -50,7 +50,6 @@ export const Showcase = () => {

AutomationConsumer

- {/*
{linkBalance?.toString()} LINK
*/}
diff --git a/packages/nextjs/components/common/InlineCode.tsx b/packages/nextjs/components/common/InlineCode.tsx index f008a20..1ca79d4 100644 --- a/packages/nextjs/components/common/InlineCode.tsx +++ b/packages/nextjs/components/common/InlineCode.tsx @@ -5,7 +5,7 @@ type Props = { export const InlineCode: React.FC = ({ text, href }) => { const content = ( - {text} + {text} ); return href ? ( diff --git a/packages/nextjs/components/price-feeds/AggregatorV3Consumer.tsx b/packages/nextjs/components/price-feeds/AggregatorV3Consumer.tsx index a328754..ac30a4c 100644 --- a/packages/nextjs/components/price-feeds/AggregatorV3Consumer.tsx +++ b/packages/nextjs/components/price-feeds/AggregatorV3Consumer.tsx @@ -43,7 +43,7 @@ export const AggregatorV3Consumer = () => {
-

The latest price quote returned by ETH/USD price feed contract on sepolia

+

The latest round data returned by ETH/USD price feed contract on sepolia

{!latestPrice || !decimals || !description ? (

Loading...

diff --git a/packages/nextjs/components/vrf/ResultsTable.tsx b/packages/nextjs/components/vrf/ResultsTable.tsx index beb8bf3..0e6dfa1 100644 --- a/packages/nextjs/components/vrf/ResultsTable.tsx +++ b/packages/nextjs/components/vrf/ResultsTable.tsx @@ -1,16 +1,12 @@ import { Address } from "../scaffold-eth"; +import { ResultType } from "./Showcase"; interface WheelOption { option: string; } -interface Result { - spinner: string; - randomValue: number; -} - interface ResultsTableProps { - results: Result[]; + results: ResultType[]; wheelOptions: WheelOption[]; } diff --git a/packages/nextjs/components/vrf/Showcase.tsx b/packages/nextjs/components/vrf/Showcase.tsx index 28d8a08..626705a 100644 --- a/packages/nextjs/components/vrf/Showcase.tsx +++ b/packages/nextjs/components/vrf/Showcase.tsx @@ -4,6 +4,7 @@ import dynamic from "next/dynamic"; import { ResultsTable } from "./ResultsTable"; import { LoaderIcon } from "react-hot-toast"; import { formatEther } from "viem"; +import { InformationCircleIcon } from "@heroicons/react/24/outline"; import { Spinner } from "~~/components/assets/Spinner"; import { ExternalLinkButton } from "~~/components/common"; import { InlineCode } from "~~/components/common"; @@ -19,38 +20,25 @@ import { } from "~~/hooks/scaffold-eth/"; import { notification } from "~~/utils/scaffold-eth"; -// dynamic import to satisfy nextjs ssr +// dynamic import of roulette wheel package to satisfy nextjs ssr const Wheel = dynamic(() => import("react-custom-roulette").then(mod => mod.Wheel), { ssr: false, loading: () =>

Loading...

, }); -type Result = { - spinner: string; - randomValue: number; +export type ResultType = { + spinner: string | undefined; + randomValue: bigint | undefined; }; -/** - * - */ + export const Showcase = () => { const [mustSpin, setMustSpin] = useState(false); const [prizeNumber, setPrizeNumber] = useState(0); - // const [isTxPending, setIsTxPending] = useState(false); - const [results, setResults] = useState([]); + const [results, setResults] = useState([]); const [pointerVisibility, setPointerVisibility] = useState<"visible" | "hidden">("visible"); const [waitingForVRF, setWaitingForVRF] = useState(false); const [notificationId, setNotificationId] = useState(null); - const handleSpinClick = async () => { - try { - setPointerVisibility("hidden"); - // setWaitingForVRF(true); - await spinWheel(); - } catch (e) { - // setWaitingForVRF(false); - } - }; - const { data: vrfConsumerContract } = useScaffoldContract({ contractName: "VRFConsumer" }); const { data: linkBalance } = useScaffoldContractRead({ @@ -70,37 +58,36 @@ export const Showcase = () => { }, }); - const { data: resultsData, isLoading: resultsLoading } = useScaffoldEventHistory({ + const { data: resultsData, isLoading: isResultsLoading } = useScaffoldEventHistory({ contractName: "VRFConsumer", eventName: "WheelResult", fromBlock: 4491891n, }); - // 4555283 + useScaffoldEventSubscriber({ contractName: "VRFConsumer", eventName: "WheelResult", listener: logs => { logs.map(log => { - setWaitingForVRF(false); const { spinner, randomValue } = log.args; - const randomNum = Number(randomValue); + setWaitingForVRF(false); setPointerVisibility("visible"); if (!mustSpin) { - setPrizeNumber(randomNum); + setPrizeNumber(Number(randomValue)); setMustSpin(true); } notification.success( , { duration: 20000, }, ); - // add result to spin events table if (spinner && randomValue) { - setResults(prev => [{ spinner, randomValue: randomNum }, ...prev]); + setResults(prev => [{ spinner, randomValue }, ...prev]); + console.log("here"); } }); }, @@ -117,14 +104,20 @@ export const Showcase = () => { }, [waitingForVRF, notificationId]); useEffect(() => { - if (!results.length && !!resultsData?.length && !resultsLoading) { + if (!results.length && !!resultsData?.length && !isResultsLoading) { setResults( - resultsData?.map(({ args }) => { - return { spinner: args.spinner!, randomValue: Number(args.randomValue!) }; - }) || [], + resultsData.map(({ args }) => ({ + spinner: args.spinner, + randomValue: args.randomValue, + })) || [], ); } - }, [resultsLoading, resultsData, results.length]); + }, [isResultsLoading, resultsData, results.length]); + + const handleSpinClick = () => { + setPointerVisibility("hidden"); + spinWheel(); + }; return (
@@ -139,11 +132,11 @@ export const Showcase = () => {

- Spin the wheel to trigger request to chainlink VRF for a random number. Each request costs{" "} - that is paid using the{" "} - . After the - request transaction is mined, the VRF Coordinator waits a minimum of{" "} - blocks before responding with a random value. + Spin the wheel to trigger a request for a random number. Each request costs that is + paid using the {" "} + method. After the request transaction is mined, the VRF Coordinator waits a minimum of{" "} + blocks before calling the {" "} + function on the VRFConsumer contract.

@@ -151,7 +144,7 @@ export const Showcase = () => {
Spin Events
- {!resultsData || resultsLoading ? ( + {!resultsData || isResultsLoading ? (
@@ -159,22 +152,14 @@ export const Showcase = () => { )}
-
- - - - {linkBalance ? parseFloat(formatEther(linkBalance)).toFixed(2) : "0.0"} LINK available in contract to pay - for VRF requests +
+ +
+ + {linkBalance ? parseFloat(formatEther(linkBalance)).toFixed(2) : "0.0"} LINK + + available in VRFConsumer to pay for requests +
@@ -185,7 +170,9 @@ export const Showcase = () => { prizeNumber={prizeNumber} data={wheelOptions} spinDuration={1} - onStopSpinning={() => setMustSpin(false)} + onStopSpinning={() => { + console.log("stopping spinner"), setMustSpin(false); + }} pointerProps={{ style: { visibility: pointerVisibility } }} />
diff --git a/packages/nextjs/pages/vrf.tsx b/packages/nextjs/pages/vrf.tsx index cba3092..1e903bf 100644 --- a/packages/nextjs/pages/vrf.tsx +++ b/packages/nextjs/pages/vrf.tsx @@ -10,15 +10,15 @@ const VRFPage: NextPage = () => {

🎲 VRF

-
+
- Chainlink VRF allows a smart contract to access verifiably random numbers. Each request for a random - number costs and the reponse is delivered on chain after{" "} + Chainlink VRF provides access to verifiably random numbers on chain. Each request for a random number + costs and the reponse is delivered after{" "} number of blocks. The example uses the {" "} method, but you may prefer the{" "} @@ -29,7 +29,7 @@ const VRFPage: NextPage = () => { details={[ <> The method - requires your smart contract hold tokens for payment{" "} + requires your smart contract hold tokens for payment , <> The function is triggered by the VRF Coordinator contract @@ -50,14 +50,19 @@ const VRFPage: NextPage = () => { <> Impliment a function that triggers request for random number by calling the{" "} - function which is inhereted from{" "} + function inhereted from{" "} , <> - You must the function + You must the function inhereted + from{" "} + , ]} />