Create Your Own NFT Gallery  with React and Chainbase API

Create Your Own NFT Gallery with React and Chainbase API

masterdai

masterdai

Devrel

Introduction

Non-Fungible Tokens (NFTs) have brought about a revolution in the digital art and collectibles market, signifying a substantial shift in our perception of ownership and value within the digital domain.

The purpose of this guide is to provide readers with instructions on crafting an NFT Profile, with a focus on the crucial technical framework and tools needed for the task. Through the utilization of Chainbase, a platform that provides a user-friendly API endpoint for NFT management, users can acquire a thorough comprehension of proficiently handling NFTs.

In this front-end demo, we need to utilize two Chainbase APIs and combine them to implement relatively complex functionalities. For instance, using ENS addresses for NFT queries, as well as conducting precise searches by connecting your wallet address and entering the NFT contract address. Moreover, it's possible to explore multi-chain NFTs by adjusting the scroll bar.

nftdemo.gif

https://nftdemo-lime.vercel.app/

Preparation

Technical Requirements: To create an NFT Profile, you will need several key tools and technologies. The primary requirement is a good understanding of React, as it will be the main library we use to build the front end of our application. Additionally, knowledge of Axios and Fetch is essential for handling HTTP requests. These libraries will enable us to interact with APIs effectively.

Setting Up the Environment:

  • Install Node.js: Ensure that you have Node.js installed on your system, as it is required for running React applications. You can download it from Node.js official website.
  • Set Up React: If you haven’t already, set up a new React project using Create React App for a quick start. You can do this by running the command
  • npx create-react-app my-nft-profile in your terminal.
  • Axios and Fetch: Install Axios by running npm install axios in your project directory. Fetch is built into modern browsers, so there's no need for additional installation if you're using a browser like Chrome or Firefox.
  • Additional Setups: Make sure your development environment includes a code editor (like Visual Studio Code), and familiarize yourself with basic command line operations, as they will be frequently used throughout the development process.

With these tools and technologies set up, you'll be ready to start building your NFT Profile.

Writing the Code for NFT Demo Tutorial

In this section, we will break down the coding process into detailed steps with code examples and explanations for each part of the NFT demo.

Step 1: BlockchainSelector Component

  • Purpose: This component allows users to select a blockchain network.
  • Code Explanation:
    • Custom Hook - useChainId: Manages the state of the selected blockchain network.

    • Component Setup: Uses the useChainId hook for network selection and updates the selected chain ID when changed.

      import React, { useState, useEffect, useMemo } from 'react';
      import PropTypes from 'prop-types';
      import styles from '../styles/Home.module.css';
      
      const capitalizeFirstLetter = (string) => {
        return string.charAt(0).toUpperCase() + string.slice(1);
      }
      
      const useChainId = (initialNetwork) => {
        const networkToChainId = useMemo(() => ({
          ethereum: 1,
          polygon: 137,
          bsc: 56,
          avalanche: 43114,
          arbitrum: 42161,
          optimism: 10,
          base: 8453,
          zksync: 324,
        }), []);
      
        const [selectedNetwork, setSelectedNetwork] = useState(initialNetwork);
        const selectedChainId = networkToChainId[selectedNetwork];
      
        return {
          selectedNetwork,
          setSelectedNetwork,
          selectedChainId,
          networkToChainId 
        };
      };
      
      const BlockchainSelector = ({ setSelectedChainId }) => {
        const { selectedNetwork, setSelectedNetwork, selectedChainId, networkToChainId } = useChainId('ethereum');
      
        useEffect(() => {
          setSelectedChainId(selectedChainId);
        }, [selectedChainId]);
      
        const handleChange = (event) => {
          setSelectedNetwork(event.target.value);
        };
      
        return (
          <div className={styles.blockchainSelectorContainer}>
            <label htmlFor="blockchainSelector" className={styles.blockchainSelectorLabel}>
              Select a Blockchain Network:
            </label>
            <select id="blockchainSelector" value={selectedNetwork} onChange={handleChange}>
              {Object.keys(networkToChainId).map((network) => (
                <option key={network} value={network}>
                  {capitalizeFirstLetter(network)}
                </option>
              ))}
            </select>
          </div>
        );
      };
      
      BlockchainSelector.propTypes = {
        setSelectedChainId: PropTypes.func.isRequired,
      };
      
      export default BlockchainSelector;
      

