To be successful in this guide, you must have the following:
- Installed Node.js
- Metamask extension in the browser
Project setup
- Creating a new project
npx create-next-app digital-marketplace
2. Сhange into new directory and install dependencies:
cd digital-marketplace
npm install ethers hardhat @nomiclabs/hardhat-waffle \
ethereum-waffle chai @nomiclabs/hardhat-ethers \
web3modal @openzeppelin/contracts ipfs-http-client@50.1.2 \
axios
Setting up Tailwind CSS
We will use Tailwind CSS, because I like it.
It makes it easier and faster to add styles than classic CSS.
Install the Tailwind dependencies:
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
Configure your template content paths in tailwind.config.js:
/* tailwind.config.js */
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Delete code in styles/globals.css and change it with the following:
@tailwind base;
@tailwind components;
@tailwind utilities;
Setting up Hardhat
Initialize a new Hardhat development environment:
npx hardhat
Now you see several new files and folders in the root of your project
To begin with, we will change hardhat.config.js:
require("@nomiclabs/hardhat-waffle")
const fs = require('fs')
const privateKey = fs.readFileSync(".secret").toString().trim() || "01234567890123456789"
module.exports = {
defaultNetwork: "hardhat",
networks: {
hardhat: {
chainId: 1337
},
Moonbeam: {
url: "https://rpc.api.moonbeam.network",
accounts: [privateKey]
},
Moonriver: {
url: "https://rpc.api.moonriver.moonbeam.network",
accounts: [privateKey]
},
MoonbaseAlpha: {
url: "https://rpc.api.moonbase.moonbeam.network",
accounts: [privateKey]
}
},
solidity: {
version: "0.8.4",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
}
}
Next, create a file named .secret at the root of your project. You need to fill it with wallet private key that will hold some tokens.
Important: Don’t forget to add .secret to .gitignore so as not to lose your wallet
Smart Contracts
So, here we need only 1 smart contract, but using its example we will learn how to code, test, and deploy smart contracts.
Create new file in the contracts directory named NFT.sol with the following code:
// contracts/NFT.sol
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "hardhat/console.sol";
contract NFT is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
constructor() ERC721("Moonbeam Test NFT", "MTN") {
}
function createToken(string memory tokenURI) public {
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
}
}
So, let’s figure out what’s going on here.
First, we import modules from OppenZeppelin, which you can learn more about by following the link.
Next, we declare the contract and various parameters, followed by the constructor, which in this case sets the name of our NFT collection
Next come the functions (in this case one). In it, we increase the number of our tokens, after which we mint token and give it an image.
And that’s it, the smart contract for mint NFT is ready!
Be sure to try to write it yourself, and not just copy it
Testing smart contracts
So, open test/sample-test.js and paste the following code:
/* test/sample-test.js */
describe("NFTMarket", function() {
it("Should create and execute market sales", async function() {
/* deploy contract */
const NFT = await ethers.getContractFactory("NFT")
const nft = await NFT.deploy()
await nft.deployed()
const nftContractAddress = nft.address
/*get test minter address*/
const [_, minterAddress] = await ethers.getSigners()
/* mint tokens */
const txid1 = await nft.connect(minterAddress).createToken("https://www.mytokenlocation.com")
const txid1 = await nft.connect(minterAddress).createToken("https://www.mytokenlocation2.com")
console.log('NFT Contract Address: ', nftContractAddress)
console.log('txid1: ', txid1)
console.log('txid2: ', txid2)
})
})
If everything is fine, then your contract is working
Building the front end
Now the contract is working and it’s time to do some UI
Open folder named pages and delete everything except _app.js and index.js
_app.js will just be a piece at the top of the page, which may become more difficult in the future if you add new sections to your site.
In _app.js insert the following code:
import '../styles/globals.css'function MyApp({ Component, pageProps }) {
return (
<div>
<nav className="border-b p-6">
<p className="text-4xl font-bold">Moonbeam NFT</p>
</nav>
<Component {...pageProps} />
</div>
)
}export default MyApp
Next, paste the following code into the index.js. Below we will analyze what is needed and why:
import { useState } from 'react'
import { ethers } from 'ethers'
import { create as ipfsHttpClient } from 'ipfs-http-client'
import { useRouter } from 'next/router'
import Web3Modal from 'web3modal'
const client = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0')
import {
nftaddress
} from '../config'
import NFT from '../artifacts/contracts/NFT.sol/NFT.json'
export default function CreateItem() {
const [fileUrl, setFileUrl] = useState(null)
const [formInput, updateFormInput] = useState({ price: '', name: '', description: '' })
const router = useRouter()
async function onChange(e) {
const file = e.target.files[0]
try {
const added = await client.add(
file,
{
progress: (prog) => console.log(`received: ${prog}`)
}
)
const url = `https://ipfs.infura.io/ipfs/${added.path}`
setFileUrl(url)
} catch (error) {
console.log('Error uploading file: ', error)
}
}async function createAndMintNFT() {
const { name, description } = formInput
if (!name || !description || !fileUrl) return
const data = JSON.stringify({
name, description, image: fileUrl
})
try {
const added = await client.add(data)
const url = `https://ipfs.infura.io/ipfs/${added.path}`
const web3Modal = new Web3Modal()
const connection = await web3Modal.connect()
const provider = new ethers.providers.Web3Provider(connection)
const signer = provider.getSigner()
let contract = new ethers.Contract(nftaddress, NFT.abi, signer)
let transaction = await contract.createToken(url)
await transaction.wait()
router.push('/')
} catch (error) {
console.log('Error uploading file: ', error)
}
}
async function createSale(url) {
}
return (
<div className="flex justify-center">
<div className="w-1/2 flex flex-col pb-12">
<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"
className="mt-2 border rounded p-4"
onChange={e => updateFormInput({ ...formInput, price: e.target.value })}
/>
<input
type="file"
name="Asset"
className="my-4"
onChange={onChange}
/>
{
fileUrl && (
<img className="rounded mt-4" width="350" src={fileUrl} />
)
}
<button onClick={createAndMintNFT} className="font-bold mt-4 bg-pink-500 text-white rounded p-4 shadow-lg">
Mint NFT
</button>
</div>
</div>
)
}
In general, everything is simple here. We initialize the necessary modules at the beginning and write a WEB layout at the end. The only thing that is interesting here is the createAndMintNFT and onChange functions.
onChange function uploads the image to ipfs (decentralized storage), and createAndMintNFT function creates and mints NFT (who would have thought).
I think it makes no sense to explain what a particular line means, you can always Google it.
What may seem strange to you about this is the following
import {
nftaddress
} from '../config'
We don’t have any config and what is this nftaddress? So, let’s move on to the next section.
Project launch on local network
First, create a config file in the root of the project.js and leave it empty.
Open folder named scripts, delete sample-script.js and create deploy.js, into which insert the following code:
const hre = require("hardhat");
const fs = require('fs');async function main() {
const NFT = await hre.ethers.getContractFactory("NFT");
const nft = await NFT.deploy(nftMarket.address);
await nft.deployed();
console.log("nft deployed to:", nft.address);
let config = `
export const nftaddress = "${nft.address}" `
let data = JSON.stringify(config)
fs.writeFileSync('config.js', JSON.parse(data))
}main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
Now, thanks to this script, we can deploy the contract on a local or any other network.
To deploy to a local network, you need to enter the following command:
npx hardhat node
This command will start the local node.
After that, in another console window, without stopping the node, deploy contract using the following command:
npx hardhat run scripts/deploy.js --network localhost
Wait a bit and deploy the site locally using the following command:
npm run dev
Open site and try to interact with it.
Project launch on Moonbeam/Moonbase Alpha/Moonriver
Now, to deploy the project, you need to enter one of the following commands.
Don’t forget that you need tokens to pay the commission
For MoonbaseAlpha:
npx hardhat run scripts/deploy.js --network MoonbaseAlpha
For Moonriver:
npx hardhat run scripts/deploy.js --network Moonriver
For Moonbeam:
npx hardhat run scripts/deploy.js --network Moonbeam
If everything went well, then launch the site, as in the local network, and interact with your project!
Congratulations! You have written your own page for the NFT mint and deployed it in one of the networks!
I hope this guide helped you better understand the topic and start creating your own project.