1"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7693],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>h});var a=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function r(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?i(Object(n),!0).forEach((function(t){o(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):i(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function s(e,t){if(null==e)return{};var n,a,o=function(e,t){if(null==e)return{};var n,a,o={},i=Object.keys(e);for(a=0;a<i.length;a++)n=i[a],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a<i.length;a++)n=i[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var l=a.createContext({}),u=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},p=function(e){var t=u(e.components);return a.createElement(l.Provider,{value:t},e.children)},c="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,o=e.mdxType,i=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),c=u(n),m=o,h=c["".concat(l,".").concat(m)]||c[m]||d[m]||i;return n?a.createElement(h,r(r({ref:t},p),{},{components:n})):a.createElement(h,r({ref:t},p))}));function h(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=n.length,r=new Array(i);r[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[c]="string"==typeof e?e:o,r[1]=s;for(var u=2;u<i;u++)r[u]=n[u];return a.createElement.apply(null,r)}return a.createElement.apply(null,n)}m.displayName="MDXCreateElement"},5579:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>h,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var a=n(7462),o=n(3366),i=(n(7294),n(3905)),r=["components"],s={title:"Create a Web Frontend"},l=void 0,u={unversionedId:"tutorials/ui/frontend",id:"tutorials/ui/frontend",title:"Create a Web Frontend",description:"In this tutorial, you\u2019ll build an NFT minter and learn how to create a full-stack dApp by connecting your smart contract to a React frontend using Metamask and Web3 tools. This guide has been adapted from the Alchemy NFT Minter tutorial.",source:"@site/../docs/4.tutorials/ui/frontend.md",sourceDirName:"4.tutorials/ui",slug:"/tutorials/ui/frontend",permalink:"/tutorials/ui/frontend",draft:!1,editUrl:"https://github.com/elastos/Elastos.Wiki/edit/master/website/../docs/4.tutorials/ui/frontend.md",tags:[],version:"current",lastUpdatedBy:"racollette",lastUpdatedAt:1677712783,formattedLastUpdatedAt:"Mar 1, 2023",frontMatter:{title:"Create a Web Frontend"},sidebar:"tutorialSidebar",previous:{title:"How to Mint an NFT",permalink:"/tutorials/nfts/mint-nft"}},p={},c=[{value:"Step 0: Making NFTs 101",id:"step-0-making-nfts-101",level:2},{value:"Step 1: Clone the Starter Files",id:"step-1-clone-the-starter-files",level:2},{value:"Step 2: Check out our Starter Files",id:"step-2-check-out-our-starter-files",level:2},{value:"Get your React project running",id:"get-your-react-project-running",level:3},{value:"The Minter.js Component",id:"the-minterjs-component",level:3},{value:"Step 3: Set up your Ethereum wallet",id:"step-3-set-up-your-ethereum-wallet",level:2},{value:"Create a Metamask Wallet",id:"create-a-metamask-wallet",level:3},{value:"Get some tELA from the Faucet",id:"get-some-tela-from-the-faucet",level:3},{value:"Step 4: Connect Metamask to your UI",id:"step-4-connect-metamask-to-your-ui",level:2},{value:"The connectWallet function",id:"the-connectwallet-function",level:3},{value:"Add connectWallet function to your Minter.js UI Component",id:"add-connectwallet-function-to-your-minterjs-ui-component",level:3},{value:"The getCurrentWalletConnected function",id:"the-getcurrentwalletconnected-function",level:3},{value:"Implement addWalletListener",id:"implement-addwalletlistener",level:3},{value:"Step 5: NFT Metadata 101",id:"step-5-nft-metadata-101",level:2},{value:"Step 6: Use Pinata to Pin you Metadata to IPFS",id:"step-6-use-pinata-to-pin-you-metadata-to-ipfs",level:2}
1,{value:"Create you Pinata API Key",id:"create-you-pinata-api-key",level:3},{value:"Create a .env file",id:"create-a-env-file",level:3},{value:"Implement pinJSONToIPFS",id:"implement-pinjsontoipfs",level:3},{value:"Step 7: Load your Smart Contract",id:"step-7-load-your-smart-contract",level:2},{value:"The contract ABI",id:"the-contract-abi",level:3},{value:"Set up Ethers",id:"set-up-ethers",level:3},{value:"Step 8: Implement the mintNFT Function",id:"step-8-implement-the-mintnft-function",level:2},{value:"Input error handling",id:"input-error-handling",level:3},{value:"Upload the metadata to IPFS",id:"upload-the-metadata-to-ipfs",level:3},{value:"Step 9: Connect mintNFT to our Minter.js Frontend",id:"step-9-connect-mintnft-to-our-minterjs-frontend",level:2},{value:"Step 10: Deploy your NFT to a Live Website",id:"step-10-deploy-your-nft-to-a-live-website",level:2}],d={toc:c},m="wrapper";function h(e){var t=e.components,s=(0,o.Z)(e,r);return(0,i.kt)(m,(0,a.Z)({},d,s,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"In this tutorial, you\u2019ll build an NFT minter and learn how to create a full-stack dApp by connecting your smart contract to a React frontend using Metamask and Web3 tools. This guide has been adapted from the ",(0,i.kt)("a",{parentName:"p",href:"https://docs.alchemy.com/docs/nft-minter#step-2-check-out-our-starter-files"},"Alchemy NFT Minter tutorial"),"."),(0,i.kt)("p",null,"One of the greatest challenges for developers coming from a Web2 background is figuring out how to connect your smart contact to a frontend project and interact with it."),(0,i.kt)("p",null,"By building an NFT minter\u2014a simple UI where you can input a link to your digital asset, a title, and a description \u2014you'll learn how to:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Connect to Metamask via your frontend project"),(0,i.kt)("li",{parentName:"ul"},"Call smart contract methods from your frontend"),(0,i.kt)("li",{parentName:"ul"},"Sign transactions using Metamask")),(0,i.kt)("p",null,"In this tutorial, we will be using ",(0,i.kt)("a",{parentName:"p",href:"https://reactjs.org/"},"React")," as our frontend framework. Because this tutorial is primarily focused on Web3 development, we won't be spending much time breaking down React fundamentals. Instead, we'll be focusing on bringing functionality to our project."),(0,i.kt)("admonition",{title:"React Understanding",type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"As a prerequisite, you should have a beginner-level understanding of React\u2014know how components, props, hooks (useState/useEffect), and basic function calling works. If you've never heard of those terms before, you may want to check out this ",(0,i.kt)("a",{parentName:"p",href:"https://reactjs.org/tutorial/tutorial.html"},"Intro to React tutorial"),".")),(0,i.kt)("h2",{id:"step-0-making-nfts-101"},"Step 0: Making NFTs 101"),(0,i.kt)("p",null,"Before we even start looking at any code, it's important to understand how making an NFT work. It involves two steps:"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},"You publish an NFT smart contract on the Ethereum blockchain. Usually this is an ERC-721 or ERC-1155 smart contract.")),(0,i.kt)("admonition",{title:"\ud83d\udcd8 Smart Contract Standards",type:"info"},(0,i.kt)("p",{parentName:"admonition"},"The biggest difference between the two NFT smart contact standards is that ERC-1155 is a multi-token standard and includes batch functionality, whereas the ERC-721 is a single-token standard and therefore only supports transferring one token at a time.")),(0,i.kt)("ol",{start:2},(0,i.kt)("li",{parentName:"ol"},"You call the minting function on that NFT smart contract to mint the NFT. Minting is simply the act of publishing a unique instance of your non-fungible token on the blockchain.")),(0,i.kt)("p",null,"Usually, this minting function requires you to pass in two variables as parameters, first the recipient, which specifies the address that will receive your freshly minted NFT, and second the NFT's tokenURI , a string that resolves to a JSON document describing the NFT's metadata."),(0,i.kt)("admonition",{title:"NFT'
1s Metadata",type:"info"},(0,i.kt)("p",{parentName:"admonition"},"An NFT's metadata is really what brings it to life, allowing it to have properties, such as a name, description, image (or different digital asset), and other attributes. Here's an example of a tokenURI, which contains an NFT's metadata.")),(0,i.kt)("p",null,"In this tutorial, we're going to focus on part 2, calling an existing NFT's smart contract minting function using our React UI."),(0,i.kt)("p",null,"Cool, now that we understand how making an NFT works, let's clone our starter files."),(0,i.kt)("h2",{id:"step-1-clone-the-starter-files"},"Step 1: Clone the Starter Files"),(0,i.kt)("p",null,"First, go to the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/racollette/nft-minter-tutorial"},"nft-minter-tutorial Github repository")," to get the starter files for this project. Clone this repository into your local environment."),(0,i.kt)("admonition",{title:"Github: Clone Repository",type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"Don't know how to clone a repository? Check out this ",(0,i.kt)("a",{parentName:"p",href:"https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository"},"guide")," from Github.")),(0,i.kt)("p",null,"When you open this cloned nft-minter-tutorial repository, you'll notice that it contains two folders: minter-starter-files and nft-minter."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"minter-starter-files")," contains the starter files (essentially the React UI) for this project. In this tutorial, we will be working in this directory, as you learn how to bring this UI to life by connecting it to your Ethereum wallet and an NFT smart contract."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"nft-minter")," contains the entire completed tutorial and is there for you as a ",(0,i.kt)("strong",{parentName:"li"},"reference if you get stuck"),".")),(0,i.kt)("p",null,"Next open your copy of ",(0,i.kt)("inlineCode",{parentName:"p"},"minter-starter-files")," in to your favorite code editor (i.e. ",(0,i.kt)("a",{parentName:"p",href:"https://code.visualstudio.com/"},"VSCode"),"), and then navigate into your src folder:"),(0,i.kt)("p",null,(0,i.kt)("img",{alt:"image",src:n(8130).Z,width:"1842",height:"1039"})),(0,i.kt)("p",null,"All the code we'll write will live under the src folder. We'll be editing the Minter.js component and writing additional JavaScript files to give our project Web3 functionality."),(0,i.kt)("h2",{id:"step-2-check-out-our-starter-files"},"Step 2: Check out our Starter Files"),(0,i.kt)("p",null,"Before we start coding, it's important to check out what's already provided to us in the starter files."),(0,i.kt)("h3",{id:"get-your-react-project-running"},"Get your React project running"),(0,i.kt)("p",null,"Let's start by running the React project in our browser. The beauty of React is that once we have our project running in our browser, any changes we save will be updated live in our browser."),(0,i.kt)("p",null,"To get the project running, navigate to the root directory of the minter-starter-files folder, and the run npm install in your terminal to install the dependencies of the project listed in package.json:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"cd minter-starter-files\nnpm install\n")),(0,i.kt)("p",null,"Once those have finished installing, run npm start in your terminal:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"npm start\n")),(0,i.kt)("p",null,"Doing so should open http://localhost:3000/ in your browser, where you'll see the frontend for our project. It should consist of 3 fields: a place to input a link to your NFT's asset, enter the name of your NFT, and provide a description."),(0,i.kt)("p",null,(0,i.kt)("img",{alt:"image",src:n(7271).Z,width:"1453",height:"759"}),"\n",(0,i.kt)("em",{parentName:"p"},"What your UI should look like")),(0,i.kt)("p",null,'If you try clicking "Connect Wallet" or "Mint NFT" buttons, you\'ll notice they don\'t work\u2014that\'s because we still need to program their functionality.'),(0,i.kt)("h3",{id:"the-minterjs-component"},"The Minter.js Component"),(0,i.kt)("admonition",{title:"NOTE",type:"caution"},(0,i.kt)("p",{parentName:"admonition"}
1,"Make sure you're in the minter-starter-files folder and not the nft-minter folder!")),(0,i.kt)("p",null,"Let's go back into the src folder in our editor and open the Minter.js file. It's super important that we understand everything in this file, as it is the primary React component we will be working on."),(0,i.kt)("p",null,"At the top of our this file, we have our state variables that we will update after specific events."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},'//State variables\nconst [walletAddress, setWallet] = useState("");\nconst [status, setStatus] = useState("");\nconst [name, setName] = useState("");\nconst [description, setDescription] = useState("");\nconst [url, setURL] = useState("");\n')),(0,i.kt)("admonition",{title:"React User Guide",type:"info"},(0,i.kt)("p",{parentName:"admonition"},"Never heard of React state variables or state hooks? Check out ",(0,i.kt)("a",{parentName:"p",href:"https://reactjs.org/docs/hooks-state.html"},"these docs"),".")),(0,i.kt)("p",null,"Here's what each of the variables represent:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"walletAddress")," - a string that stores the user's wallet address"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"status"),"- a string that contains a message to display at the bottom of the UI"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"name")," - a string that stores the NFT's name"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"description")," - a string that stores the NFT's description"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"url")," - a string that is a link to the NFT's digital asset")),(0,i.kt)("p",null,"After the state variables, you'll see three un-implemented functions: ",(0,i.kt)("inlineCode",{parentName:"p"},"useEffect"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"connectWalletPressed"),", and ",(0,i.kt)("inlineCode",{parentName:"p"},"onMintPressed"),". You'll notice that all of these functions are async, that's because we will be making asynchronous API calls in them! Their names describe their functionalities:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},"useEffect(async () => {\n //TODO: implement\n}, []);\n\nconst connectWalletPressed = async () => {\n //TODO: implement\n};\n\nconst onMintPressed = async () => {\n //TODO: implement\n};\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"useEffect"),"- this is a React hook that is called after your component is rendered. Because it has an empty array [] prop passed into it (see line 3), it will only be called on the component's first render. Here we'll call our wallet listener and another wallet function to update our UI to reflect whether a wallet is already connected."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"connectWalletPressed"),"- this function will be called to connect the user's Metamask wallet to our dApp."),
1(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"onMintPressed")," - this function will be called to mint the user's NFT.")),(0,i.kt)("p",null,"Near the end of this file, we have the UI of our component. If you scan this code carefully, you'll notice that we update our ",(0,i.kt)("inlineCode",{parentName:"p"},"url"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"name"),", and ",(0,i.kt)("inlineCode",{parentName:"p"},"description")," state variables when the input in their corresponding text fields change."),(0,i.kt)("p",null,"You'll also see that ",(0,i.kt)("inlineCode",{parentName:"p"},"connectWalletPressed")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"onMintPressedare")," called when the buttons with IDs mintButton and ",(0,i.kt)("inlineCode",{parentName:"p"},"walletButton")," are clicked respectively."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},'//The UI of our Minter Component\nreturn (\n <div className="Minter">\n <button id="walletButton" onClick={connectWalletPressed}>\n {walletAddress.length > 0 ? (\n "Connected: " +\n String(walletAddress).substring(0, 6) +\n "..." +\n String(walletAddress).substring(38)\n ) : (\n <span>Connect Wallet</span>\n )}\n </button>\n\n <br></br>\n <h1 id="title">NFT Minter</h1>\n <p>\n Simply add your asset\'s link, name, and description, then press "Mint."\n </p>\n <form>\n <h2>\ud83d\uddbc Link to asset: </h2>\n <input\n type="text"\n placeholder="e.g. https://gateway.pinata.cloud/ipfs/<hash>"\n onChange={(event) => setURL(event.target.value)}\n />\n <h2>\ud83e\udd14 Name: </h2>\n <input\n type="text"\n placeholder="e.g. My first NFT!"\n onChange={(event) => setName(event.target.value)}\n />\n <h2>\u270d\ufe0f Description: </h2>\n <input\n type="text"\n placeholder="e.g. 10,000 Happy Hedgehog NFTs"\n onChange={(event) => setDescription(event.target.value)}\n />\n </form>\n <button id="mintButton" onClick={onMintPressed}>\n Mint NFT\n </button>\n <p id="status">{status}</p>\n </div>\n);\n')),(0,i.kt)("p",null,"Finally, let's address where this Minter component is added."),(0,i.kt)("p",null,"If you go to the ",(0,i.kt)("inlineCode",{parentName:"p"},"App.js")," file, which is the main component in React that acts as a container for all other components, you'll see that the Minter component is injected on line 7."),(0,i.kt)("p",null,"In this tutorial, ",(0,i.kt)("strong",{parentName:"p"},"we'll only be editing")," the ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," file and ",(0,i.kt)("strong",{parentName:"p"},"adding files in our")," ",(0,i.kt)("inlineCode",{parentName:"p"},"src")," folder."),(0,i.kt)("p",null,"Now that we understand what we're working with, let's set up our Ethereum wallet!"),(0,i.kt)("h2",{id:"step-3-set-up-your-ethereum-wallet"},"Step 3: Set up your Ethereum wallet"),(0,i.kt)("p",null,"For users to be able to interact with your smart contract they will need to connect their Ethereum wallet to your dApp."),(0,i.kt)("h3",{id:"create-a-metamask-wallet"},"Create a Metamask Wallet"),(0,i.kt)("p",null,"We need an ESC wallet to send and receive transactions. For this tutorial, we\u2019ll use Metamask, a virtual wallet in the browser."),(0,i.kt)("p",null,"You can download and create a Metamask account for free ",(0,i.kt)("a",{parentName:"p",href:"https://metamask.io/download/"},"here"),". Once you have an account, you'll need to configure the network to use the ESC testnet. This can be accomplished in MetaMask by navigating to ",(0,i.kt)("inlineCode",{parentName:"p"},"Settings")," -> ",(0,i.kt)("inlineCode",{parentName:"p"},"Network"),"-> ",(0,i.kt)("inlineCode",{parentName:"p"},"Add Network")," -> ",(0,i.kt)("inlineCode",{parentName:"p"},"Add a Network Manually")," and entering the following information:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"Network name:")," ESC Testnet"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"New RPC URL:")," ",(0,i.kt)("inlineCode",{parentName:"li"},"https://api-testnet.elastos.io/esc")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"Chain ID:")," 21"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"Currency symbol:")," tELA"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("strong",{parentName:"li"},"Block explorer URL:")," ",(0,i.kt)("inlineCode",{parentName:"li"},"https://esc-testnet.elastos.io/"))),(0,i.kt)("p",null,(0,i.kt)("img",{alt:"image",src:n(5344).Z,width:"667",height:"688"})),(0,i.kt)("h3",{id:"get-some-tela-from-the-faucet"},"Get some tELA from the Faucet"),(0,i.kt)("p",null,"In order to deploy our smart contract to the test network, we\u2019ll need some fake ELA. You can acquire some by visiting the ",(0,i.kt)("a",{parentName:"p",href:"https://esc-faucet.elastos.io/"},"faucet"),"."),(0,i.kt)("p",null,"Simply enter your address, verify your humanity and press the 'Request 1 ESC' button."),(0,i.kt)("p",null,(0,i.kt)("img",{alt:"image",src:n(4055).Z,width:"447",height:"439"})),(0,i.kt)("p",null,"If all goes well, you should see your tELA balance updated on Metamask."),(0,i.kt)("h2",{id:"step-4-connect-metamask-to-your-ui"},"Step 4: Connect Metamask to your UI"),(0,i.kt)("p",null,"Now that our Metamask wallet is set up, let's connect our dApp to it!"),(0,i.kt)("p",null,"Because we want to prescribe to the ",(0,i.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller"},"M-V-C")," paradigm, we're going to create a separate file that contains our functions to manage the logic, data, and rules of our dApp, and then pass those functions to our frontend (our ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," component)."),(0,i.kt)("h3",{id:"the-connectwallet-function"},"The connectWallet function"),(0,i.kt)("p",null,"To do so, let's create a new folder called ",(0,i.kt)("inlineCode",{parentName:"p"},"utils")," in your ",(0,i.kt)("inlineCode",{parentName:"p"},"src")," directory and add a file called ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js")," inside it, which will contain all of our wallet and smart contract interaction functions."),(0,i.kt)("p",null,"In our ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js")," file, we will write a ",(0,i.kt)("inlineCode",{parentName:"p"},"connectWallet")," function, which we will then import and call in our ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," component."),(0,i.kt)("p",null,"In your ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js")," file, add the following"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="interact.js"',title:'"interact.js"'},'export const connectWallet = async () => {\n if (window.ethereum) {\n try {\n const addressArray = await window.ethereum.request({\n method: "eth_requestAccounts",\n });\n const obj = {\n status: "\ud83d\udc46\ud83c\udffd Write a message in the text-field above.",\n address: addressArray[0],\n };\n return obj;\n } catch (err) {\n return {\n address: "",\n status: "\ud83d\ude25 " + err.message,\n };\n }\n } else {\n return {\n address: "",\n status: (\n <sp
1an>\n <p>\n {" "}\n \ud83e\udd8a <a target="_blank" href={`https://metamask.io/download.html`}>\n You must install Metamask, a virtual Ethereum wallet, in your\n browser.\n </a>\n </p>\n </span>\n ),\n };\n }\n};\n')),(0,i.kt)("p",null,"Let's breakdown what this code does:"),(0,i.kt)("p",null,"First, our function checks if it ",(0,i.kt)("inlineCode",{parentName:"p"},"window.ethereum")," is enabled in your browser."),(0,i.kt)("admonition",{title:"Window.Ethereum API",type:"info"},(0,i.kt)("p",{parentName:"admonition"},"window.ethereum is a global API injected by Metamask and other wallet providers that allows websites to request users' Ethereum accounts. If approved, it can read data from the blockchains the user is connected to, and suggest that the user sign messages and transactions. Check out the ",(0,i.kt)("a",{parentName:"p",href:"https://docs.metamask.io/guide/ethereum-provider.html#table-of-contents"},"Metamask docs")," for more info!")),(0,i.kt)("p",null,"If ",(0,i.kt)("inlineCode",{parentName:"p"},"window.ethereum")," is not present, then that means Metamask is not installed. This results in a JSON object being returned, where ",(0,i.kt)("inlineCode",{parentName:"p"},"address")," returned is an empty string, and the ",(0,i.kt)("inlineCode",{parentName:"p"},"status")," JSX object relays that the user must install Metamask."),(0,i.kt)("admonition",{title:"JSON Objects",type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"Most of the functions we write will be returning JSON objects that we can use to update our state variables and UI.")),(0,i.kt)("p",null,"Now if ",(0,i.kt)("inlineCode",{parentName:"p"},"window.ethereum")," is present, then that's when things get interesting. Using a try/catch loop, we'll try to connect to Metamask by calling ",(0,i.kt)("a",{parentName:"p",href:"https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts"},(0,i.kt)("inlineCode",{parentName:"a"},'window.ethereum.request({ method: "eth_requestAccounts" });'))),(0,i.kt)("p",null,"Calling this function will open up Metamask in the browser, whereby the user will be prompted to connect their wallet to your dApp."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"If the user chooses to connect, ",(0,i.kt)("inlineCode",{parentName:"li"},'method: "eth_requestAccounts"')," will return an array that contains all of the user's account addresses that are connected to the dApp. Altogether, our ",(0,i.kt)("inlineCode",{parentName:"li"},"connectWallet")," function will return a JSON object that contains the first address in this array (see line 9) and a status message that prompts the user to write a message to the smart contract."),(0,i.kt)("li",{parentName:"ul"},"If the user rejects the connection, then the JSON object will contain an empty string for the address returned and a status message that reflects that the user rejected the connection.")),(0,i.kt)("h3",{id:"add-connectwallet-function-to-your-minterjs-ui-component"},"Add connectWallet function to your Minter.js UI Component"),(0,i.kt)("p",null,"Now that we've written this ",(0,i.kt)("inlineCode",{parentName:"p"},"connectWallet")," function, let's connect it to our ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js"),". component."),(0,i.kt)("p",null,"First, we'll have to import our function into our ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js"),' file by adding import { connectWallet } from "./utils/interact.js"; to the top of the ',(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," file. Your first 11 lines of ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," should now look like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},'\nimport { useEffect, useState } from "react";\nimport { connectWallet } from "./utils/interact.js";\n\nconst Minter = (props) => {\n\n //State variables\n const [walletAddress, setWallet] = useState("");\n const [status, setStatus] = useState("");\n const [name, setName] = useState("");\n const [description, setDescription] = useState("");\n const [url, setURL] = useState("");\n')),(0,i.kt)("p",null,"Then, inside our connectWalletPressed function, we'll call our imported connectWallet function, like so:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},"const connectWalletPressed = async () => {\n const walletResponse = await connectWallet();\n setStatus(walletResponse.status);\n setWallet(walletResponse.address);\n};\n")),(0,i.kt)("p",null,"In ",(0,i.kt)("inlineCode",{parentName:"p"},"connectWalletPressed"),", we simply make an await call to our imported ",(0,i.kt)("inlineCode",{parentName:"p"},"connectWallet")," function, and using its response, we update our ",(0,i.kt)("inlineCode",{parentName:"p"},"status")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"walletAddress")," variables via their state hooks."),(0,i.kt)("p",null,"Now, let's save both files (",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js"),") and test out our UI so far."),(0,i.kt)("p",null,'Open your browser on the http://localhost:3000/ page, and press the "Connect Wallet" button on the top right of the page.'),(0,i.kt)("p",null,"If you have Metamask installed, you should be prompted to connect your wallet to your dApp. Accept the invitation to connect."),(0,i.kt)("p",null,"You should see that the wallet button now reflects that your address is connected! \ud83d\udd25"),(0,i.kt)("h3",{id:"the-getcurrentwalletconnected-function"},"The getCurrentWalletConnected function"),(0,i.kt)("p",null,"Next, to fix the issue of our connect button prompting us to to connect to Metamask even though it is already connected, we can fix that by implementing a function ",(0,i.kt)("inlineCode",{parentName:"p"},"calledgetCurrentWalletConnected"),", which will check if an address is already connected to our dApp and update our UI accordingly."),(0,i.kt)("p",null,"In your ",(0,i.kt)("inlineCode",{parentName:"p"},"interact."),"js file, add the following getCurrentWalletConnected function:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="interact.js"',title:'"interact.js"'},'export const getCurrentWalletConnected = async () => {\n if (window.ethereum) {\n try {\n const addressArray = await window.ethereum.request({\n method: "eth_accounts",\n });\n if (addressArray.length > 0) {\n return {\n address: addressArray[0],\n status: "\ud83d\udc46\ud83c\udffd Write a message in the text-field above.",\n };\n } else {\n return {\n address: "",\n status: "\ud83e\udd8a Connect to Metamask using the top right button.",\n };\n }\n } catch (err) {\n return {\n address: "",\n status: "\ud83d\ude25 " + err.message,\n };\n }\n } else {\n return {\n address: "",\n status: (\n <sp
1an>\n <p>\n {" "}\n \ud83e\udd8a <a target="_blank" href={`https://metamask.io/download.html`}>\n You must install Metamask, a virtual Ethereum wallet, in your\n browser.\n </a>\n </p>\n </span>\n ),\n };\n }\n};\n')),(0,i.kt)("p",null,"This code is very similar to the ",(0,i.kt)("inlineCode",{parentName:"p"},"connectWallet")," function we just wrote earlier."),(0,i.kt)("p",null,"The main difference is that instead of calling the method ",(0,i.kt)("inlineCode",{parentName:"p"},"eth_requestAccounts"),", which opens Metamask for the user to connect their wallet, here we call the method ",(0,i.kt)("inlineCode",{parentName:"p"},"eth_accounts"),", which simply returns an array containing the Metamask addresses currently connected to our dApp."),(0,i.kt)("p",null,"To see this function in action, let's call it in the ",(0,i.kt)("inlineCode",{parentName:"p"},"useEffect")," function of our ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," component."),(0,i.kt)("p",null,"Like we did for ",(0,i.kt)("inlineCode",{parentName:"p"},"connectWallet"),", we must import this function from our ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js")," file into our ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," file like so:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},'import { useEffect, useState } from "react";\nimport {\n connectWallet,\n getCurrentWalletConnected, //import here\n} from "./utils/interact.js";\n')),(0,i.kt)("p",null,"Now, we simply call it in our ",(0,i.kt)("inlineCode",{parentName:"p"},"useEffect")," function:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},"useEffect(async () => {\n const { address, status } = await getCurrentWalletConnected();\n setWallet(address);\n setStatus(status);\n}, []);\n")),(0,i.kt)("p",null,"Notice, we use the response of our call to ",(0,i.kt)("inlineCode",{parentName:"p"},"getCurrentWalletConnected")," to update our ",(0,i.kt)("inlineCode",{parentName:"p"},"walletAddress")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"status")," state variables."),(0,i.kt)("p",null,"Once you've added this code, try refreshing our browser window. The button should say that you're connected, and show a preview of your connected wallet's address - even after you refresh! \ud83d\ude05"),(0,i.kt)("h3",{id:"implement-addwalletlistener"},"Implement addWalletListener"),(0,i.kt)("p",null,"The final step in our dApp wallet setup is implementing the wallet listener so our UI updates when our wallet's state changes, such as when the user disconnects or switches accounts."),(0,i.kt)("p",null,"In your ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," file, add a function addWalletListener that looks like the following:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},'function addWalletListener() {\n if (window.ethereum) {\n window.ethereum.on("accountsChanged", (accounts) => {\n if (accounts.length > 0) {\n setWallet(accounts[0]);\n setStatus("\ud83d\udc46\ud83c\udffd Write a message in the text-field above.");\n } else {\n setWallet("");\n setStatus("\ud83e\udd8a Connect to Metamask using the top right button.");\n }\n });\n } else {\n setStatus(\n <p>\n {" "}\n \ud83e\udd8a <a target="_blank" href={`https://metamask.io/download.html`}>\n You must install Metamask, a virtual Ethereum wallet, in your browser.\n </a>\n </p>\n );\n }\n}\n')),(0,i.kt)("p",null,"Let's quickly break down what's happening here:"),(0,i.kt)("p",null,"First, our function checks if ",(0,i.kt)("inlineCode",{parentName:"p"},"window.ethereum")," is enabled (i.e. Metamask is installed)."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"If it's not, we simply set our status state variable to a JSX string that prompts the user to install Metamask."),(0,i.kt)("li",{parentName:"ul"},"If it is enabled, we set up the listener ",(0,i.kt)("inlineCode",{parentName:"li"},'window.ethereum.on("accountsChanged")')," on line 3 that listens for state changes in the Metamask wallet, which include when the user connects an additional account to the dApp, switches accounts, or disconnects an account. If there is at least one account connected, the ",(0,i.kt)("inlineCode",{parentName:"li"},"walletAddress")," state variable is updated as the first account in the accounts array returned by the listener. Otherwise, ",(0,i.kt)("inlineCode",{parentName:"li"},"walletAddress")," is set as an empty string.")),(0,i.kt)("p",null,"Finally, we must call it in our useEffect function:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},"useEffect(async () => {\n const { address, status } = await getCurrentWalletConnected();\n setWallet(address);\n setStatus(status);\n\n addWalletListener();\n}, []);\n")),(0,i.kt)("p",null,"And that's it! We've completed programming all of our wallet functionality! Now that our wallet is set up, let's figure out how to mint our NFT!"),(0,i.kt)("h2",{id:"step-5-nft-metadata-101"},"Step 5: NFT Metadata 101"),(0,i.kt)("p",null,"So remember the NFT metadata we just talked about in Step 0 of this tutorial\u2014it brings an NFT to life, allowing it to have properties, such as a digital asset, name, description, and other attributes."),(0,i.kt)("p",null,"We're going to need to configure this metadata as a JSON object and store it, so we can pass it in as the tokenURI parameter when calling our smart contract's ",(0,i.kt)("inlineCode",{parentName:"p"},"mintNFT")," function."),(0,i.kt)("p",null,'The text in the "Link to Asset", "Name", "Description" fields will comprise the different properties of our NFT\'s metadata. We\'ll format this metadata as a JSON object, but there are a couple options for where we can store this JSON object:'),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"We could store it on the Elastos blockchain;
1 however, doing so would be somewhat expensive due to the nature of the Ethereum Virtual Machine. \u274c"),(0,i.kt)("li",{parentName:"ul"},"We could store it on a centralized server, like AWS or Firebase. But that would defeat our decentralization ethos. \u274c"),(0,i.kt)("li",{parentName:"ul"},"We could use IPFS, a decentralized protocol and peer-to-peer network for storing and sharing data in a distributed file system. As this protocol is decentralized and free, it is our best option! \u2705")),(0,i.kt)("p",null,"To store our metadata on IPFS, we will use Pinata, a convenient IPFS API and toolkit. In the next step, we'll explain exactly how to do this."),(0,i.kt)("h2",{id:"step-6-use-pinata-to-pin-you-metadata-to-ipfs"},"Step 6: Use Pinata to Pin you Metadata to IPFS"),(0,i.kt)("p",null,"If you don't have a Pinata account, sign up for a free account ",(0,i.kt)("a",{parentName:"p",href:"https://pinata.cloud/signup"},"here")," and complete the steps to verify your email and account."),(0,i.kt)("h3",{id:"create-you-pinata-api-key"},"Create you Pinata API Key"),(0,i.kt)("p",null,"Navigate to the ",(0,i.kt)("a",{parentName:"p",href:"https://pinata.cloud/keys"},"https://pinata.cloud/keys"),' page, then select the "New Key" button at the top, set the Admin widget as enabled, and name your key.'),(0,i.kt)("p",null,(0,i.kt)("img",{alt:"image",src:n(9522).Z,width:"2880",height:"1644"}),"\n",(0,i.kt)("em",{parentName:"p"},"Create your Pinata API key")),(0,i.kt)("p",null,"You'll then be shown a popup with your API info. Make sure to put this somewhere safe."),(0,i.kt)("p",null,(0,i.kt)("img",{alt:"image",src:n(1975).Z,width:"2880",height:"1650"}),"\n",(0,i.kt)("em",{parentName:"p"},"Make sure to save your API key and secret in a safe place")),(0,i.kt)("p",null,"Now that our key is set up, let's add it to our project so we can use it."),(0,i.kt)("h3",{id:"create-a-env-file"},"Create a .env file"),(0,i.kt)("p",null,"We can safely store our Pinata key and secret in an environment file. Let's install the dotenv package in your project directory."),(0,i.kt)("p",null,"Open up a new tab in your terminal (separate from the one running local host) and make sure you are in the minter-starter-files folder, then run the following command in your terminal:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"npm install dotenv --save\n")),(0,i.kt)("p",null,"Next, create a ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file in the root directory of your minter-starter-files by entering the following on your command line:"),(0,i.kt)("admonition",{title:"WARNING:",type:"warning"},(0,i.kt)("p",{parentName:"admonition"},"Do not ship a production app with secrets in the .env file. This tutorial shows you how to upload to IPFS via your react client directly as a demonstration only. When you are ready for production, you should re-factor your application to upload IPFS files using a backend service."),(0,i.kt)("p",{parentName:"admonition"},"For more context on React environment variables: ",(0,i.kt)("a",{parentName:"p",href:"https://create-react-app.dev/docs/adding-custom-environment-variables/#adding-development-environment-variables-in-env"},"https://create-react-app.dev/docs/adding-custom-environment-variables/#adding-development-environment-variables-in-env"))),(0,i.kt)("p",null,"Next, in VSCode, navigate to your .env file and add your Pinata API key and API secret to it, like so:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"REACT_APP_PINATA_KEY = <pinata-api-key>\nREACT_APP_PINATA_SECRET = <pinata-api-secret>\n")),(0,i.kt)("p",null,"Save the file, and then you're ready to start writing the function to upload your JSON metadata to IPFS!"),(0,i.kt)("h3",{id:"implement-pinjsontoipfs"},"Implement pinJSONToIPFS"),(0,i.kt)("p",null,"Fortunately for us, Pinata has an ",(0,i.kt)("a",{parentName:"p",href:"https://pinata.cloud/documentation#PinJSONToIPFS"},"API specifically for uploading JSON data to IPFS")," and a convenient JavaScript with axios example that we can use, with some slight modifications."),(0,i.kt)("p",null,"In your ",(0,i.kt)("inlineCode",{parentName:"p"},"utils")," folder, let's create another file called ",(0,i.kt)("inlineCode",{parentName:"p"},"pinata.js")," and then import our Pinata secret and key from the .env file like so:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="pinata.js"',title:'"pinata.js"'},'require("dotenv").config();\nconst key = process.env.REACT_APP_PINATA_KEY;\nconst secret = process.env.REACT_APP_PINATA_SECRET;\n')),(0,i.kt)("p",null,"Next, paste the additional code from below into your ",(0,i.kt)("inlineCode",{parentName:"p"},"pinata.js")," file. Don't worry, we'll break down what everything means!"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="pinata.js"',title:'"pinata.js"'},'require("dotenv").config();\nconst key = process.env.REACT_APP_PINATA_KEY;\nconst secret = process.env.REACT_APP_PINATA_SECRET;\n\nconst axios = require("axios");\n\nexport const pinJSONToIPFS = async (JSONBody) =>
1 {\n const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`;\n //making axios POST request to Pinata \u2b07\ufe0f\n return axios\n .post(url, JSONBody, {\n headers: {\n pinata_api_key: key,\n pinata_secret_api_key: secret,\n },\n })\n .then(function (response) {\n return {\n success: true,\n pinataUrl:\n "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash,\n };\n })\n .catch(function (error) {\n console.log(error);\n return {\n success: false,\n message: error.message,\n };\n });\n};\n')),(0,i.kt)("p",null,"So what does this code do exactly?"),(0,i.kt)("p",null,"First, it imports ",(0,i.kt)("a",{parentName:"p",href:"https://www.npmjs.com/package/axios"},"axios"),", a promise based HTTP client for the browser and node.js, which we will use to make a request to Pinata."),(0,i.kt)("p",null,"Then we have our asynchronous function ",(0,i.kt)("inlineCode",{parentName:"p"},"pinJSONToIPFS"),", which takes a ",(0,i.kt)("inlineCode",{parentName:"p"},"JSONBody")," as its input and the Pinata api key and secret in its header, all to make a POST request to their ",(0,i.kt)("inlineCode",{parentName:"p"},"pinJSONToIPFS")," API."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"If this POST request is successful, then our function returns a JSON object with the ",(0,i.kt)("inlineCode",{parentName:"li"},"success")," boolean as true and the ",(0,i.kt)("inlineCode",{parentName:"li"},"pinataUrl")," where our metadata was pinned. We will use this ",(0,i.kt)("inlineCode",{parentName:"li"},"pinataUrl")," returned as the ",(0,i.kt)("inlineCode",{parentName:"li"},"tokenURI")," input to our smart contract's ",(0,i.kt)("inlineCode",{parentName:"li"},"mint")," function."),(0,i.kt)("li",{parentName:"ul"},"If this post request fails, then our function returns a JSON object with the ",(0,i.kt)("inlineCode",{parentName:"li"},"success")," boolean as false and a ",(0,i.kt)("inlineCode",{parentName:"li"},"message")," string that relays our error.")),(0,i.kt)("p",null,"As with our ",(0,i.kt)("inlineCode",{parentName:"p"},"connectWalletfunction")," return types, we're returning JSON objects so we can use their parameters to update our state variables and UI."),(0,i.kt)("h2",{id:"step-7-load-your-smart-contract"},"Step 7: Load your Smart Contract"),(0,i.kt)("p",null,"Now that we have a way to upload our NFT metadata to IPFS via our ",(0,i.kt)("inlineCode",{parentName:"p"},"pinJSONToIPFS")," function, we're going to need a way to load an instance of our smart contract so we can call its ",(0,i.kt)("inlineCode",{parentName:"p"},"mintNFT")," function."),(0,i.kt)("admonition",{title:"NFT Smart Contract",type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"As we mentioned earlier, in this tutorial we will be using this ",(0,i.kt)("a",{parentName:"p",href:"https://esc-testnet.elastos.io/address/0x93E5Dcad2238273B3A258A358834F210FE5f0Fef"},"existing NFT smart contract"),"; however, if you'd like to learn how we made it, or make one yourself, we highly recommend you check out the tutorial from the previous section, ",(0,i.kt)("a",{parentName:"p",href:"/tutorials/nfts/create-md"},'"Create an NFT Contract"'),".")),(0,i.kt)("h3",{id:"the-contract-abi"},"The contract ABI"),(0,i.kt)("p",null,"If you examined our files closely, you'll have noticed that in our ",(0,i.kt)("inlineCode",{parentName:"p"},"src")," directory, there's a ",(0,i.kt)("inlineCode",{parentName:"p"},"contract-abi.json")," file. An ABI is necessary for specifying which function a contract will invoke as well ensuring that the function will return data in the format you're expecting."),(0,i.kt)("h3",{id:"set-up-ethers"},"Set up Ethers"),(0,i.kt)("p",null,"First, if you don't have it already, you'll need to install ",(0,i.kt)("a",{parentName:"p",href:"https://docs.ethers.org/v5/getting-started/"},"Ethers")," by navigating to the home directory: ",(0,i.kt)("inlineCode",{parentName:"p"},"nft-minter-tutorial")," in the terminal:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"npm install ethers\n")),(0,i.kt)("p",null,"Next let's go back to our ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js")," file. At the top of the file, add the following code to set up your ethers endpoint:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="interact.js"',title:'"interact.js"'},'import { pinJSONToIPFS } from "./pinata.js";\nimport { ethers } from "ethers";\nconst provider = new ethers.providers.Web3Provider(window.ethereum);\n')),(0,i.kt)("p",null,"Next, let's add our contract ABI and contract address to our ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js")," file."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="interact.js"',title:'"interact.js"'},'import { pinJSONToIPFS } from "./pinata.js";\nimport { ethers } from "ethers";\nconst provider = new ethers.providers.Web3Provider(window.ethereum);\n\nconst contractABI = require("../contract-abi.json");\nconst contractAddress = "0x93E5Dcad2238273B3A258A358834F210FE5f0Fef";\n')),(0,i.kt)("p",null,"Once we have both of those, we're ready to start coding our mint function!"),(0,i.kt)("h2",{id:"step-8-implement-the-mintnft-function"},"Step 8: Implement the mintNFT Function"),(0,i.kt)("p",null,"Inside your ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js")," file, let's define our function, mintNFT , which will mint our NFT as the name implies.\n`\nBecause we will be making numerous asynchronous calls (to Pinata to pin our metadata to IPFS, ethers to load our smart contract, and Metamask to sign our transactions), our function will also be asynchronous."),
1(0,i.kt)("p",null,"The three inputs to our function will be the ",(0,i.kt)("inlineCode",{parentName:"p"},"url")," of our digital asset, ",(0,i.kt)("inlineCode",{parentName:"p"},"name"),", and ",(0,i.kt)("inlineCode",{parentName:"p"},"description"),". Add the following function signature below the ",(0,i.kt)("inlineCode",{parentName:"p"},"connectWallet")," function:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="interact.js"',title:'"interact.js"'},"export const mintNFT = async (url, name, description) => {};\n")),(0,i.kt)("h3",{id:"input-error-handling"},"Input error handling"),(0,i.kt)("p",null,"Naturally, it makes sense to have some sort of input error handling at the start of the function, so we exit this function if our input parameters aren't correct. Inside our function, let's add the following code:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="interact.js"',title:'"interact.js"'},'export const mintNFT = async (url, name, description) => {\n //error handling\n if (url.trim() == "" || name.trim() == "" || description.trim() == "") {\n return {\n success: false,\n status: "\u2757Please make sure all fields are completed before minting.",\n };\n }\n};\n')),(0,i.kt)("p",null,"Essentially, if any of the input parameters are an empty string, then we return a JSON object where the ",(0,i.kt)("inlineCode",{parentName:"p"},"success")," boolean is false, and the ",(0,i.kt)("inlineCode",{parentName:"p"},"status")," string relays that all fields in our UI must be complete."),(0,i.kt)("h3",{id:"upload-the-metadata-to-ipfs"},"Upload the metadata to IPFS"),(0,i.kt)("p",null,"Once we know our metadata is formatted properly, the next step is to wrap it into a JSON object and upload it to IPFS via the ",(0,i.kt)("inlineCode",{parentName:"p"},"pinJSONToIPFS")," we wrote!"),(0,i.kt)("p",null,"To do so, we firstly need to import the ",(0,i.kt)("inlineCode",{parentName:"p"},"pinJSONToIPFS")," function into our ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js")," file. At the very top of the ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js"),", let's add:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js"},'import { pinJSONToIPFS } from "./pinata.js";\n')),(0,i.kt)("p",null,"Recall that ",(0,i.kt)("inlineCode",{parentName:"p"},"pinJSONToIPFS")," takes in a JSON body. So before we make a call to it, we're going to need to format our ",(0,i.kt)("inlineCode",{parentName:"p"},"url"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"name"),", and ",(0,i.kt)("inlineCode",{parentName:"p"},"description")," parameters into a JSON object."),(0,i.kt)("p",null,"Let's update our code in ",(0,i.kt)("inlineCode",{parentName:"p"},"interact.js")," to create a JSON object called metadata and then make a call to ",(0,i.kt)("inlineCode",{parentName:"p"},"pinJSONToIPFS")," with this metadata parameter:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js"},'export const mintNFT = async (url, name, description) => {\n //error handling\n if (url.trim() == "" || name.trim() == "" || description.trim() == "") {\n return {\n success: false,\n status: "\u2757Please make sure all fields are completed before minting.",\n };\n }\n\n //make metadata\n const metadata = new Object();\n metadata.name = name;\n metadata.image = url;\n metadata.description = description;\n\n //make pinata call\n const pinataResponse = await pinJSONToIPFS(metadata);\n if (!pinataResponse.success) {\n return {\n success: false,\n status: "\ud83d\ude22 Something went wrong while uploading your tokenURI.",\n };\n }\n const tokenURI = pinataResponse.pinataUrl;\n};\n')),(0,i.kt)("p",null,"Notice, we store the response of our call to ",(0,i.kt)("inlineCode",{parentName:"p"},"pinJSONToIPFS")," (metadata) in the ",(0,i.kt)("inlineCode",{parentName:"p"},"pinataResponse")," object. Then, we parse this object for any errors."),(0,i.kt)("p",null,"If there's an error, we return a JSON object where the success boolean is false and our status string relays that our call failed. Otherwise, we extract the pinataURL from the ",(0,i.kt)("inlineCode",{parentName:"p"},"pinataResponse")," and store it as our tokenURIvariable."),(0,i.kt)("p",null,"Now it's time to load our smart contract using the ethers package that we initialized at the top of our file. Add the following line of code to the bottom of the ",(0,i.kt)("inlineCode",{parentName:"p"},"mintNFT")," function to set the contract at the window.contract global variable:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js"},"window.contract = await new ethers.Contract(\n contractAddress,\n contractABI,\n provider\n);\n")),(0,i.kt)("p",null,"The last thing to add in our mintNFT function is our Ethereum transaction:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="interact.js"',title:'"interact.js"'},'export const mintNFT = async (url, name, description) =>
1 {\n //error handling\n if (url.trim() == "" || name.trim() == "" || description.trim() == "") {\n return {\n success: false,\n status: "\u2757Please make sure all fields are completed before minting.",\n };\n }\n\n //make metadata\n const metadata = new Object();\n metadata.name = name;\n metadata.image = url;\n metadata.description = description;\n\n //make pinata call\n const pinataResponse = await pinJSONToIPFS(metadata);\n if (!pinataResponse.success) {\n return {\n success: false,\n status: "\ud83d\ude22 Something went wrong while uploading your tokenURI.",\n };\n }\n const tokenURI = pinataResponse.pinataUrl;\n};\n')),(0,i.kt)("p",null,"If you're already familiar with Ethereum transactions, you'll notice that the structure is pretty similar to what you've seen."),(0,i.kt)("p",null,"First, we set up our transactions parameters."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"to")," specifies the recipient address (our smart contract)"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"from")," specifies the signer of the transaction (the user's connected address to Metamask: ",(0,i.kt)("inlineCode",{parentName:"li"},"window.ethereum.selectedAddress"),")"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"data")," contains the call to our smart contract mintNFT method, which receives our ",(0,i.kt)("inlineCode",{parentName:"li"},"tokenURI")," and the user's wallet address, ",(0,i.kt)("inlineCode",{parentName:"li"},"window.ethereum.selectedAddress"),", as inputs")),(0,i.kt)("p",null,"Then, we make an await call, ",(0,i.kt)("inlineCode",{parentName:"p"},"window.ethereum.request"),", where we ask Metamask to sign the transaction. Notice, in this request, we're specifying our eth method (",(0,i.kt)("inlineCode",{parentName:"p"},"eth_SentTransaction"),") and passing in our ",(0,i.kt)("inlineCode",{parentName:"p"},"transactionParameters"),". At this point, Metamask will open up in the browser, and prompt the user to sign or reject the transaction."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"If the transaction is successful, the function will return a JSON object where the boolean ",(0,i.kt)("inlineCode",{parentName:"li"},"success")," is set to true and the status string prompts the user to check out Etherscan for more information about their transaction."),(0,i.kt)("li",{parentName:"ul"},"If the transaction fails, the function will return a JSON object where the ",(0,i.kt)("inlineCode",{parentName:"li"},"success")," boolean is set to false, and the status string relays the error message.")),(0,i.kt)("p",null,"Altogether, our ",(0,i.kt)("inlineCode",{parentName:"p"},"mintNFT")," function should look like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},'export const mintNFT = async (url, name, description) => {\n if (url.trim() == "" || name.trim() == "" || description.trim() == "") {\n return {\n success: false,\n status: "\u2757Please make sure all fields are completed before minting.",\n };\n }\n\n //make metadata\n const metadata = new Object();\n metadata.name = name;\n metadata.image = url;\n metadata.description = description;\n\n const pinataResponse = await pinJSONToIPFS(metadata);\n if (!pinataResponse.success) {\n return {\n success: false,\n status: "\ud83d\ude22 Something went wrong while uploading your tokenURI.",\n };\n }\n const tokenURI = pinataResponse.pinataUrl;\n\n window.ethereum.enable();\n const signer = await provider.getSigner();\n const contract = await new ethers.Contract(\n contractAddress,\n contractABI,\n provider\n );\n const contractWithSigner = contract.connect(signer);\n\n try {\n const { hash } = await contractWithSigner.mintNFT(\n window.ethereum.selectedAddress,\n tokenURI\n );\n return {\n success: true,\n status:\n "\u2705 Check out your transaction on the explorer: https://esc-testnet.elastos.io/tx/" +\n hash,\n };\n } catch (error) {\n return {\n success: false,\n status: "\ud83d\ude25 Something went wrong: " + error.message,\n };\n }\n};\n')),(0,i.kt)("p",null,"Now, we just need to connect our mintNFT function to our ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," component."),(0,i.kt)("h2",{id:"step-9-connect-mintnft-to-our-minterjs-frontend"},"Step 9: Connect mintNFT to our Minter.js Frontend"),(0,i.kt)("p",null,"Open up your ",(0,i.kt)("inlineCode",{parentName:"p"},"Minter.js")," file and update the ",(0,i.kt)("inlineCode",{parentName:"p"},"import { connectWallet, getCurrentWalletConnected }")," from ",(0,i.kt)("inlineCode",{parentName:"p"},'"./utils/interact.js";')," line at the top to be:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js"},'import {\n connectWallet,\n getCurrentWalletConnected,\n mintNFT,\n} from "./utils/interact.js";\n')),(0,i.kt)("p",null,"Finally, implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"onMintPressed")," function to make await call to your imported ",(0,i.kt)("inlineCode",{parentName:"p"},"mintNFTfunction")," and update the status state variable to reflect whether our transaction succeeded or failed:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Minter.js"',title:'"Minter.js"'},"const onMintPressed = async () => {\n const { status } = await mintNFT(url, name, description);\n setStatus(status);\n};\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Congratulations!")," You can now mint NFTs using your own minting website!"),(0,i.kt)("h2",{id:"step-10-deploy-your-nft-to-a-live-website"},"Step 10: Deploy your NFT to a Live Website"),(0,i.kt)("p",null,"Ready to take your project live for users to interact with? Check out ",(0,i.kt)("a",{parentName:"p",href:"https://docs.alchemy.com/docs/how-to-build-an-nft-website"},"this Alchemy guide")," for some tips."))}h.isMDXComponent=!0},5344:(e,t,n)=>{n.d(t,{Z:()=>a});const a=n.p+"assets/images/add-esc-testnet-metamask-b6770c6f054866df4857ee014ca1d7fb.png"},4055:(e,t,n)=>{n.d(t,{Z:()=>a});const a=n.p+"assets/images/esc-testnet-faucet-4b2fabef019e0bc102cb52a24735ac72.png"},8130:(e,t,n)=>{n.d(t,{Z:()=>a});const a=n.p+"assets/images/code-directory-e5ea24c0969bb85e4879d270aa7bed51.png"},7271:(e,t,n)=>{n.d(t,{Z:()=>a});const a=n.p+"assets/images/nft-minter-localhost-f789dcbb25562a28512d13a75717358c.png"},9522:(e,t,n)=>{n.d(t,{Z:()=>a});const a=n.p+"assets/images/pinata-1-e114ae628f5fc39aab90486b7be8b492.png"},1975:(e,t,n)=>{n.d(t,{Z:()=>a});const a=n.p+"assets/images/pinata-2-7de0c101603823f367ce5b7e448c9378.png"}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.