Step 2: NftCard Component

  • Purpose: Displays individual NFT details, including images and metadata.
  • Code Explanation:
    • Utility Functions:

      • convertToHttpUrl: Converts IPFS URLs for display.
      • handleImageError: Manages errors during image load.
    • Component Rendering: Shows NFT details like name, symbol, token ID, and image.

      import React, { useState, useEffect } from 'react';
      
      const convertToHttpUrl = (url) => {
        if (typeof url === 'string' && url.startsWith('ipfs://')) {
          return `https://cloudflare-ipfs.com/ipfs/${url.split('ipfs://')[1]}`;
        }
        return url;
      };
      
      const handleImageError = (e,setImageError) => {
        const ipfsPattern = /ipfs\/(Qm[1-9A-Za-z]{44}\/?.*)/;
        const match = e.target.src.match(ipfsPattern);
        if (match) {
          e.target.src = `https://cloudflare-ipfs.com/ipfs/${match[1]}`;
        }
        else {
          setImageError(true);
        }
      };
      
      const NftCard = ({ nft }) => {
        const maxTokenIdLength = 10;
        const truncatedTokenId = nft.token_id.length > maxTokenIdLength ? nft.token_id.substring(0, maxTokenIdLength) + '...' : nft.token_id;
        const imageUri = nft.image_uri || (nft.metadata ? nft.metadata.image : '');
        const imageUrl = convertToHttpUrl(imageUri);
        const [isVideo, setIsVideo] = useState(false);
        const [imageError, setImageError] = useState(false);
        //console.log('Image URL:', imageUrl); 
      
        useEffect(() => {
          fetch(imageUrl)
            .then((response) => {
              if (!response.ok) {
                throw new Error('Failed to fetch image');
              }
              const contentType = response.headers.get('Content-Type');
              if (contentType && contentType.startsWith('video')) {
                setIsVideo(true);
              }
            })
            .catch((error) => {
              // This is where errors can be logged or default images can be set, but it will not cause unhandled runtime errors.
              console.error('Error fetching image:', error);
            });
        }, [imageUrl]);
      
        return (
          <div style={{ width: '25%', padding: '10px', border: '1px solid #ccc' }}>
            {isVideo ? (
              <video src={imageUrl} controls style={{ width: '100%' }} />
            ): imageError ? (
              <div>NFT not shown</div> // If the image fails to load, display this message.
            ) 
             : (
                <img src={imageUrl} alt={nft.name} style={{ width: '100%' }} onError={(e) => handleImageError(e, setImageError)} />
              )}
            <h3>{nft.name}</h3>
            <p><strong>Symbol:</strong> {nft.symbol}</p>
            <p><strong>Token ID:</strong> <span title={nft.token_id}>{truncatedTokenId}</span></p>
            <p><strong>Contract Address:</strong> {nft.contract_address}</p>
          </div>
        );
      };
      
      export default NftCard;
      

Step 3: SearchBar Component

  • Purpose: Handles user inputs and MetaMask integration for NFT searches.
  • Code Explanation:
    • State Management: Manages user-entered and MetaMask-connected addresses.

    • MetaMask Functions: Includes connect and disconnect functionality.

    • Search Functionality: Triggers search on user input.

      import React, { useState } from 'react';
      
      const SearchBar = ({ onSearch }) => {
          const [inputAddress, setInputAddress] = useState(''); // The address manually entered by the user
          const [connectedAddress, setConnectedAddress] = useState(''); //  The address connected from MetaMask
          const [contractAddress, setContractAddress] = useState('');
      
          const connectMetaMask = async () => {
              if (!window.ethereum) {
                  alert('Please install MetaMask first.');
                  return;
              }
      
              let accounts;
              try {
                  accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
                  setConnectedAddress(accounts[0]);
                  setInputAddress(''); // address is cleared after connection
              } catch (error) {
                  alert('You rejected the request.');
              }
          };
      
          const disconnectWallet = () => {
              setConnectedAddress(''); // Clear the connected MetaMask address
              setInputAddress(''); //  Clear the input address at the same time
          };
      
          const handleSearch = () => {
              const addressToSearch = connectedAddress || inputAddress;
              onSearch(addressToSearch, contractAddress);
          };
      
          return (
              <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
                  {connectedAddress ? (
                      <>
                          <span>Connected Address: {connectedAddress}</span>
                          <button onClick={disconnectWallet}>Disconnect Wallet</button>
                      </>
                  ) : (
                      <>
                          <button onClick={connectMetaMask}>Connect MetaMask</button>
                          <input
                              type="text"
                              placeholder='Your ETH address or ENS name'
                              value={inputAddress}
                              onChange={(e) => setInputAddress(e.target.value)}
                              style={{ margin: '10px 0', width: '300px' }}
                          />
                      </>
                  )}
                  <input 
                      type="text"
                      placeholder='NFT Contract address (optional)'
                      value={contractAddress}
                      onChange={(e) => setContractAddress(e.target.value)}
                      style={{ margin: '10px 0', width: '300px' }}
                  />
                  <button onClick={handleSearch}>Search</button>
              </div>
          );
      };
      
      export default SearchBar;
      

