# Build SVG NFT dApp by Scaffold-Eth | Web3.0 dApp Dev 0x07

By [Web3dAppDevCamp](https://paragraph.com/@apecoder) · 2022-02-11

---

> 国内用户如需交流，可加微信：197626581.

> **Authors:** [qiwihui](https://github.com/qiwihui), msfew

> History Articles List:
> 
> *   [Quickstart | Web3.0 dApp Dev 0x01](https://t.co/sUbwu4kzGA)
>     
> *   [Web3.0 dApp Developer Growth Path | Web3.0 dApp Dev 0x02](https://t.co/sfpx5DjoZj)
>     
> *   [Scaffold-Eth Quickstart | Web3.0 dApp Dev 0x03](https://t.co/lvL1IfousS)
>     
> *   [Get & Set Value 1.0 | Web3.0 dApp Dev 0x04](https://t.co/6v0ajRD1kp)
>     
> *   [Get & Set Value 2.0 | Web3.0 dApp Dev 0x05](https://mirror.xyz/0x73c7448760517E3E6e416b2c130E3c6dB2026A1d/b2Pz3Cxq3A9bjKWNlkgEqStZaMc6w89Cupgw9CcsPSs)
>     
> *   [Get & Set Value 3.0 | Web3.0 dApp Dev 0x06](https://mirror.xyz/0x73c7448760517E3E6e416b2c130E3c6dB2026A1d/HvS9-pIa1raDuBUCaKGDZUpkFqElUX0fZpMkjoe5HQA)
>     

loogies-svg-nft is a simple NFT minting and displaying project provided by scaffold-eth. In this tutorial, we will walk you through a step-by-step analysis and implementation of this project.

Since the `loogies-svg-nft` branch and the `master` branch of the project have some differences in the component library and home page, the `master` branch code needs to be merged with the `loogies-svg-nft` branch to resolve the conflicts and get a brand new set of code based on the new component library. You can see the `loogies-svg-nft` branch of the project at [https://github.com/qiwihui/scaffold-eth.git](https://github.com/qiwihui/scaffold-eth.git). The following part of this article will be based on these codes for deployment and analysis.

0x01 Running and testing locally
--------------------------------

First let's run the project to see what we are going to analyze and implement.

### Running locally

First we run the project locally.

1.  clone the project and switch to the `loogies-svg-nft` branch:
    
        git clone https://github.com/qiwihui/scaffold-eth.git loogies-svg-nft
        cd loogies-svg-nft
        git checkout loogies-svg-nft
        
    
2.  install dependencies
    
3.  start the front end server
    
        yarn start
        
    
4.  run the local test network in a second terminal window
    
5.  deploy contract in a third terminal window
    
6.  go to `http://localhost:3000` in browser to see the app
    

### Testing locally

1.  First add the local network to the MetaMask wallet and switch to the local network;
    
    *   Network name: `Localhost 8545`
        
    *   Add RPC URL: `http://localhost:8545`
        
    *   Chain ID: `31337`
        
    *   Currency Symbol: `ETH`
        
2.  Create a new local wallet address;
    
3.  Copy the wallet address and send some test ETH to this address in the bottom left corner of the page;
    
4.  Click on `connect` in the top right corner of the page to connect to the wallet;
    
5.  Click on Mint button to mint;
    
6.  When the transaction succeed, you can see the newly minted NFT.
    

Now we begin our analysis of the project contract.

0x02 Loogies Contract Analysis
------------------------------

### NFT and ERC721

**NFT** (Non-Fungible Token), refers to non-homogenized tokens, corresponding to the ERC-721 standard on Ethereum. Generally in smart contracts, the definition of NFT contains `tokenId` and `tokenURI`, `tokenId` is unique ID for each NFT, and `tokenURI` for the metadata of the NFT is saved, which can be image URL, description, attributes, etc. If an NFT wants to display and sell in the NFT marketplace, the `tokenURI` content needs to correspond to the standards of the NFT marketplace. For example, in the NFT marketplace OpenSea's [metadata standards](https://docs.opensea.io/docs/metadata-standards), it is indicate attributes that need to be set for NFT display.

NFT metadata and presentation's connection in OpenSea

NFT metadata and presentation's connection in OpenSea

### Contract overview

[loogies-svg-nft project](https://github.com/scaffold-eth/scaffold-eth/tree/loogies-svg-nft)'s contract code is at ' `packages/hardhat/contracts/`, and contains three files.

    packages/hardhat/contracts/
    ├── HexStrings.sol
    ├── ToColor.sol
    └── YourCollectible.sol
    

*   `HexString.sol`: generate address string;
    
*   `ToColor.sol`: generate color value string;
    
*   `YourCollectible.sol`： `Loogies` NFT's contract file with functions of minting and metadata generating.
    

Contract architecture and methods:

    contract YourCollectible is ERC721, Ownable {
    
        // Constructor function
      constructor() public ERC721("Loogies", "LOOG") {
      }
      // Mint NFT
      function mintItem()
          public
          returns (uint256)
      {
        ...
      }
        // Get tokenURI from tokenId
      function tokenURI(uint256 id) public view override returns (string memory) {
        ...
      }
        // Generate SVG from tokenId
      function generateSVGofTokenById(uint256 id) internal view returns (string memory) {
        ...
      }
    
        // Render tokenId's svg code for graphics display
      function renderTokenById(uint256 id) public view returns (string memory) {
        ...
      }
    
    }
    

### Constructor function

    constructor() public ERC721("Loogies", "LOOG") {
        // RELEASE THE LOOGIES!
      }
    

Token name: `Loogies`

Token symbol: `LOOG`

The contract is inherited from OpenZeppelin's `ERC721.sol`, which is the basic contract code provided by OpenZeppelin and can be easily used by developers.

### Library functions

Contract applies different library functions for `uint256`, `uint160` and `bytes3` to extend features:

    // let uint256 have toHexString
    using Strings for uint256;
    // let uint160 have configurable toHexString
    using HexStrings for uint160;
    // let bytes3 have easy color representation
    using ToColor for bytes3;
    // counter feature
    using Counters for Counters.Counter;
    

### Mint time limit

The following code is for Mint time limit:

    uint256 mintDeadline = block.timestamp + 24 hours;
    
    function mintItem()
          public
          returns (uint256)
      {
          require( block.timestamp < mintDeadline, "DONE MINTING");
    ...
    

Contracts is mintable within 24 hours of deployment, and beyond that time an exception is raised. This mechanism is similar to pre-sales. Since this contract is relatively simple, no whitelist mechanism is used, and generally in practice, pre-sales and whitelists are used to control the NFTs issuing.

### Mint NFT

Minting NFT is to set two variables in contract:

*   `tokenId` with `owner`
    
*   `tokenId` with `tokenURI`
    

Let's look at the minting function `mintItem`:

    // Store each Loogies' attribute
    mapping (uint256 => bytes3) public color;
    mapping (uint256 => uint256) public chubbiness;
    
    ...
    
    function mintItem()
          public
          returns (uint256)
      {
          require( block.timestamp < mintDeadline, "DONE MINTING");
                // Self increment for _tokenIds to make sure _tokenIds is non-fungiable
          _tokenIds.increment();
    
          uint256 id = _tokenIds.current();
                // Binder minter and tokenId
          _mint(msg.sender, id);
                // Randomly generate tokenId's attributes
          bytes32 predictableRandom = keccak256(abi.encodePacked( blockhash(block.number-1), msg.sender, address(this), id ));
          color[id] = bytes2(predictableRandom[0]) | ( bytes2(predictableRandom[1]) >> 8 ) | ( bytes3(predictableRandom[2]) >> 16 );
          chubbiness[id] = 35+((55*uint256(uint8(predictableRandom[3])))/255);
    
          return id;
      }
    

Also:

*   `tokenId` self increments when minting to make sure each `tokenId` is only one;
    
*   `_mint` function binds `tokenId` with `owner`;
    
*   Each `tokenId`'s attribute is generated randomly:
    
    *   By pre-block's hash (`blockhash(block.number-1)`), `msg.sender`, contract address (`address(this)`) and `tokenId`'s generated hash `predictableRandom`;
        
    *   Calculate NFT color: doing bit operations to `predictableRandom`'s first three bits to get the color, color is represented by bytes3. `bytes2(predictableRandom[0])` is the lowest blue value, `( bytes2(predictableRandom[1]) >> 8 )` is the green value, `( bytes3(predictableRandom[2]) >> 16 )` is the red value;
        
    *   Calculate NFT chubbiness: `35+((55*uint256(uint8(predictableRandom[3])))/255);` ，`uint8(predictableRandom[3])` is between 0~255, so the minimum is 35, and the maximum is 35 + 55 = 90.
        

For example: When `color` is `0x4cc4c1`, `chubbiness` is 88, the NFT picture is:

loogies-1

### tokenURI function

Function `tokenURI` takes `tokenId` parameter, returns encoded metadata string:

    function tokenURI(uint256 id) public view override returns (string memory) {
                // check id if exist
          require(_exists(id), "not exist");
          string memory name = string(abi.encodePacked('Loogie #',id.toString()));
          string memory description = string(abi.encodePacked('This Loogie is the color #',color[id].toColor(),' with a chubbiness of ',uint2str(chubbiness[id]),'!!!'));
          // generate svg base64 for image
                string memory image = Base64.encode(bytes(generateSVGofTokenById(id)));
    
          return
              string(
                  abi.encodePacked(
                    'data:application/json;base64,',
                                    // encode metadata with base64
                    Base64.encode(
                        bytes(
                              abi.encodePacked(
                                  '{"name":"',
                                  name,
                                  '", "description":"',
                                  description,
                                  '", "external_url":"https://burnyboys.com/token/',
                                  id.toString(),
                                  '", "attributes": [{"trait_type": "color", "value": "#',
                                  color[id].toColor(),
                                  '"},{"trait_type": "chubbiness", "value": ',
                                  uint2str(chubbiness[id]),
                                  '}], "owner":"',
                                  (uint160(ownerOf(id))).toHexString(20),
                                  '", "image": "',
                                  'data:image/svg+xml;base64,',
                                  image,
                                  '"}'
                              )
                            )
                        )
                  )
              );
      }
    // Generated SVG string
    function generateSVGofTokenById(uint256 id) internal view returns (string memory) {
    ...
    }
    
    // Render image
    // Visibility is `public` to enable it being called by other contracts for composition.
    function renderTokenById(uint256 id) public view returns (string memory) {
    ...
    }
    

In this, `generateSVGofTokenById` function returns `tokenId`'s SVG string on its color and chubbiness, `renderTokenById` renders the token.

We see that the NFT needs to include:

*   name
    
*   description
    
*   external\_url
    
*   attributes
    
    *   color
        
    *   chubbiness
        
    *   owner
        
    *   image
        

We can get to know more about SVG by an example.When `tokenId` is 1, the `tokenURI` is:

    data:application/json;base64,eyJuYW1lIjoiTG9vZ2llICMxIiwiZGVzY3JpcHRpb24iOiJUaGlzIExvb2dpZSBpcyB0aGUgY29sb3IgIzRjYzRjMSB3aXRoIGEgY2h1YmJpbmVzcyBvZiA4OCEhISIsImV4dGVybmFsX3VybCI6Imh0dHBzOi8vYnVybnlib3lzLmNvbS90b2tlbi8xIiwiYXR0cmlidXRlcyI6W3sidHJhaXRfdHlwZSI6ImNvbG9yIiwidmFsdWUiOiIjNGNjNGMxIn0seyJ0cmFpdF90eXBlIjoiY2h1YmJpbmVzcyIsInZhbHVlIjo4OH1dLCJvd25lciI6IjB4MTY5ODQxYWEzMDI0Y2ZhNTcwMDI0ZWI3ZGQ2YmY1Zjc3NDA5MjA4OCIsImltYWdlIjoiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCM2FXUjBhRDBpTkRBd0lpQm9aV2xuYUhROUlqUXdNQ0lnZUcxc2JuTTlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TURBdmMzWm5JajQ4WnlCcFpEMGlaWGxsTVNJK1BHVnNiR2x3YzJVZ2MzUnliMnRsTFhkcFpIUm9QU0l6SWlCeWVUMGlNamt1TlNJZ2NuZzlJakk1TGpVaUlHbGtQU0p6ZG1kZk1TSWdZM2s5SWpFMU5DNDFJaUJqZUQwaU1UZ3hMalVpSUhOMGNtOXJaVDBpSXpBd01DSWdabWxzYkQwaUkyWm1aaUl2UGp4bGJHeHBjSE5sSUhKNVBTSXpMalVpSUhKNFBTSXlMalVpSUdsa1BTSnpkbWRmTXlJZ1kzazlJakUxTkM0MUlpQmplRDBpTVRjekxqVWlJSE4wY205clpTMTNhV1IwYUQwaU15SWdjM1J5YjJ0bFBTSWpNREF3SWlCbWFXeHNQU0lqTURBd01EQXdJaTgrUEM5blBqeG5JR2xrUFNKb1pXRmtJajQ4Wld4c2FYQnpaU0JtYVd4c1BTSWpOR05qTkdNeElpQnpkSEp2YTJVdGQybGtkR2c5SWpNaUlHTjRQU0l5TURRdU5TSWdZM2s5SWpJeE1TNDRNREEyTlNJZ2FXUTlJbk4yWjE4MUlpQnllRDBpT0RnaUlISjVQU0kxTVM0NE1EQTJOU0lnYzNSeWIydGxQU0lqTURBd0lpOCtQQzluUGp4bklHbGtQU0psZVdVeUlqNDhaV3hzYVhCelpTQnpkSEp2YTJVdGQybGtkR2c5SWpNaUlISjVQU0l5T1M0MUlpQnllRDBpTWprdU5TSWdhV1E5SW5OMloxOHlJaUJqZVQwaU1UWTRMalVpSUdONFBTSXlNRGt1TlNJZ2MzUnliMnRsUFNJak1EQXdJaUJtYVd4c1BTSWpabVptSWk4K1BHVnNiR2x3YzJVZ2NuazlJak11TlNJZ2NuZzlJak1pSUdsa1BTSnpkbWRmTkNJZ1kzazlJakUyT1M0MUlpQmplRDBpTWpBNElpQnpkSEp2YTJVdGQybGtkR2c5SWpNaUlHWnBiR3c5SWlNd01EQXdNREFpSUhOMGNtOXJaVDBpSXpBd01DSXZQand2Wno0OEwzTjJaejQ9In0=
    

Decode `data:application/json;base64,` with base64. Then the string can generate the following json (simplified version):

    {
      "name": "Loogie #1",
      "description": "This Loogie is the color #4cc4c1 with a chubbiness of 88!!!",
      "external_url": "https://burnyboys.com/token/1",
      "attributes": [
        {
          "trait_type": "color",
          "value": "#4cc4c1"
        },
        {
          "trait_type": "chubbiness",
          "value": 88
        }
      ],
      "owner": "0x169841aa3024cfa570024eb7dd6bf5f774092088",
      "image": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBpZD0iZXllMSI+PGVsbGlwc2Ugc3Ryb2tlLXdpZHRoPSIzIiByeT0iMjkuNSIgcng9IjI5LjUiIGlkPSJzdmdfMSIgY3k9IjE1NC41IiBjeD0iMTgxLjUiIHN0cm9rZT0iIzAwMCIgZmlsbD0iI2ZmZiIvPjxlbGxpcHNlIHJ5PSIzLjUiIHJ4PSIyLjUiIGlkPSJzdmdfMyIgY3k9IjE1NC41IiBjeD0iMTczLjUiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlPSIjMDAwIiBmaWxsPSIjMDAwMDAwIi8+PC9nPjxnIGlkPSJoZWFkIj48ZWxsaXBzZSBmaWxsPSIjNGNjNGMxIiBzdHJva2Utd2lkdGg9IjMiIGN4PSIyMDQuNSIgY3k9IjIxMS44MDA2NSIgaWQ9InN2Z181IiByeD0iODgiIHJ5PSI1MS44MDA2NSIgc3Ryb2tlPSIjMDAwIi8+PC9nPjxnIGlkPSJleWUyIj48ZWxsaXBzZSBzdHJva2Utd2lkdGg9IjMiIHJ5PSIyOS41IiByeD0iMjkuNSIgaWQ9InN2Z18yIiBjeT0iMTY4LjUiIGN4PSIyMDkuNSIgc3Ryb2tlPSIjMDAwIiBmaWxsPSIjZmZmIi8+PGVsbGlwc2Ugcnk9IjMuNSIgcng9IjMiIGlkPSJzdmdfNCIgY3k9IjE2OS41IiBjeD0iMjA4IiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9IiMwMDAwMDAiIHN0cm9rZT0iIzAwMCIvPjwvZz48L3N2Zz4="
    }
    

We decode `image` value and format it to get image SVG:

    <?xml version="1.0" encoding="UTF-8"?>
    <svg xmlns="http://www.w3.org/2000/svg" width="400" height="400">
       <g id="eye1">
          <ellipse stroke-width="3" ry="29.5" rx="29.5" id="svg_1" cy="154.5" cx="181.5" stroke="#000" fill="#fff" />
          <ellipse ry="3.5" rx="2.5" id="svg_3" cy="154.5" cx="173.5" stroke-width="3" stroke="#000" fill="#000000" />
       </g>
       <g id="head">
          <ellipse fill="#4cc4c1" stroke-width="3" cx="204.5" cy="211.80065" id="svg_5" rx="88" ry="51.80065" stroke="#000" />
       </g>
       <g id="eye2">
          <ellipse stroke-width="3" ry="29.5" rx="29.5" id="svg_2" cy="168.5" cx="209.5" stroke="#000" fill="#fff" />
          <ellipse ry="3.5" rx="3" id="svg_4" cy="169.5" cx="208" stroke-width="3" fill="#000000" stroke="#000" />
       </g>
    </svg>
    

SVG is a language defined in XML to describe two-dimensional vector and vector/raster graphics. It is widely used as it can be displayed in arbitrarily large sizes and without sacrificing image quality, it can be described using code, and it is easy to edit.

As can be seen from the above code combined with the following image, this SVG contains the following.

*   An XML declaration in the first line, indicating the version and encoding type, followed by the width and height of the SVG;
    
*   `eye1`: eye circle and black eyes drawn by two ellipses;
    
*   `head`: ellipse filled with `#4cc4c1` color as the body;
    
*   `eye2`: identical to `eye1`, in a different position;
    

`eye1`, `head` and `eye2` are superimposed in sequence to get the final shape.

loogies-1

### Helper function

1.  `uint2str` converts `uint` to string, like convert `123` to `'123'`
    

    function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
          if (_i == 0) {
              return "0";
          }
          uint j = _i;
                // uint length
          uint len;
          while (j != 0) {
              len++;
              j /= 10;
          }
          bytes memory bstr = new bytes(len);
          uint k = len;
          while (_i != 0) {
              k = k-1;
                    // _i single digits
              uint8 temp = (48 + uint8(_i - _i / 10 * 10));
              bytes1 b1 = bytes1(temp);
              bstr[k] = b1;
              _i /= 10;
          }
          return string(bstr);
      }
    

1.  `ToColor.sol` library: convert `byte3` type to color strings. Eg. `0x4cc4c1` as input and `'4cc4c1'` as output.
    

    library ToColor {
        bytes16 internal constant ALPHABET = '0123456789abcdef';
    
        function toColor(bytes3 value) internal pure returns (string memory) {
          bytes memory buffer = new bytes(6);
          for (uint256 i = 0; i < 3; i++) {
              buffer[i*2+1] = ALPHABET[uint8(value[i]) & 0xf];
              buffer[i*2] = ALPHABET[uint8(value[i]>>4) & 0xf];
          }
          return string(buffer);
        }
    }
    

1.  `HexStrings.sol` library: Mainly extract `uint` based on `length` index, just like extract 20 indices of address: `(*uint160*(ownerOf(id))).toHexString(20)`, this expression generates the coresponding `tokenId` owner address.
    

    library HexStrings {
        bytes16 internal constant ALPHABET = '0123456789abcdef';
    
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = '0';
            buffer[1] = 'x';
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = ALPHABET[value & 0xf];
                value >>= 4;
            }
            return string(buffer);
        }
    }
    

That's it for the contract code analysis.

Below we will provide a brief analysis of the front-end logic, and then we will implement the NFT minting and display functionality step by step. Before submitting front-end code, follow the steps below to add the functionality.

0x03 Front end logic analysis
-----------------------------

The project front-end files are in `packages/react-app`, and the locations of the files covered in the following articles will be found in this file.

> check App.jsx：
> 
> [https://github.com/qiwihui/scaffold-eth/blob/loogies-svg-nft/packages/react-app/src/App.jsx](https://github.com/qiwihui/scaffold-eth/blob/loogies-svg-nft/packages/react-app/src/App.jsx)

First we take a look at `src/App.jsx`. It is the main component for the project. We can view it in our editor.

Appjsx.png

It has sections and functions of:

*   `Header`: show title
    
*   \_`NetworkDisplay`: show network
    
*   \_`Menu`, `Switch`: show and switch menu
    
*   \_`ThemeSwitch`: switch light and dark theme
    
*   \_`Account`: show account info
    
*   And two `Row`s show gas and supported faucet in the lower left corner
    

Then we take a look at \_`NetworkDisplay` and `Account`'s logic design, and functions in `Menu`, and `Switch`.

### `NetworkDisplay`

Component is at: `src/components/NetworkDisplay.jsx`.

With those two functions:

1.  Show current network;
    
2.  If the current network is not supported, then show error. The network id for local network should be `31337`.
    

    function NetworkDisplay({
      NETWORKCHECK,
      localChainId,
      selectedChainId,
      targetNetwork,
      USE_NETWORK_SELECTOR,
      logoutOfWeb3Modal,
    }) {
      let networkDisplay = "";
      if (NETWORKCHECK && localChainId && selectedChainId && localChainId !== selectedChainId) {
        const networkSelected = NETWORK(selectedChainId);
        const networkLocal = NETWORK(localChainId);
        if (selectedChainId === 1337 && localChainId === 31337) {
                // show error network id
                ...
        } else {
                // show network is not correct
                ...
        }
      } else {
        networkDisplay = USE_NETWORK_SELECTOR ? null : (
          // show network name
          <div style={{ zIndex: -1, position: "absolute", right: 154, top: 28, padding: 16, color: targetNetwork.color }}>
            {targetNetwork.name}
          </div>
        );
      }
    
      console.log({ networkDisplay });
    
      return networkDisplay;
    }
    

### `Account`

Component is at `src/components/Account.jsx`.

With those three functions:

1.  show current address
    
2.  show address balance
    
3.  show `Connect` or `Logout`
    

When user clicks `Connect`, the front end calls `loadWeb3Modal`. This function is needed to connect with wallets such as MetaMask and listen to the wallet's \_ `chainChanged`, `accountsChanged` and `disconnect` events, i.e. to modify the display status when we switch networks, select connected accounts and cancel connections in the wallet.

    const loadWeb3Modal = useCallback(async () => {
      // connect wallet
      const provider = await web3Modal.connect();
      setInjectedProvider(new ethers.providers.Web3Provider(provider));
      // listen to network switch
      provider.on('chainChanged', (chainId) => {
        console.log(`chain changed to ${chainId}! updating providers`);
        setInjectedProvider(new ethers.providers.Web3Provider(provider));
      });
      // listen to changed accounts
      provider.on('accountsChanged', () => {
        console.log(`account changed!`);
        setInjectedProvider(new ethers.providers.Web3Provider(provider));
      });
      // Subscribe to session disconnection
      provider.on('disconnect', (code, reason) => {
        console.log(code, reason);
        logoutOfWeb3Modal();
      });
      // eslint-disable-next-line
    }, [setInjectedProvider]);
    

Similarly, in the case of a connected wallet, the user clicks `Logout` to call the `logoutOfWeb3Modal` function.

    const logoutOfWeb3Modal = async () => {
      // clear cached network provider and disconnect
      await web3Modal.clearCachedProvider();
      if (
        injectedProvider &&
        injectedProvider.provider &&
        typeof injectedProvider.provider.disconnect == 'function'
      ) {
        await injectedProvider.provider.disconnect();
      }
      setTimeout(() => {
        window.location.reload();
      }, 1);
    };
    

### `Menu`， `Switch`

These two correspond to the display menu and correspond to the switch menu functions, these menus include:

*   `App Home`: the project wants us to put the functions we need to implement in this menu, such as the minting and display functions of NFT that we will implement.
    
*   `Debug Contracts`: debug their own written contract functions, will be based on the contract's ABI file to show the state variables and functions that can be called.
    
*   `Hints`: hints for coding.
    
*   `ExampleUI`: sample UI, which can be used for programming purposes.
    
*   `Mainnet DAI`: the state of the contracts and available functions of the `DAI` network, with the same functionality as `Debug Contracts`.
    
*   `Subgraph`: Listening and querying for events in contracts using The Graph protocol.
    

### Debug output

`App.jsx` also has debugging output that prints the current page status, allowing you to view the current status variables in real time during development.

    //
    // 🧫 DEBUG 👨🏻‍🔬
    //
    useEffect(() => {
      if (
        DEBUG &&
        mainnetProvider &&
        address &&
        selectedChainId &&
        yourLocalBalance &&
        yourMainnetBalance &&
        readContracts &&
        writeContracts &&
        mainnetContracts
      ) {
        console.log(
          '_____________________________________ 🏗 scaffold-eth _____________________________________',
        );
        console.log('🌎 mainnetProvider', mainnetProvider);
        console.log('🏠 localChainId', localChainId);
        console.log('👩‍💼 selected address:', address);
        console.log('🕵🏻‍♂️ selectedChainId:', selectedChainId);
        console.log(
          '💵 yourLocalBalance',
          yourLocalBalance ? ethers.utils.formatEther(yourLocalBalance) : '...',
        );
        console.log(
          '💵 yourMainnetBalance',
          yourMainnetBalance ? ethers.utils.formatEther(yourMainnetBalance) : '...',
        );
        console.log('📝 readContracts', readContracts);
        console.log('🌍 DAI contract on mainnet:', mainnetContracts);
        console.log('💵 yourMainnetDAIBalance', myMainnetDAIBalance);
        console.log('🔐 writeContracts', writeContracts);
      }
    }, [
      mainnetProvider,
      address,
      selectedChainId,
      yourLocalBalance,
      yourMainnetBalance,
      readContracts,
      writeContracts,
      mainnetContracts,
      localChainId,
      myMainnetDAIBalance,
    ]);
    

After viewing the basic functions of the home page, we start implementing the two functions of NFT minting and displaying the NFT list.

0x04 NFT functions
------------------

We will implement the following three main components of functionality.

*   Minting NFTs;
    
*   Displaying the NFT list;
    
*   Displaying a list of NFT contract interfaces.
    

### Minting NFT

First we find the component corresponding to `App Home`, as you can see from the code below, which uses the `Home` component, located at `src/views/Home.jsx`.

        ...
        <Switch>
            <Route exact path="/">
              {/* pass in any web3 props to this Home component. For example, yourLocalBalance */}
              <Home yourLocalBalance={yourLocalBalance} readContracts={readContracts} />
            </Route>
        ....
    

Delete contents in `Home.jsx` and add the following Mint button:

    import React, { useState } from 'react';
    import { Button, Card, List } from 'antd';
    
    function Home({ isSigner, loadWeb3Modal, tx, writeContracts }) {
      return (
        <div>
          {/* Mint button */}
          <div
            style={{
              maxWidth: 820,
              margin: 'auto',
              marginTop: 32,
              paddingBottom: 32,
            }}
          >
            {isSigner ? (
              <Button
                type={'primary'}
                onClick={() => {
                  tx(writeContracts.YourCollectible.mintItem());
                }}
              >
                MINT
              </Button>
            ) : (
              <Button type={'primary'} onClick={loadWeb3Modal}>
                CONNECT WALLET
              </Button>
            )}
          </div>
        </div>
      );
    }
    
    export default Home;
    

Change `Switch`'s component into:

          ...
          <Switch>
            <Route exact path="/">
              {/* pass in any web3 props to this Home component. For example, yourLocalBalance */}
              <Home
                isSigner={userSigner}
                loadWeb3Modal={loadWeb3Modal}
                tx={tx}
                writeContracts={writeContracts}
              />
           ...
    

It should look like this:

mint-button

After clicking Mint, we can see that the transaction was successfully issued, at this point, although we successfully minted the NFT, we still need to add the list to show our NFT.

### Show NFT list

Add a list to display containing the NFT can be transferred to other addresses.

    import React, { useState } from 'react';
    import { Button, Card, List } from 'antd';
    import { useContractReader } from 'eth-hooks';
    import { Address, AddressInput } from '../components';
    
    function Home({
      isSigner,
      loadWeb3Modal,
      yourCollectibles,
      address,
      blockExplorer,
      mainnetProvider,
      tx,
      readContracts,
      writeContracts,
    }) {
      const [transferToAddresses, setTransferToAddresses] = useState({});
    
      return (
        <div>
          {/* Mint button */}
          ...
          {/* List */}
          <div style={{ width: 820, margin: 'auto', paddingBottom: 256 }}>
            <List
              bordered
              dataSource={yourCollectibles}
              renderItem={(item) => {
                const id = item.id.toNumber();
                console.log('IMAGE', item.image);
                return (
                  <List.Item key={id + '_' + item.uri + '_' + item.owner}>
                    <Card
                      title={
                        <div>
                          <span style={{ fontSize: 18, marginRight: 8 }}>
                            {item.name}
                          </span>
                        </div>
                      }
                    >
                      <a
                        href={
                          'https://opensea.io/assets/' +
                          (readContracts &&
                            readContracts.YourCollectible &&
                            readContracts.YourCollectible.address) +
                          '/' +
                          item.id
                        }
                        target='_blank'
                      >
                        <img src={item.image} />
                      </a>
                      <div>{item.description}</div>
                    </Card>
                    {/* NFT transfering */}
                    <div>
                      owner:{' '}
                      <Address
                        address={item.owner}
                        ensProvider={mainnetProvider}
                        blockExplorer={blockExplorer}
                        fontSize={16}
                      />
                      <AddressInput
                        ensProvider={mainnetProvider}
                        placeholder='transfer to address'
                        value={transferToAddresses[id]}
                        onChange={(newValue) => {
                          const update = {};
                          update[id] = newValue;
                          setTransferToAddresses({
                            ...transferToAddresses,
                            ...update,
                          });
                        }}
                      />
                      <Button
                        onClick={() => {
                          console.log('writeContracts', writeContracts);
                          tx(
                            writeContracts.YourCollectible.transferFrom(
                              address,
                              transferToAddresses[id],
                              id,
                            ),
                          );
                        }}
                      >
                        Transfer
                      </Button>
                    </div>
                  </List.Item>
                );
              }}
            />
          </div>
          {/* Display info */}
          <div
            style={{
              maxWidth: 820,
              margin: 'auto',
              marginTop: 32,
              paddingBottom: 256,
            }}
          >
            🛠 built with{' '}
            <a
              href='https://github.com/austintgriffith/scaffold-eth'
              target='_blank'
            >
              🏗 scaffold-eth
            </a>
            🍴 <a
              href='https://github.com/austintgriffith/scaffold-eth'
              target='_blank'
            >
              Fork this repo
            </a> and build a cool SVG NFT!
          </div>
        </div>
      );
    }
    
    export default Home;
    

Change the component into:

          ...
          <Switch>
            <Route exact path="/">
              {/* pass in any web3 props to this Home component. For example, yourLocalBalance */}
              <Home
                isSigner={userSigner}
                loadWeb3Modal={loadWeb3Modal}
                yourCollectibles={yourCollectibles}
                address={address}
                blockExplorer={blockExplorer}
                mainnetProvider={mainnetProvider}
                tx={tx}
                writeContracts={writeContracts}
                readContracts={readContracts}
              />
           ...
    

It should look like this:

nft-display-list

But we found that when we mint again, the list doesn't update, it's still the same, so we need to add an event listener to `App.jsx` that will refresh the list once we mint NFT:

    // Track current NFT numbers
    const balance = useContractReader(
      readContracts,
      'YourCollectible',
      'balanceOf',
      [address],
    );
    console.log('🤗 balance:', balance);
    
    const yourBalance = balance && balance.toNumber && balance.toNumber();
    const [yourCollectibles, setYourCollectibles] = useState();
    //
    // 🧠 This useEffect hook updates yourCollectibles when balance is changing.
    //
    useEffect(() => {
      const updateYourCollectibles = async () => {
        const collectibleUpdate = [];
        for (let tokenIndex = 0; tokenIndex < balance; tokenIndex++) {
          try {
            console.log('GEtting token index', tokenIndex);
            const tokenId = await readContracts.YourCollectible.tokenOfOwnerByIndex(
              address,
              tokenIndex,
            );
            console.log('tokenId', tokenId);
            const tokenURI = await readContracts.YourCollectible.tokenURI(tokenId);
            const jsonManifestString = atob(tokenURI.substring(29));
            console.log('jsonManifestString', jsonManifestString);
    
            try {
              const jsonManifest = JSON.parse(jsonManifestString);
              console.log('jsonManifest', jsonManifest);
              collectibleUpdate.push({
                id: tokenId,
                uri: tokenURI,
                owner: address,
                ...jsonManifest,
              });
            } catch (e) {
              console.log(e);
            }
          } catch (e) {
            console.log(e);
          }
        }
        setYourCollectibles(collectibleUpdate.reverse());
      };
      updateYourCollectibles();
    }, [address, yourBalance]);
    

At this point, when we mint again, the list is automatically updated to show the latest NFT.

### Show NFT contract ABI list

This function is relatively simple and requires only the following changes to the corresponding debug section:

          <Route exact path="/debug">
              {/*
                    🎛 this scaffolding is full of commonly used components
                    this <Contract/> component will automatically parse your ABI
                    and give you a form to interact with it locally
                */}
    
              <Contract
                name="YourCollectible"
                price={price}
                signer={userSigner}
                provider={localProvider}
                address={address}
                blockExplorer={blockExplorer}
                contractConfig={contractConfig}
              />
    

更新之后，可以在 `Debug Contracts` 菜单下看到合约的可以调用的函数。

After the update, you can see the callable functions of the contract under the `Debug Contracts` menu:

contract-funcs

At this point, we are done with a simple NFT minting and display DApp.

0x05 Conclusion
---------------

Through this project, we learnt and understood the following knowledge:

1.  the basic of NFT contracts and how to upload NFT in marketplaces such as OpenSea.
    
2.  how the DApp front-end connects to wallets such as MetaMask.
    
3.  how to call contract functions on the front end.
    

msfew

---

*Originally published on [Web3dAppDevCamp](https://paragraph.com/@apecoder/build-svg-nft-dapp-by-scaffold-eth-web3-0-dapp-dev-0x07)*
