diff --git a/src/pages/lessons/projects/2.mdx b/src/pages/lessons/projects/2.mdx index db77b414..fe6f3420 100644 --- a/src/pages/lessons/projects/2.mdx +++ b/src/pages/lessons/projects/2.mdx @@ -319,7 +319,7 @@ and copy this code inside: ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; contract ProjectNFT { } @@ -358,7 +358,7 @@ This is what we need in our code: ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; @@ -395,7 +395,7 @@ which we can use as our ID for each new token created: ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; @@ -437,7 +437,7 @@ NFT: ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; @@ -500,7 +500,7 @@ Here's the modifications we will add and I'll explain them below: ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; @@ -793,7 +793,7 @@ deployed contract to the console. Before we run our script, we need to tell Hardhat what Solidity version our contracts are using. For that, we need to go into the `hardhat.config.js` file in our project's root folder. Find the line that says `solidity: '0.8.xx',` and -replace the `0.8.xx` for the pragma used in our contract: `0.8.12`. +replace the `0.8.xx` for the pragma used in our contract: `0.8.20`. We could have used a range of Solidity versions e.g. `^0.8.0` in our contract, but we like to promote best practices by choosing a fixed Solidity version and @@ -872,17 +872,16 @@ going to run the deploy again, but this time to a testnet. What is a testnet? It is a basically a whole running blockchain, but it runs only so people can try stuff out. On it, you have ETH, NFTs, or other tokens but they have no monetary value. This way, you can develop and test your contracts -without fear of losing anything valuable. Ethereum has many testnets, and you -might notice that we used _Goerli_ testnet. But we suggest that you use +without fear of losing anything valuable. Ethereum has many testnets, and we suggest that you use _Sepolia_ testnet, now that it has compatible NFT hosting support, and longevity -for app development that _Goerli_ won't outlast. +for app development. Before we go any further, let's take an extra step for precaution. In the next project we'll learn how to use collaborative tools to store our projects, but for now, let's open our root directory's `.gitignore` file and add this line: ```bash -hardhat.config.js +.env ``` In order to deploy to a real testnet we'll need: @@ -895,7 +894,7 @@ In order to deploy to a real testnet we'll need: [#3](https://faucet.quicknode.com/ethereum/sepolia) - An API Key from an Ethereum RPC Node Provider ([Alchemy](https://www.alchemy.com/), [Infura](https://infura.io/), - [Ankr](https://rpc.ankr.com/eth_goerli)) + [Ankr](https://rpc.ankr.com/eth_sepolia)) - A minor change in the Hardhat configuration file First and foremost, **security**. We are exploring new grounds, experimenting, @@ -936,27 +935,36 @@ We are going to replace our file with this: ```jsx require("@nomicfoundation/hardhat-toolbox"); - -const WALLET_PRIVATE_KEY = "YOUR-PRIVATE-KEY-DONT-SHARE"; - -const RPC_API_KEY = "YOUR-API-KEY-FROM-INFURA-OR-ALCHEMY"; +require("dotenv").config(); /** * @type import('hardhat/config').HardhatUserConfig */ module.exports = { - solidity: "0.8.12", + solidity: "0.8.20", networks: { - goerli: { - url: RPC_API_KEY, - accounts: [WALLET_PRIVATE_KEY], + sepolia: { + url: process.env.RPC_URL, + accounts: [process.env.PRIVATE_KEY], }, }, }; ``` -And you'll need to set the two global variables using the `const` keyword with -your own values for Hardhat to communicate correctly to the network. +We strongly recommend using a `.env` file to store your sensitive information. This is a common practice among professional developers to keep secrets out of their source code. + +First, install the `dotenv` package: + +```bash +npm install dotenv +``` + +Then, create a `.env` file in your project root and add your keys: + +```bash +RPC_URL=YOUR-API-KEY-FROM-INFURA-OR-ALCHEMY +PRIVATE_KEY=YOUR-PRIVATE-KEY-DONT-SHARE +``` We have already gone through how to get your API KEY. @@ -965,11 +973,11 @@ to open Metamask, click on the three dots next to your _Account Name_, and then on _Account Details_, then click on _Export Private Key_. It will ask for your Metamask password - the one you use to open it, NOT your seed phrase. It also issues a warning to 'Never disclose this key'. Confirm and you'll be able to -copy your private key. Paste it in to our `hardhat.config.js` +copy your private key. Paste it into your `.env` file. Please, if you are already a developer, and you plan to use Git to store your -project, don't store your `hardhat.config.js` on it, because you will have your -private key there. +project, don't store your `.env` file on it, because you will have your +private key there. That's why we added it to our `.gitignore` file earlier. = TIER_VALUE_0, "Not enough value for the minimum Tier" @@ -365,7 +365,7 @@ so, you'll definitely know `if else`! tokenTier[totalSupply] = tierId; } - // We will add more code here + // We will add more code here } ``` @@ -390,7 +390,7 @@ through the code and find out. **View full code here** ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; @@ -469,7 +469,7 @@ it into the contract! Nifty, eh? override returns (string memory) { - require(_exists(tokenId), "Nonexistent token"); + _requireOwned(tokenId); string memory imageSVG = "PLACEHOLDER FOR SVG IMAGE"; @@ -489,7 +489,7 @@ it into the contract! Nifty, eh? return string(abi.encodePacked("data:application/json;base64,", json)); } - // We will add more code here + // We will add more code here } ``` @@ -498,7 +498,7 @@ Let’s stop to break it down and examine it a little. - Within the `tokenURI` function, you'll notice `override`, an `ERC721` function we'll use, since we are not creating a separate JSON file to store images or other services, but creating it right here in the contract. -- We also added `require(_exists(tokenId). "Nonexistent token");`. According to +- We also added `_requireOwned(tokenId);`. According to the ERC-721 specification, it is required to throw an error if the NFT doesn't exist. - `imageSVG` is a placeholder for our image, and we will deal with it a bit @@ -523,9 +523,9 @@ TokenID were `3`, our JSON would end up look something like this: ```solidity { - "name": "TierNFT #3", - "description": "TierNFTs collection",' - "image": "data:image/svg+xml;base64,A_BUNCH_OF_BASE64_LETTERS_AND_NUMBERS_HERE" + "name": "TierNFT #3", + "description": "TierNFTs collection",' + "image": "data:image/svg+xml;base64,A_BUNCH_OF_BASE64_LETTERS_AND_NUMBERS_HERE" } ``` @@ -534,7 +534,7 @@ TokenID were `3`, our JSON would end up look something like this: **here’s the updated code** ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/Base64.sol"; @@ -574,7 +574,7 @@ uint256) public tokenTier; override returns (string memory) { - require(_exists(tokenId), "Nonexistent token"); + _requireOwned(tokenId); string memory imageSVG = "PLACEHOLDER FOR SVG IMAGE"; @@ -666,7 +666,7 @@ Standard. **View updated code** ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/Base64.sol"; @@ -712,7 +712,7 @@ contract TierNFT is ERC721 { override returns (string memory) { - require(_exists(tokenId), "Nonexistent token"); + _requireOwned(tokenId); string memory tierName = tokenTier[tokenId] == 2 ? TIER_NAME_2 @@ -763,7 +763,7 @@ And inherit `Ownable` from the OpenZeppelin contract into our own. contract TierNFT is ERC721, Ownable { - // Our whole contract code here + // Our whole contract code here } ``` @@ -774,7 +774,7 @@ all of it! Let’s get this withdraw function coded in here! ```solidity // tokenURI function part of the code... - // Function to withdraw funds from contract + // Function to withdraw funds from contract function withdraw() public onlyOwner { // Check that we have funds to withdraw uint256 balance = address(this).balance; @@ -785,7 +785,7 @@ all of it! Let’s get this withdraw function coded in here! require(success, "Withdraw failed"); } - // 'withdraw' will be our last function at the end of the contract + // 'withdraw' will be our last function at the end of the contract } ``` @@ -815,7 +815,7 @@ all of it! Let’s get this withdraw function coded in here! **View the Full Contract Here** ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; @@ -835,7 +835,7 @@ contract TierNFT is ERC721, Ownable { uint256 public totalSupply; mapping(uint256 => uint256) public tokenTier; constructor(string memory _name, string memory _symbol) - ERC721(_name, _symbol) {} + ERC721(_name, _symbol) Ownable(msg.sender) {} function mint() public payable { require( @@ -860,7 +860,7 @@ mapping(uint256 => uint256) public tokenTier; override returns (string memory) { - require(_exists(tokenId), "Nonexistent token"); + _requireOwned(tokenId); string memory tierName = tokenTier[tokenId] == 2 ? TIER_NAME_2 @@ -987,10 +987,10 @@ require("dotenv").config(); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { - solidity: "0.8.12", + solidity: "0.8.20", networks: { - mumbai: { - url: "https://rpc-mumbai.maticvigil.com", + amoy: { + url: "https://rpc-amoy.polygon.technology", accounts: [process.env.PRIVATE_KEY], }, }, @@ -1078,7 +1078,7 @@ to keep your wallet safe and **unwanted guests out**. We will deploy our smart contract by using this command: ```bash -npx hardhat run scripts/deploy.js --network mumbai +npx hardhat run scripts/deploy.js --network amoy ``` We specify the network where we want the contract to be deployed using the @@ -1179,7 +1179,7 @@ main().catch((error) => { To mint our tier NFTs we will run the following command. ```bash -npx hardhat run scripts/mint.js --network mumbai +npx hardhat run scripts/mint.js --network amoy ``` If we look at our terminal we will see something like this. diff --git a/src/pages/lessons/projects/4.mdx b/src/pages/lessons/projects/4.mdx index 55a68196..9e79479b 100644 --- a/src/pages/lessons/projects/4.mdx +++ b/src/pages/lessons/projects/4.mdx @@ -126,7 +126,7 @@ that same contract. Please create a new file called `TierNFT.sol` in a ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; @@ -148,7 +148,7 @@ contract TierNFT is ERC721, Ownable { mapping(uint256 => uint256) public tokenTier; constructor(string memory _name, string memory _symbol) - ERC721(_name, _symbol) + ERC721(_name, _symbol) Ownable(msg.sender) {} function mint() public payable { @@ -177,7 +177,7 @@ contract TierNFT is ERC721, Ownable { override returns (string memory) { - require(_exists(tokenId), "Nonexistent token"); + _requireOwned(tokenId); string memory tierName = tokenTier[tokenId] == 2 ? TIER_NAME_2 @@ -233,14 +233,14 @@ Copy the Solidity version from the `pragma` statement at the top of your contract, to the `hardhat.config.js` file and replace that `solidity: "version"` with that of your contract. -For example, since our contract is using Solidity `0.8.12`: +For example, since our contract is using Solidity `0.8.20`: ```javascript require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config(); module.exports = { - solidity: "0.8.12", + solidity: "0.8.20", }; ``` @@ -592,9 +592,10 @@ Let's look at testing `withdraw()` next with these 3 tests: ```javascript describe("withdrawal", async () => { it("should error if not owner", async function () { - await expect(contract.connect(otherUser).withdraw()).to.be.revertedWith( - "Ownable: caller is not the owner", - ); + await expect(contract.connect(otherUser).withdraw()).to.be.revertedWithCustomError( + contract, + "OwnableUnauthorizedAccount" + ).withArgs(otherUser.address); }); it("should error if balance is zero", async function () { @@ -632,7 +633,7 @@ of additional highlights: withdraw from a zero-balance contact - Finally let's make sure an owner can successfully withdraw funds. Notice the `withdraw()` method call is surrounded by - `hre.ethers.provider.getBalance(contract.address)` lines. When we mint we know + `hre.ethers.provider.getBalance(contract.target)` lines. When we mint we know the contract now has `0.01` Eth so we check that. Then after running `withdraw`, check the balance again and it should be back to zero since all funds are withdrawn. Ok, yes we do have two `expect()` lines in one test that @@ -714,7 +715,7 @@ be tested and understood by humans writing these tests. ```solidity // SPDX-License-Identifier: MIT -pragma solidity 0.8.12; +pragma solidity 0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; @@ -736,7 +737,7 @@ contract TierNFT is ERC721, Ownable { mapping(uint256 => uint256) public tokenTier; constructor(string memory _name, string memory _symbol) - ERC721(_name, _symbol) + ERC721(_name, _symbol) Ownable(msg.sender) {} function mint() public payable { @@ -808,7 +809,7 @@ contract TierNFT is ERC721, Ownable { override returns (string memory) { - require(_exists(tokenId), "Nonexistent token"); + _requireOwned(tokenId); // string memory tierName = tierNameOf(tokenTier[tokenId]); string memory imageSVG = imageSVGOf(tokenTier[tokenId]); @@ -851,7 +852,7 @@ Ok, here are some tests for these helpers! ```javascript describe('tokenURI and helpers', async () => { it('should error if token does not exist', async function () { - await expect(contract.tokenURI(0)).to.be.revertedWith('Nonexistent token') + await expect(contract.tokenURI(0)).to.be.revertedWithCustomError(contract, 'ERC721NonexistentToken').withArgs(0) }) }) diff --git a/src/pages/lessons/projects/5.mdx b/src/pages/lessons/projects/5.mdx index ac9b09d5..68d5cf9d 100644 --- a/src/pages/lessons/projects/5.mdx +++ b/src/pages/lessons/projects/5.mdx @@ -146,11 +146,11 @@ But first we need to install the necessary dependencies. changes from future and to give better learner experience */} ```bash -npm install @rainbow-me/rainbowkit wagmi ethers +npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query # or if you have chosen to use yarn: -yarn add @rainbow-me/rainbowkit wagmi ethers +yarn add @rainbow-me/rainbowkit wagmi viem @tanstack/react-query ``` To get this setup we need to start working on the `_app.js` file in the `/frontend/pages` @@ -172,99 +172,65 @@ We need to add some additional imports to create our configurations. // previous imports... import '@rainbow-me/rainbowkit/styles.css'; import { - getDefaultWallets, + getDefaultConfig, RainbowKitProvider, } from '@rainbow-me/rainbowkit'; +import { WagmiProvider } from 'wagmi'; import { - chain, - configureChains, - createClient, - WagmiConfig, -} from 'wagmi'; -import { alchemyProvider } from 'wagmi/providers/alchemy'; -import { publicProvider } from 'wagmi/providers/public'; + polygonAmoy, +} from 'wagmi/chains'; +import { + QueryClientProvider, + QueryClient, +} from "@tanstack/react-query"; import { useEffect, useState } from 'react'; +const config = getDefaultConfig({ + appName: 'My RainbowKit App', + projectId: 'YOUR_PROJECT_ID', + chains: [polygonAmoy], + ssr: true, // If your dApp uses server side rendering (SSR) +}); + +const queryClient = new QueryClient(); + // function MyApp code... ``` -- `import '@rainbow-me/rainbowkit/styles.css';` gives us some basic styling from -Rainbowkit. -- `import { getDefaultWallets, RainbowKitProvider, } from '@rainbow-me/rainbowkit';` returns -the default wallet provider option and a RainbowKit Provider to "wrap" around our app -so that we can use its features throughout the app's pages and components. -- `import { chain, configureChains, createClient, WagmiConfig, } from 'wagmi';` provides -a way to configure the WagmiConfig wrapper for our app based on the chains and providers -of our choice along with some other customisations. -- `import { alchemyProvider } from 'wagmi/providers/alchemy';` and -`import { publicProvider } from 'wagmi/providers/public';` give us the provider configs -for the providers we will be using. In our app we are using an `alchemyProvider` along -with a fall back `publicProvider`. A provider allows our application to communicate with -a blockchain. -{/* It would be better to have a side drawer on 'state' and 'useEffect' here. We already have a few external links, and we have hardly built anything so far in this lesson. That's a lot of potential rabbitholes. */} -- `useEffect` and `useState` are react hooks that help us perform side effects and -capture the state, respectively. More on [state](https://www.freecodecamp.org/news/what-is-state-in-react-explained-with-examples/) -and the [useEffect](https://www.freecodecamp.org/news/react-useeffect-absolute-beginners/) hook. +- `import '@rainbow-me/rainbowkit/styles.css';` gives us some basic styling from RainbowKit. +- `import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit';` provides the default configuration options and a RainbowKit Provider to "wrap" around our app. +- `import { WagmiProvider } from 'wagmi';` provides the main context for WAGMI hooks. +- `import { QueryClient, QueryClientProvider } from "@tanstack/react-query";` is now required by WAGMI v2 for efficient data fetching and caching. +- `import { polygonAmoy } from 'wagmi/chains';` imports the network configuration for Polygon Amoy. - - Hooks are JavaScript functions that manage the state's behaviour and side effects by isolating them from a component. - - -After importing, we create the configurations we need, and create a new instance of -`wagmiClient` using them. +After importing, we create the configurations we need using `getDefaultConfig`. ```jsx -// import statements code... - -const { chains, provider } = configureChains( - [chain.polygonMumbai], - [ - alchemyProvider({ apiKey: process.env.API_KEY }), - publicProvider() - ] -); - -const { connectors } = getDefaultWallets({ +const config = getDefaultConfig({ appName: 'My RainbowKit App', - chains + projectId: 'YOUR_PROJECT_ID', // Get one at https://cloud.walletconnect.com + chains: [polygonAmoy], + ssr: true, }); -const wagmiClient = createClient({ - autoConnect: true, - connectors, - provider -}) - -// function MyApp code... +const queryClient = new QueryClient(); ``` -For this application we will continue using the `mumbai` testnet. Other chains can -be added simply using the following syntax: `chain.chain_name`. -For custom providers, like the `alchemyProvider` we can pass in our private -`apiKey` as well. - -{/* Only the API key? Or the whole URL?: */} -Set your API Key in a new file named `.env` inside our `frontend` directory -as follows: +For this application we will be using the `Amoy` testnet. Other chains can be added to the `chains` array. +For the `projectId`, you'll need to sign up at [WalletConnect Cloud](https://cloud.walletconnect.com) to get a free project ID. -```dotenv -API_KEY='YOUR-API-KEY-FROM-PROVIDER' -``` - -Now we can wrap our application in the `RainbowKitProvider` and `WagmiConfig` -so that it can have access to their features throughout our application. Our code should -look like this: +Now we can wrap our application in the `WagmiProvider`, `QueryClientProvider`, and `RainbowKitProvider`. Our code should look like this: ```jsx -// import statements and configs code... - function MyApp({ Component, pageProps }) { return ( - - - - - + + + + + + + ); }; @@ -413,10 +379,10 @@ Moving onto imports. // prev imports... import { ConnectButton } from '@rainbow-me/rainbowkit'; -import { useAccount, useContractRead, useContractWrite } from 'wagmi'; +import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'; import TierABI from '../../artifacts/contracts/TierNFT.sol/TierNFT.json'; import styles from '../styles/Home.module.css'; -import { ethers } from 'ethers'; +import { parseEther } from 'viem'; import { useEffect, useState } from 'react'; // function Home() code... @@ -424,13 +390,13 @@ import { useEffect, useState } from 'react'; - RainbowKit provides us a ready-made `ConnectButton`. - We need to use the `useAccount` hook from WAGMI to check account connection -and `useContractRead` and `useContractWrite` to interact with our smart +and `useReadContract` and `useWriteContract` to interact with our smart contract. We will look into WAGMI and these hooks a little down the line. - `TierABI` is our contract's Application Binary Interface (ABI). The ABI helps us interact with the smart contract outside the blockchain or contract-to-contract. You can read more about ABIs [here](https://www.quicknode.com/guides/solidity/what-is-an-abi). -- The `ethers` library will assist us with some utilities in our code. +- The `viem` library will assist us with some utilities like `parseEther`. - Once again, `useEffect` and `useState` are react hooks that will help us perform side effects and capture the state, respectively. @@ -525,51 +491,45 @@ export default function Home() { // variable definitions... const { - data: mintData, - writeAsync: mint, - isLoading: isMintLoading, - } = useContractWrite({ - addressOrName: CONTRACT_ADDRESS, - contractInterface: TierABI.abi, - functionName: "mint", - }); + data: mintHash, + writeContractAsync: mint, + isPending: isMintLoading, + } = useWriteContract(); // return statement for rendering... } ``` -The [`useContractWrite`](https://wagmi.sh/docs/hooks/useContractWrite) hook -from WAGMI let's us pass in the contract address, contract ABI and the -function name to return a JavaScript function (that we have called `mint`) +The [`useWriteContract`](https://wagmi.sh/react/api/hooks/useWriteContract) hook +from WAGMI returns a function (that we have called `mint`) that can interact with our smart contract and trigger a `write` function in it. How cool is that?! -It also returns some additional features for error handling and checking +It also returns some additional features for checking whether the function is running or completed. We are assigning well defined names to the returned values for easily calling them -later. For example, the `data` returned is called `mintData`. +later. For example, the `data` returned is the transaction hash, which we call `mintHash`. Even though we have this function, we need to pass in custom `message values` -i.e. an amount in **ETH**, as defined in our contract. For this we create our -own `mintToken` function that calls the `mint` function from WAGMI within it. +i.e. an amount in **ETH**, as defined in our contract, along with the contract address and ABI. +For this we create our own `mintToken` function that calls the `mint` function from WAGMI within it. {/* should be: // 'return statement for rendering...' and not: // function Home() code... */} ```jsx -// WAGMI useContractWrite hook... +// WAGMI useWriteContract hook... const mintToken = async (e) => { try { - let mintTxn = await mint({ - recklesslySetUnpreparedOverrides: { - value: ethers.utils.parseEther(e.target.value), - } + await mint({ + address: CONTRACT_ADDRESS, + abi: TierABI.abi, + functionName: 'mint', + value: parseEther(e.target.value), }); - await mintTxn.wait(); - console.log("This is the mint data", mintData); } catch (error) { console.log("Error minting NFT", error.message); } @@ -579,13 +539,8 @@ const mintToken = async (e) => { ``` - We pass in `e` as an argument so that we can pick the amount of ETH -associated with a particular Tier. This is passed to the mint function using the -`recklesslySetUnpreparedOverrides` override config. Read more about it -[here](https://wagmi.sh/docs/hooks/useContractWrite#override-config). -- We want to wait for our minting transaction to complete before moving -on to other pieces of code so we use `wait()`. -- After minting is processed we log the returned data to ensure the -success of our transaction. +associated with a particular Tier. This is passed to the mint function along with the `address`, `abi`, and `functionName`. +- After minting is triggered, we can use the returned hash to track the transaction. - Using `try...catch` statements makes our error handling more robust. The compiler runs each line of code and exits in between if an error occurs. We can console log this error to easily identify the cause and location @@ -759,10 +714,10 @@ side drawer for the code written till now if stuck somewhere. import Head from 'next/head'; import Image from 'next/image'; import { ConnectButton } from '@rainbow-me/rainbowkit'; -import { useAccount, useContractRead, useContractWrite } from 'wagmi'; +import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'; import TierABI from '../../artifacts/contracts/TierNFT.sol/TierNFT.json'; import styles from '../styles/Home.module.css'; -import { ethers } from 'ethers'; +import { parseEther } from 'viem'; import { useEffect, useState } from 'react'; export default function Home() { @@ -776,14 +731,10 @@ export default function Home() { const [isMinting, setIsMinting] = useState(false); const { - data: mintData, - writeAsync: mint, - isLoading: isMintLoading, - } = useContractWrite({ - addressOrName: CONTRACT_ADDRESS, - contractInterface: TierABI.abi, - functionName: "mint", - }); + data: mintHash, + writeContractAsync: mint, + isPending: isMintLoading, + } = useWriteContract(); useEffect(() => { try { @@ -795,13 +746,12 @@ export default function Home() { const mintToken = async (e) => { try { - let mintTxn = await mint({ - recklesslySetUnpreparedOverrides: { - value: ethers.utils.parseEther(e.target.value), - } + await mint({ + address: CONTRACT_ADDRESS, + abi: TierABI.abi, + functionName: 'mint', + value: parseEther(e.target.value), }); - await mintTxn.wait(); - console.log("This is the mint data", mintData); } catch (error) { console.log("Error minting NFT", error.message); } @@ -978,26 +928,24 @@ To be able to display information about our latest mint, we must first get information about it from our smart contract. ```jsx -// imports, consts and WAGMI useContractWrite hook... +// imports, consts and WAGMI useWriteContract hook... const { data: tokenData, refetch: refetchTokenData, - } = useContractRead({ - addressOrName: CONTRACT_ADDRESS, - contractInterface: TierABI.abi, + } = useReadContract({ + address: CONTRACT_ADDRESS, + abi: TierABI.abi, functionName: "totalSupply", - watch: true, }) const { data: tokenURI, - } = useContractRead({ - addressOrName: CONTRACT_ADDRESS, - contractInterface: TierABI.abi, + } = useReadContract({ + address: CONTRACT_ADDRESS, + abi: TierABI.abi, functionName: "tokenURI", - args: tokenData, - watch: true, + args: [tokenData], }) // useEffect to check user connection... @@ -1016,7 +964,7 @@ get information about it from our smart contract. try { let mintTxn = await mint({ recklesslySetUnpreparedOverrides: { - value: ethers.utils.parseEther(e.target.value), + value: parseEther(e.target.value), } }); await mintTxn.wait(); @@ -1032,15 +980,13 @@ get information about it from our smart contract. - The first thing we need is the `tokenId` of the latest NFT minted. We can do so by reading the value of the `totalSupply` stored in our smart contract because the `totalSupply` should equal to the latest `tokenId` in -our case. We can fetch this using the `useContractRead` hook from WAGMI +our case. We can fetch this using the `useReadContract` hook from WAGMI that is configured for reading data from smart contracts. The cool thing about solidity is that it automatically gives us a way to read values of variables that are tagged as `public`, which is why we are able to read the value of `totalSupply`. - The `refetchTokenData` function will be called in our `mintToken` function -to update the `tokenData` after minting has succeeded. We pass a -`watch: true` key-value pair so that the hook keeps an active watch on -the value of totalSupply. +to update the `tokenData` after minting has succeeded. - Then the returned value (tokenData) is passed in as an argument to the `tokenURI` function from our smart contract and we receive a `Base64` encoded string in return. @@ -1061,16 +1007,17 @@ now: try { setIsMinting(true); setModalShow(true); - let mintTxn = await mint({ - recklesslySetUnpreparedOverrides: { - value: ethers.utils.parseEther(e.target.value), - } + const hash = await mint({ + address: CONTRACT_ADDRESS, + abi: TierABI.abi, + functionName: 'mint', + value: parseEther(e.target.value), }); - await mintTxn.wait(); - console.log("This is the mint data", mintData); + console.log("Transaction Hash:", hash); refetchTokenData(); setIsMinting(false); } catch (error) { + setIsMinting(false); console.log("Error minting NFT", error.message); } }; @@ -1112,12 +1059,12 @@ We're there at last! Now we can render our modal.
@@ -1158,10 +1105,10 @@ If you are stuck somewhere, please refer to the code side drawer below. import Head from 'next/head'; import Image from 'next/image'; import { ConnectButton } from '@rainbow-me/rainbowkit'; -import { useAccount, useContractRead, useContractWrite } from 'wagmi'; +import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'; import TierABI from '../../artifacts/contracts/TierNFT.sol/TierNFT.json'; import styles from '../styles/Home.module.css'; -import { ethers } from 'ethers'; +import { parseEther } from 'viem'; import { useEffect, useState } from 'react'; export default function Home() { @@ -1175,33 +1122,27 @@ export default function Home() { const [isMinting, setIsMinting] = useState(false); const { - data: mintData, - writeAsync: mint, - isLoading: isMintLoading, - } = useContractWrite({ - addressOrName: CONTRACT_ADDRESS, - contractInterface: TierABI.abi, - functionName: "mint", - }); + data: mintHash, + writeContractAsync: mint, + isPending: isMintLoading, + } = useWriteContract(); const { data: tokenData, refetch: refetchTokenData, - } = useContractRead({ - addressOrName: CONTRACT_ADDRESS, - contractInterface: TierABI.abi, + } = useReadContract({ + address: CONTRACT_ADDRESS, + abi: TierABI.abi, functionName: "totalSupply", - watch: true, }) const { data: tokenURI, - } = useContractRead({ - addressOrName: CONTRACT_ADDRESS, - contractInterface: TierABI.abi, + } = useReadContract({ + address: CONTRACT_ADDRESS, + abi: TierABI.abi, functionName: "tokenURI", - args: tokenData, - watch: true, + args: [tokenData], }) useEffect(() => { @@ -1226,16 +1167,17 @@ export default function Home() { try { setIsMinting(true); setModalShow(true); - let mintTxn = await mint({ - recklesslySetUnpreparedOverrides: { - value: ethers.utils.parseEther(e.target.value), - } + const hash = await mint({ + address: CONTRACT_ADDRESS, + abi: TierABI.abi, + functionName: 'mint', + value: parseEther(e.target.value), }); - await mintTxn.wait(); - console.log("This is the mint data", mintData); + console.log("Transaction Hash:", hash); refetchTokenData(); setIsMinting(false); } catch (error) { + setIsMinting(false); console.log("Error minting NFT", error.message); } }; @@ -1312,12 +1254,12 @@ export default function Home() {