I created a page using Nextjs that enables users to create single ERC-721 tokens in two ways:

  • from an existing URL
  • uploading to IPFS from their local machine

This code is an extension of the most excellent tutorial on NFT marketplace development by Nader Dabit, which you can find here. I cannot recommend that enough for giving you the basics of NFT marketplace development.  

How the page works

The page is a form, allowing users to specify the name, description and price of their NFT before uploading it directly (the file ends up on IPFS) or providing their own IPFS URL. Once users have specified the details, they click on the form’s button to launch two transactions on the blockchain: one to create the NFT and another to put it on the market for sale.

Let’s look at extracts of the code for the page:

We need to set up a client that can interact with IPFS. Using the ipfs-http-client library in conjunction with the public endpoint provided by Infura for IPFS, we implement this:

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

Next, we need to import artefacts describing our smart contracts on the blockchain. I’ve done two things to facilitate this:

  • place my Market.sol and NFT.sol contracts on the blockchain (in this case, Polygon’s Mumbai testnet)
  • Compile the contract using Hardhat, which creates my ABIs

Our import statement for these is as follows:

import { nftaddress, nftmarketaddress } from '../config';

import NFT from '../artifacts/Contracts/NFT.sol/NFT.json';
import Market from '../artifacts/Contracts/Market.sol/Market.json';

We’re exporting one function called CreateItem(). We have useState() hooks to manage the file URL and the form input details here. Our form is pretty basic, with just price, name, and description parameters. We also use next/router to put our users back to the home screen after offering the NFT for sale.

const [fileUrl, setFileUrl] = useState(null);
const [formInput, updateFormInput] = useState({
price: '',
name: '',
description: '',
});
const router = useRouter();

 

Adding name, description and price

In the JSX, we create three basic inputs. Note the TailwindCSS styling. This is such an easy framework to use. I definitely recommend you investigate it.

<input
placeholder='Asset Name'
className='mt-8 border rounded p-4'
onChange={(e) =>
updateFormInput({ ...formInput, name: e.target.value })
}
/>
<textarea
placeholder='Asset Description'
className='mt-2 border rounded p-4'
onChange={(e) =>
updateFormInput({ ...formInput, description: e.target.value })
}
/>
<input
placeholder='Asset Price in Matic'
className='mt-2 border rounded p-4'
onChange={(e) =>
updateFormInput({ ...formInput, price: e.target.value })
}
/>

 

Creating the IPFS URL

We have two functions to either create or accept IPFS urls. In the form, we have a standard file upload input which will launch this function when invoked:

async function onFileUpload(e) {
const file = e.target.files[0];
try {
const added = await client.add(file, {
progress: (prog) => console.log(`uploaded: ${prog} bytes`),
});
const url = `https://ipfs.infura.io/ipfs/${added.path}`;
setFileUrl(url);
} catch (e) {
console.log(e);
}
}

Tracking progress in the console log, it will eventually resolve a URL and store it using setState. Alternately to this method, the user can simply paste in an existing URL which will be added to state as below:

async function onAddIPFSUrl(e) {
try {
setFileUrl(e.target.value);
} catch (e) {
console.log(e);
}
}

Now clearly if we’re using one of these methods to upload our NFT image, we don’t want to clutter the form with the other. In the JSX, we’ll need a little bit of trickery to display only one when the other isn’t in use. We’ll also need a way to reset the form if we want to change the file:

{!fileUrl && (
<input
    type='file'
    name='Asset'
    className='my-4'
    onChange={onFileUpload}
  />
)}

{fileUrl && (
<img className='rounted mt-4' width='350' src={fileUrl} />
)}
{!fileUrl && (
<input
    placeholder='Add your IPFS URL'
    className='mt-2 border rounded p-4'
    onChange={onAddIPFSUrl}
  />
)}
{fileUrl && (
<button className='mt-2 border rounded p-4' onClick={formReset}>
Reset
</button>
)}

 

Part II: Putting it on the blockchain

So, we have the basics for uploading our NFT image and adding basic information to sell it. Now we need to create the NFT on-chain and then add it as a sale to our Market smart contract. These will be coming in Part II.