Beau Chaseling
Beau Chaseling

Reputation: 93

Error uploading file to IPFS: HTTPError: project id required

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;

Upvotes: 7

Views: 9027

Answers (4)

Brijmohan Karadia
Brijmohan Karadia

Reputation: 447

I wasted my whole day to resolve this issue, update code in different ways, finally I create new app on infura and test code with new project id and secret, it is working for me :

JAVASCRIPT CODE :

const projectId = process.env.IPFS_PROVIDER_PROJECTID;
const projectSecret = process.env.IPFS_PROVIDER_PROJECTSECRET;
const ipfsClient = require(‘ipfs-http-client’);

async function main() {


 const auth = 'Basic ’ + Buffer.from(projectId + ‘:’ + projectSecret).toString(‘base64’);
    const client = await ipfsClient.create({
    host: ‘ipfs.infura.io’,
    port: 5001,
    protocol: ‘https’,
    apiPath: ‘/api/v0’,
    headers: {
    authorization: auth
    }
})

try {

    const file = await client.add(‘test.png’)
    console.log(file)

} catch (error) {
   console.log(error)
}
}
main();

CURL :

curl -X POST -F [email protected] -u “KKKKKKKEY:SSSSSSECRET” “https://ipfs.infura.io:5001/api/v0/add” --insecure -A “curl”

it seems not working with old details!

thanks

Upvotes: 0

Mukund Kinholkar
Mukund Kinholkar

Reputation: 31

Step 1 : Create a IPFS Infura project by putting card details

Step 2: Enable Dedicated Gateway give any subdomain name.

Step 3 : npm install --save buffer

Step 4 : Add below code in your project

import { Buffer } from 'buffer';
const ipfsClient = require('ipfs-http-client');

const projectId = '---Enter projectID from infura.io project---';
const projectSecret = '---Enter project secrate key from infura.io project---';
const auth =
'Basic ' + Buffer.from(projectId + ':' + projectSecret).toString('base64');

const client = ipfsClient.create({
  host: 'ipfs.infura.io',
  port: 5001,
  protocol: 'https',
  headers: {
    authorization: auth,
  },
});

Step 5: use similar dedicated Gateway to see your uploaded image https://(your gateway name).infura-ipfs.io/ipfs/${result.path} https://github.com/dappuniversity/nft_marketplace/issues/5#issuecomment-1219131315

Upvotes: 3

gotnoshoeson
gotnoshoeson

Reputation: 31

I was also having this problem this week, August 2022.

Authorization credentials are a recent requirement. Infura has articles on their site from 2020-2021 that do not include any mention of authorization credentials. They also have a recent article and video that do require auth to access IPFS through their API endpoint. Link below for deprecation of the public API, relevant info towards the end of the article.

https://blog.infura.io/post/introducing-ipfs-dedicated-gateways

Storing auth tokens in .env is always considered best practice so you don’t leak them when committing to your public git repo.

Unfortunately this is still not enough to secure your tokens if you deploy your app online. Lots of resources online to address this once you get there.

Upvotes: 2

maxeth
maxeth

Reputation: 1515

You are missing authorization headers that tell Infura who you are, and that you're authorized to access the API.

Here's the snippet from the docs:

const ipfsClient = require('ipfs-http-client');

const projectId = '1qmt...XXX';   // <---------- your Infura Project ID

const projectSecret = 'c920...XXX';  // <---------- your Infura Secret
// (for security concerns, consider saving these values in .env files)

const auth = 'Basic ' + Buffer.from(projectId + ':' + projectSecret).toString('base64');

const client = ipfsClient.create({
    host: 'ipfs.infura.io',
    port: 5001,
    protocol: 'https',
    headers: {
        authorization: auth,
    },
});

You just have to add an options object with your credentials to

const client = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0');

as shown above.

Source: https://docs.infura.io/infura/networks/ipfs/how-to/make-requests#ipfs-http-client

Upvotes: 6

Related Questions