1

Currently making a rudimentary DApp for posting chats, similar to twitter except built on a smart contract. I am using hardhat and running my app on localhost. When creating a profile I want users to be able to upload a profile picture, however currently whenever I try to do so I get the following error:

POST https://ipfs.infura.io:5001/api/v0/add?stream-channels=true&progress=false 401 (Unauthorized)

Accompanied by the error message:

Error uploading file:  HTTPError: project id required

    at Object.errorHandler [as handleError] (core.js?edc8:103:1)
    at async Client.fetch (http.js?8f3e:149:1)
    at async addAll (add-all.js?93f2:36:1)
    at async last (index.js?7e49:13:1)
    at async Object.add (add.js?6672:22:1)

The console says that the error is occurring in this function:

    const uploadToInfura = async (file) => {
      try {
        const added = await client.add({ content: file });
  
        const url = `https://ipfs.infura.io/ipfs/${added.path}`;
  
        setFileUrl(url);
      } catch (error) {
        console.log('Error uploading file: ', error);
      }
    };

I will attach the entire code for this page below, if you can please let me know what I need to fix in order to get this error to stop happening. Any other tips on what I could improve in general would also be appreciated :)

import { useState, useEffect, useContext, useCallback, useMemo } from 'react'
import { useRouter } from 'next/router';
import { useDropzone } from 'react-dropzone';
import Image from 'next/image';
import { useTheme } from 'next-themes';
import { ethers } from "ethers";
import Web3Modal from 'web3modal';

import { Input, Button, Banner, SearchBar, PostCard, PostCardNFT, SmallInput } from '../components';
import images from '../assets';
import DecentratwitterAbi from './contractsData/decentratwitter.json';
import DecentratwitterAddress from './contractsData/decentratwitter-address.json';
import { Home } from './index'

import { create as ipfsHttpClient } from 'ipfs-http-client';
const client = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0');