Step 4: Home Component

  • Purpose: Serves as the main container of the NFT Profile application.
  • Code Explanation:
    • State Management: Manages the state for NFT data and selected blockchain ID.

    • Search Handling: Calls API to fetch NFT data based on search parameters.

    • UI Composition: Integrates all subcomponents for the application's UI.

      import React, { useState } from 'react';
      import axios from 'axios';
      import SearchBar from '../components/SearchBar';
      import NftCard from '../components/NftCard';
      import BlockchainSelector from '../components/BlockchainSelector';  
      
      const Home = () => {
        const [nftData, setNftData] = useState(null);
        const [selectedChainId, setSelectedChainId] = useState('');
      
        const handleSearch = async (address, contractAddress) => {
          setNftData(null);
          try {
            const response = await axios.get(`/api/chainbaseApi?address=${address}&contract_address=${contractAddress}&chain_id=${selectedChainId}`);
            setNftData(response.data.data);
          } catch (error) {
            console.error(error);
          }
        };
      
        return (
          <div style={{ textAlign: 'center' }}>
            <img src="/logo.png" alt="Chainbase Logo" style={{ marginTop: '20px', width: '300px', height: '50px' }} />
            
            <BlockchainSelector setSelectedChainId={setSelectedChainId} />
            <SearchBar onSearch={handleSearch} />
      
            {nftData && nftData.length > 0 ? (
              <div>
                <h2>NFTs for Address: {nftData[0].owner}</h2>
                <div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center' }}>
                  {nftData.map((nft) => (
                    <NftCard key={`${nft.contract_address}-${nft.token_id}`} nft={nft} />
                  ))}
                </div>
              </div>
            ) : (
              <div></div>
            )}
          </div>
        );
      };
      
      export default Home;
      

Each component in this NFT demo is designed to handle specific functionalities, ensuring a clear separation of concerns. This modular approach helps in maintaining and scaling the application efficiently.

Step 5: Detailed Explanation of Chainbase API Integration with ENS API Usage

In this step, we dive deeper into the crucial aspect of integrating Chainbase API in our NFT demo, with a particular focus on the usage of the ENS API for resolving Ethereum addresses.

  • API Handler Setup:
    • Purpose: This server-side function acts as an intermediary, handling requests from the front-end, processing them, and communicating with the Chainbase API to fetch NFT data.
  • Code Breakdown:
    • Initial Setup and CORS Configuration: Setting up the handler and configuring CORS to allow cross-origin requests.

      export default async function handler(req, res) {
        // Logging and CORS setup
      }
      
      
    • Handling ENS Names:

      • ENS API Call: If the provided address is an ENS name (ending with '.eth'), the code calls the Chainbase ENS API to resolve it to a standard Ethereum address.

        if (address.endsWith('.eth')) {
          // Fetching from Chainbase ENS API
          // Convert ENS to Ethereum address
        }
        
        
    • Building the Chainbase API Request URL:

      • After resolving the ENS name to an Ethereum address (or using the directly provided Ethereum address), the function constructs the URL for the Chainbase API request.

      • It appends parameters like chain_id, address, and contract_address to the base URL.

        const baseUrl = `https://api.chainbase.online/v1/account/nfts?chain_id=${chainId}`;
        let targetUrl = `${baseUrl}&address=${address}&limit=100`;
        if (query.contract_address) {
          targetUrl += `&contract_address=${query.contract_address}`;
        }
        
        
    • Fetching Data from Chainbase API:

      • API Request: The code uses the fetch function to make a GET request to the Chainbase API using the constructed URL.

      • Handling the Response: The response from Chainbase API is then parsed and sent back to the front-end.

        const backendResponse = await fetch(targetUrl, {
          headers: {
            accept: 'application/json',
            'x-api-key': process.env.CHAINBASE_API_KEY,
          },
        });
        // Processing and sending the response to the front-end
        
        
    • Error Handling: The function includes error handling to manage any issues that arise during the API call, ensuring the front-end receives an appropriate response even in case of failure.

      try {
        // Chainbase API request and response handling
      } catch (error) {
        res.status(500).json({ error: 'Error' });
      }
      
      

