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;
CodePudding user response:
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