const Profile = () => {
    const [profile, setProfile] = useState('');
    const [posts, setPosts] = useState('');
    const [nfts, setNfts] = useState('');
    const [fileUrl, setFileUrl] = useState(null);
    const [isloading, setIsLoading] = useState(true);
    const { theme } = useTheme();
    const [files] = useState([]);
    const [formInput, updateFormInput] = useState({ username: '' });
    const router = useRouter();
    
    
    const uploadToInfura = async (file) => {
      try {
        const added = await client.add({ content: file });
  
        const url = `https://ipfs.infura.io/ipfs/${added.path}`;
  
        setFileUrl(url);
      } catch (error) {
        console.log('Error uploading file: ', error);
      }
    };


    const createProfile = async () => {
      const web3Modal = new Web3Modal();
      const connection = await web3Modal.connect();
      const provider = new ethers.providers.Web3Provider(connection);
      const signer = provider.getSigner();
      const contract = new ethers.Contract(
        DecentratwitterAddress.address,
        DecentratwitterAbi.abi,
        signer
      );

      const { username } = formInput;
      if (!username || !fileUrl) return;
      /* first, upload to IPFS */
      const data = JSON.stringify({ username, avatar: fileUrl });
      try {
        const added = await client.add(data);
        const url = `https://ipfs.infura.io/ipfs/${added.path}`;
        /* after file is uploaded to IPFS, pass the URL to save it on Polygon */
        await contract.mint(url);
        fetchMyNFTs();
      } catch (error) {
        console.log('Error uploading file: ', error);
      }
    };


    const fetchProfile = async (nfts) => {
      const web3Modal = new Web3Modal();
      const connection = await web3Modal.connect();
      const provider = new ethers.providers.Web3Provider(connection);
      const signer = provider.getSigner();
      const contract = new ethers.Contract(
        DecentratwitterAddress.address,
        DecentratwitterAbi.abi,
        signer
      );

      const address = await contract.signer.getAddress();
      const id = await contract.profiles(address);
      const profile = nfts.find((i) => i.id.toString() === id.toString());
      setProfile(profile);
      setIsLoading(false);
    };

    const loadPosts = async () => {
      const web3Modal = new Web3Modal();
      const connection = await web3Modal.connect();
      const provider = new ethers.providers.Web3Provider(connection);
      const signer = provider.getSigner();
      const contract = new ethers.Contract(
        DecentratwitterAddress.address,
        DecentratwitterAbi.abi,
        signer
      );
        // Get user's address
        let address = await contract.signer.getAddress()
        setAddress(address)
        // Check if user owns an nft
        // and if they do set profile to true
        const balance = await contract.balanceOf(address)
        setHasProfile(() => balance > 0)
        // Get all posts
        let results = await contract.getAllPosts()
        // Fetch metadata of each post and add that to post object.
        let posts = await Promise.all(results.map(async i => {
            // use hash to fetch the post's metadata stored on ipfs 
            let response = await fetch(`https://ipfs.infura.io/ipfs/${i.hash}`)
            const metadataPost = await response.json()
            // get authors nft profile
            const nftId = await contract.profiles(i.author)
            // get uri url of nft profile
            const uri = await contract.tokenURI(nftId)
            // fetch nft profile metadata
            response = await fetch(uri)
            const metadataProfile = await response.json()
            // define author object
            const author = {
                address: i.author,
                username: metadataProfile.username,
                avatar: metadataProfile.avatar
            }
            // define post object
            let post = {
                id: i.id,
                content: metadataPost.post,
                tipAmount: i.tipAmount,
                author
            }
            return post
        }))
        posts = posts.sort((a, b) => b.tipAmount - a.tipAmount)
        // Sort posts from most tipped to least tipped. 
        setPosts(posts)
        setLoading(false)
    };

    const fetchMyNFTs = async () => {
      const web3Modal = new Web3Modal();
      const connection = await web3Modal.connect();
      const provider = new ethers.providers.Web3Provider(connection);
      const signer = provider.getSigner();
      const contract = new ethers.Contract(
        DecentratwitterAddress.address,
        DecentratwitterAbi.abi,
        signer
      );

      const results = await contract.getMyNfts();
      let nfts = await Promise.all(results.map(async i => {
        const uri = await contract.tokenURI(i);
        const response = await fetch(uri);
        const metadata = await response.json();
        return ({
          id: i,
          username: metadata.username,
          avatar: metadata.avatar
        });
      }));
      setNfts(nfts);
      fetchProfile(nfts);
    };

    const tip = async (post) => {

      const web3Modal = new Web3Modal();
      const connection = await web3Modal.connect();
      const provider = new ethers.providers.Web3Provider(connection);
      const signer = provider.getSigner();
      const contract = new ethers.Contract(
        DecentratwitterAddress.address,
        DecentratwitterAbi.abi,
        signer
      );
        // tip post owner
        await (await contract.tipPostOwner(post.id, { value: ethers.utils.parseEther(tipAmount) })).wait()
        fetchMyNFTs()
    }

  useEffect(() => {
    fetchMyNFTs()
      .then((nfts) => {
        setNfts(nfts);
        setIsLoading(false);
      });
  }, []);

  const onDrop = useCallback(async (acceptedFile) => {
    await uploadToInfura(acceptedFile[0]);
  }, []);

  const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject } = useDropzone({
    onDrop,
    accept: 'image/*',
    maxSize: 5000000,
  });

  const fileStyle = useMemo(
    () => (
      `dark:bg-nft-black-1 bg-white border dark:border-white border-nft-gray-2 flex flex-col items-center p-5 rounded-sm border-dashed  
       ${isDragActive ? ' border-file-active ' : ''} 
       ${isDragAccept ? ' border-file-accept ' : ''} 
       ${isDragReject ? ' border-file-reject ' : ''}`),
    [isDragActive, isDragReject, isDragAccept],
  );



  return (

    
    <div className="w-full flex justify-start items-center flex-col min-h-screen">
      <div className="w-full flexCenter flex-col">
      <Banner
          name={
            profile ? (
              <div>{profile.username}</div>
            ) : (
              "No profile, please create one"
            )
          }
          childStyles="text-center mb-4"
          parentStyles="h-80 justify-center"
        />

        <div className="flexCenter flex-col -mt-20 z-0">
          <div className="flexCenter w-40 h-40 sm:w-36 sm:h-36 p-1 bg-nft-black-2 rounded-full">
            {profile ? (
              <Image src={profile.avatar} className="rounded-full object-cover" objectFit="cover" width="200%" height="200%" alt='avatar' />
            ) : (
              "No Profile"
            )
            }
          </div>
          <p className="font-poppins dark:text-white text-nft-black-1 font-semibold text-2xl mt-6"></p>
        </div>
      </div>
      

      
      {profile ? (
        <div>
        {nfts ? (
          nfts.map((nft, key) => {
          <div key={key} >
            <PostCardNFT 
              image={<Image src={nft.author.avatar} layout="fixed" width='60' height='40' alt='post' className='rounded-[6px]' />}
              content={nft.content}
              tipAmount={ethers.utils.formatEther(post.tipAmount)}
              address={shortenAddress(post.author.address)}
              tipInput={
                <div className='pb-2'>
                  <SmallInput 
                    inputType='number'
                    title='Tip'
                    placeholder='ETH'
                    handleClick={(e) => setTipAmount(e.target.value)}
                  />
                </div>
              }
              button={
                <Button 
                  btnName="Tip"
                  btnType="primary"
                  classStyles="rounded-xl"
                  handleClick={() => tip(post)}
                />
              }
            />
          </div>
          })
        ) : (
          <div className="text-2xl font-bold pt-20">
            No Posts, Create One ...
          </div>
        )}
        </div>
      ) : (
        <div className="w-full px-20">
          <Input
            inputType="input"
            title="Username"
            placeholder="Input Username"
            handleClick={(e) => updateFormInput({ ...formInput, username: e.target.value })} 
          />

        <div className="mt-16">
          <p className="font-poppins dark:text-white text-nft-black-1 font-semibold text-xl">Profile Avatar</p>
          <div className="mt-4">
            <div {...getRootProps()} className={fileStyle}>
            <>
              {fileUrl ? (
                <div>
                  <Image
                    src={fileUrl}
                    className="object-cover"
                    objectFit="cover"
                    width="200%"
                    height="200%"
                    alt="Asset_file" />
                  </div>
              ) : (
                <div>
                  <input {...getInputProps()} /><div className="flexCenter flex-col text-center">
                    <p className="font-poppins dark:text-white text-nft-black-1 font-semibold text-xl">JPG, PNG, GIF, SVG, WEBM, MP3, MP4. Max 100mb.</p>
                      <div className="my-12 w-full flex justify-center">
                        <Image
                          src={images.upload}
                          width={100}
                          height={100}
                          objectFit="contain"
                          alt="file upload"
                          className={theme === 'light' ? 'filter invert' : undefined}
                        />
                      </div>  
                    <p className="font-poppins dark:text-white text-nft-black-1 font-semibold text-sm">Drag and Drop File</p>
                    <p className="font-poppins dark:text-white text-nft-black-1 font-semibold text-sm mt-2">Or browse media on your device</p>
                  </div>
                </div>
              )}
            </>
            </div>
          </div>
        </div>
            
          <div className="mt-7 w-full flex justify-end">
            <Button
              btnName="Mint Profile"
              btnType="primary"
              classStyles="rounded-xl"
              handleClick={createProfile} />
          </div>
        </div>
      )}
    </div>
  );
};

export default Profile;

1 Answer 1

1

The Infura IPFS public gateway was deprecated on August 10, 2022

const ipfsClient = require(‘ipfs-http-client’);
const projectId = ’XXX...XXX;
const projectSecret = ‘XXX...XXX’;
const auth = ‘Basic ’ + Buffer.from(projectId + ‘:’ + projectSecret).toString(‘base64’);
const client = ipfsClient.create({
    host: ‘ipfs-infura.io’,
    port: 5001,
    protocol: ‘https’,
    headers: {
        authorization: auth,
    },
});

https://blog.infura.io/post/ipfs-public-api-and-gateway-deprecation

4
  • 1
    If you replace "ipfs.infura.io" with "infura-ipfs.io", your answer will be the most recent solution to the problem. Nov 17, 2022 at 18:59
  • @SinaRahimi thank you!
    – Yilmaz
    Dec 15, 2022 at 22:50
  • 1
    Can you upvote my comment plz? Jan 2, 2023 at 21:12
  • @SinaRahimi I did already :) i did the latest too :)
    – Yilmaz
    Jan 2, 2023 at 21:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.