This handler function is key to the functionality of the NFT demo, as it seamlessly integrates ENS name resolution and NFT data retrieval from the Chainbase API. Understanding this function provides a comprehensive view of how backend logic can be implemented to support the front-end features of a web application.

5. Testing and Deployment

Testing Process

Before deploying your NFT Profile app, it's crucial to thoroughly test it in a local development environment.

  1. Start the Local Development Server:
    • Run npm run dev in your terminal. This command starts the development server and allows you to access your application locally.
    • By default, the application should be accessible at http://localhost:3000 or a similar URL provided in your terminal.
  2. Testing Functionality:
    • Navigate through the application in your web browser to ensure all components are rendering correctly.
    • Test the search functionality and blockchain network selection by entering different addresses, ENS names, and contract addresses. Ensure that the app correctly displays the NFT data or handles errors appropriately.
  3. Checking Console Logs:
    • Monitor the console for any errors or warnings that might indicate issues in your code.
    • Verify that the API calls are being made correctly and that responses are being handled as expected.

Deployment Guide

Once your application is tested and ready, you can deploy it to a cloud platform such as Vercel for public access.

  1. Setting Up Deployment:
    • If you haven’t already, sign up for an account on Vercel.
    • Install Vercel CLI by running npm install -g vercel or follow the instructions in the Vercel documentation.
  2. Preparing Your Project for Deployment:
    • Ensure your project's package.json file is correctly set up with a start script.
    • Add any environment variables (like your Chainbase API key) to Vercel. These can be added in the project settings on the Vercel dashboard.
  3. Deploying to Vercel:
    • Navigate to your project directory in the terminal.
    • Run the command vercel to initiate the deployment. Vercel CLI will guide you through the process.
    • Once deployed, Vercel will provide a URL to access your deployed application.
  4. Verifying Deployment:
    • Visit the provided URL to ensure that your NFT Profile app is accessible and functioning correctly in the live environment.

Deploying to a platform like Vercel offers the benefits of ease of use, scalability, and integration with various frontend frameworks. Remember to keep your API keys and sensitive data secure and avoid exposing them in your client-side code.

6. Conclusion

Summary

In this tutorial, we've covered the key steps to create an NFT Profile using modern web technologies. We started by setting up our environment, using tools like React and Axios. We then dove into the coding process, where we developed several components such as BlockchainSelector, NftCard, and SearchBar, each playing a vital role in our application.

We also explored how to integrate Chainbase API to fetch NFT data, including handling Ethereum Name Service (ENS) addresses. The importance of testing and deployment was emphasized, ensuring that our application is robust and accessible.

The creation of an NFT Profile not only highlights the practical applications of blockchain technology but also showcases the potential of NFTs in representing digital ownership and identity in the evolving web space.

Further Reading and Resources

To deepen your understanding of the technologies and concepts used in this tutorial, consider exploring the following resources:

  1. IPFS (InterPlanetary File System):
    • Understand how IPFS works and why it's crucial for handling decentralized file storage in the context of NFTs.
    • IPFS Documentation
  2. Handling Different Media Types in NFTs:
    • Learn more about identifying and handling various media types like GIFs or videos within NFT metadata.
    • Understanding NFT Metadata
  3. Advanced Blockchain Concepts:
    • Dive deeper into blockchain technology and its applications, especially in the context of NFTs and digital ownership.
    • Blockchain Basics
  4. React and Next.js Documentation:
  5. Vercel Deployment Guide:

By following this tutorial and exploring these additional resources, you'll be well-equipped to venture further into the world of NFTs and blockchain technology, unlocking new possibilities in web development and digital asset creation.

About Chainbase

Chainbase is an open Web3 data infrastructure for accessing, organizing, and analyzing on-chain data at scale. It helps people to better utilize on-chain data by combining rich datasets with open computing technologies in one data platform. Chainbase’s ultimate goal is to increase data freedom in the crypto.

More than 5,000 developers actively interact with our platform and over 200Mn data requests per day as their data backend and integrate chainbase into their main workflow.  Additionally, we are working with ~10 top-tier public chains as first-tier validators and managing over US $500Mn tokens non-custodially as a validator provider. Find out more at: chainbase.com

Want to learn more about Chainbase?

Sign up for a free account, and Check out our documentation.

Website|Blog|Twitter|Discord|Link3