Jika Anda baru dalam dunia blockchain dan ingin mempelajari cara membangun NFT Marketplace, tutorial ini adalah tempat yang tepat untuk memulai. Dalam panduan ini, saya akan menjelaskan setiap langkah dengan detail dan membantu Anda memahami konsep dasar NFT Marketplace hingga deploy ke Infura. Sumber asli tutorial ini Anda bisa lihat di sini, pada tutorial ini saya sempurnakan dan mengubah bahasanya menjadi lebih mudah dipahami.
Prerequisites
- Node.js v12 atau diatasnya
- Truffle
npm install -g truffle
- Visual Studio Code
- Git
- Next.js
- Tailwind.css
- Metamask
- Infura
Lakukan instalasi Visual Studio Code, Git, Node.js, Truffle, untuk Metamask, Infura, Next.js, dan Tailwind.css akan saya jelaskan lebih detail pada section berikut.
Metamask
Metmask adalah salah satu wallet kripto, di sini kita akan menginstall Metamask dalam bentuk ekstensi Chrome.
Setelah berhasil install, buatlah Wallet baru dengan langkah berikut:
Penting : Simpan Secret Recovery Phrase dengan baik, karena nanti akan digunakan kembali.
Karena kita akan menggunakan jaringan Goerli ( jaringan testnet untuk melakukan uji coba ) maka aktifkan pada Metamask dengan langkah berikut:
Karena kita akan menggunakan jaringan Optimism Goerli maka kita perlu menambahkan Network baru.
Optimism Goerli
- Network Name : Optimism Goerli test network
- New RPC URL : https://goerli.optimism.io/
- Chain ID : 420
- Currency Symbol : ETH
- Block Explorer URL : https://blockscout.com/optimism/goerli/
Dapatkan token Goerli ETH yang nantinya akan kita transfer ke Goerli Optimism pada link berikut:
https://goerli-faucet.pk910.de/
Pastikan sudah pindah ke jaringan Goerli ETH, kemudian masukkan wallet address Anda
Setelah mendapatkan Goerli ETH, transfer ke Goerli Optimism dengan mentransfer ke wallet address berikut : 0x636Af16bf2f682dD3109e60102b8E1A089FedAa8
Infura
Infura adalah service backend untuk jaringan blockhain Etherium, daftar akun pada link berikut : https://app.infura.io/register. Meskipun ada layanan gratis, tapi untuk menggunakan IPFS Infura Anda harus memasukkan kartu kredit, jika Anda tidak memiliki kartu kredit Anda bisa menghubungi saya untuk saya share API Key.
Jika tidak ada masalah pada langkah di atas, maka Anda bisa melanjutkan Tutorial ini. Login ke dashboard Infura, kemudian buat Web3API baru:
Web3 API ini digunakan untuk berinteraksi dengan jaringan blockchain Ethereum seperti membuat smart contract. Selanjutnya buat IPFS untuk menyimpan file terdesentralisasi.
Setup Project
Buka Git Bash kemudian masuk ke directory project yang ingin Anda simpan, contoh saya akan menyimpan di D:\Codes\Projects. Tuliskan perintah seperti berikut:
truffle unbox optimism nft-marketplace
cd nft-marketplace
npm install
Selanjutnya lakukan setup Next.js untuk client ( Frontside ) dengan perintah berikut :
npx create-next-app@latest client
Selanjutnya install Tailwind untuk CSS-nya dengan perintah berikut : ( Pindah dulu ke directory client )
cd client
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
Init tailwind yang sudah terinstall dengan perintah berikut:
npx tailwindcss init -p
Buka project nft-marketplace dengan Visual Studio Code, kemudian edit file tailwind.config.js
Edit file styles/global.css seperti berikut:
@tailwind base;
@tailwind components;
@tailwind utilities;
Truffle Config
Selanjutnya kita beralih ke file truffle-config.js, ubah value dari contracts_build_directory menjadi :
./client/contracts/optimism-contracts
Dan value dari contracts_directory menjadi:
./contracts/optimism
Build Smart Contract
Install OpenZeppelin Smart Contract dengan cara membuka Git Bash, kemudian kembali ke directory nft-marketplace dulu dan tulis perintah seperti berikut:
cd ..
npm install @openzeppelin/contracts
Selanjutnya edit Smart Contract SimpleStorage.sol yang ada di folder contracts/optimism menjadi menjadi BoredPetsNFT.sol dengan code seperti berikut:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract BoredPetsNFT is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
address marketplaceContract;
event NFTMinted(uint256);
constructor(address _marketplaceContract) ERC721("Bored Pets Yacht Club", "BPYC") {
marketplaceContract = _marketplaceContract;
}
function mint(string memory _tokenURI) public {
_tokenIds.increment();
uint256 newTokenId = _tokenIds.current();
_safeMint(msg.sender, newTokenId);
_setTokenURI(newTokenId, _tokenURI);
setApprovalForAll(marketplaceContract, true);
emit NFTMinted(newTokenId);
}
}
Selanjutnya buat Marketplace Contract dengan membuat file baru Marketplace.sol di dalam folder contracts/optimism dengan code seperti berikut:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Marketplace is ReentrancyGuard {
using Counters for Counters.Counter;
Counters.Counter private _nftsSold;
Counters.Counter private _nftCount;
uint256 public LISTING_FEE = 0.0001 ether;
address payable private _marketOwner;
mapping(uint256 => NFT) private _idToNFT;
struct NFT {
address nftContract;
uint256 tokenId;
address payable seller;
address payable owner;
uint256 price;
bool listed;
}
event NFTListed(
address nftContract,
uint256 tokenId,
address seller,
address owner,
uint256 price
);
event NFTSold(
address nftContract,
uint256 tokenId,
address seller,
address owner,
uint256 price
);
constructor() {
_marketOwner = payable(msg.sender);
}
// List the NFT on the marketplace
function listNft(address _nftContract, uint256 _tokenId, uint256 _price) public payable nonReentrant {
require(_price > 0, "Price must be at least 1 wei");
require(msg.value == LISTING_FEE, "Not enough ether for listing fee");
IERC721(_nftContract).transferFrom(msg.sender, address(this), _tokenId);
_nftCount.increment();
_idToNFT[_tokenId] = NFT(
_nftContract,
_tokenId,
payable(msg.sender),
payable(address(this)),
_price,
true
);
emit NFTListed(_nftContract, _tokenId, msg.sender, address(this), _price);
}
// Buy an NFT
function buyNft(address _nftContract, uint256 _tokenId) public payable nonReentrant {
NFT storage nft = _idToNFT[_tokenId];
require(msg.value >= nft.price, "Not enough ether to cover asking price");
address payable buyer = payable(msg.sender);
payable(nft.seller).transfer(msg.value);
IERC721(_nftContract).transferFrom(address(this), buyer, nft.tokenId);
_marketOwner.transfer(LISTING_FEE);
nft.owner = buyer;
nft.listed = false;
_nftsSold.increment();
emit NFTSold(_nftContract, nft.tokenId, nft.seller, buyer, msg.value);
}
// Resell an NFT purchased from the marketplace
function resellNft(address _nftContract, uint256 _tokenId, uint256 _price) public payable nonReentrant {
require(_price > 0, "Price must be at least 1 wei");
require(msg.value == LISTING_FEE, "Not enough ether for listing fee");
IERC721(_nftContract).transferFrom(msg.sender, address(this), _tokenId);
NFT storage nft = _idToNFT[_tokenId];
nft.seller = payable(msg.sender);
nft.owner = payable(address(this));
nft.listed = true;
nft.price = _price;
_nftsSold.decrement();
emit NFTListed(_nftContract, _tokenId, msg.sender, address(this), _price);
}
function getListingFee() public view returns (uint256) {
return LISTING_FEE;
}
function getListedNfts() public view returns (NFT[] memory) {
uint256 nftCount = _nftCount.current();
uint256 unsoldNftsCount = nftCount - _nftsSold.current();
NFT[] memory nfts = new NFT[](unsoldNftsCount);
uint nftsIndex = 0;
for (uint i = 0; i < nftCount; i++) {
if (_idToNFT[i + 1].listed) {
nfts[nftsIndex] = _idToNFT[i + 1];
nftsIndex++;
}
}
return nfts;
}
function getMyNfts() public view returns (NFT[] memory) {
uint nftCount = _nftCount.current();
uint myNftCount = 0;
for (uint i = 0; i < nftCount; i++) {
if (_idToNFT[i + 1].owner == msg.sender) {
myNftCount++;
}
}
NFT[] memory nfts = new NFT[](myNftCount);
uint nftsIndex = 0;
for (uint i = 0; i < nftCount; i++) {
if (_idToNFT[i + 1].owner == msg.sender) {
nfts[nftsIndex] = _idToNFT[i + 1];
nftsIndex++;
}
}
return nfts;
}
function getMyListedNfts() public view returns (NFT[] memory) {
uint nftCount = _nftCount.current();
uint myListedNftCount = 0;
for (uint i = 0; i < nftCount; i++) {
if (_idToNFT[i + 1].seller == msg.sender && _idToNFT[i + 1].listed) {
myListedNftCount++;
}
}
NFT[] memory nfts = new NFT[](myListedNftCount);
uint nftsIndex = 0;
for (uint i = 0; i < nftCount; i++) {
if (_idToNFT[i + 1].seller == msg.sender && _idToNFT[i + 1].listed) {
nfts[nftsIndex] = _idToNFT[i + 1];
nftsIndex++;
}
}
return nfts;
}
}
Secara singkat code di atas digunakan untuk membuat fungsionalitas marketplace seperti menampilkan daftar NFT, menjual NFT, dan memberli NFT.
Deploy Smart Contracts
Edit file migrations/1_deploy_contracts.js dengan code seperti berikut:
var BoredPetsNFT = artifacts.require("BoredPetsNFT");
var Marketplace = artifacts.require("Marketplace");
module.exports = async function(deployer) {
await deployer.deploy(Marketplace);
const marketplace = await Marketplace.deployed();
await deployer.deploy(BoredPetsNFT, marketplace.address);
}
Edit file .env pada root project ( di luar folder )
INFURA_KEY="<API Key Infura Anda>"
GANACHE_MNEMONIC="<Kosongkan Saja>"
GOERLI_MNEMONIC="<Mnemonic atau Secret Prashe Recovery Metamask Anda >"
Edit truffle-config.js kemudian tambahkan code berikut:
optimistic_goerli: {
network_id: 420,
chain_id: 420,
provider: function () {
return new HDWalletProvider(goerliMnemonic, "https://optimism-goerli.infura.io/v3/" + infuraKey, 0, 1);
}
},
Build smart contract pada jaringan optimism goerli di Infura kita, buka terminal Visual Studio Code Ctrl + ~ kemudian tuliskan perintah berikut:
truffle migrate --network optimistic_goerli
Jika berhasil maka pada file clients/contracts akan tergenerate file json dari smart contract yang barusan dibuild.
Cek pada dashboard Infura, maka akan muncul Request
Oke, kita sudah berhasil deploy smart contract ke Infura, selanjutnya kita akan buat Apps Front End Web-nya.
Front End
Masuk ke dalam directory client, kemudian install semua package yang dibutuhkan satu persatu.
cd client
npm install axios
npm install web3modal
npm install web3
npm install ipfs-http-client
Saya sarankan menggunakan Git Bash
Edit file _app.tsx menjadi _app.js dengan code berikut:
import '../styles/globals.css'
import Link from 'next/link'
function MyApp({ Component, pageProps }) {
return (
<div>
<nav className="border-b p-6">
<p className="text-4xl font-bold">Bored Pet Marketplace</p>
<div className="flex mt-4">
<Link href="/" legacyBehavior>
<a className="mr-4 text-teal-400">
Home
</a>
</Link>
<Link href="/create-and-list-nft" legacyBehavior>
<a className="mr-6 text-teal-400">
Sell a new NFT
</a>
</Link>
<Link href="/my-nfts" legacyBehavior>
<a className="mr-6 text-teal-400">
My NFTs
</a>
</Link>
<Link href="/my-listed-nfts" legacyBehavior>
<a className="mr-6 text-teal-400">
My Listed NFTs
</a>
</Link>
</div>
</nav>
<Component {...pageProps} />
</div>
)
}
export default MyApp
Edit file index.tsx menjadi index.js dengan code berikut:
import Web3 from 'web3';
import Web3Modal from 'web3modal';
import { useEffect, useState } from 'react';
import axios from 'axios';
import Marketplace from '../contracts/optimism-contracts/Marketplace.json'
import BoredPetsNFT from '../contracts/optimism-contracts/BoredPetsNFT.json'
export default function Home() {
const [nfts, setNfts] = useState([])
const [loadingState, setLoadingState] = useState('not-loaded')
useEffect(() => { loadNFTs() }, [])
async function loadNFTs() {
const web3Modal = new Web3Modal()
const provider = await web3Modal.connect()
const web3 = new Web3(provider)
const networkId = await web3.eth.net.getId()
// Get all listed NFTs
const marketPlaceContract = new web3.eth.Contract(Marketplace.abi, Marketplace.networks[networkId].address)
const listings = await marketPlaceContract.methods.getListedNfts().call()
// Iterate over the listed NFTs and retrieve their metadata
const nfts = await Promise.all(listings.map(async (i) => {
try {
const boredPetsContract = new web3.eth.Contract(BoredPetsNFT.abi, BoredPetsNFT.networks[networkId].address)
const tokenURI = await boredPetsContract.methods.tokenURI(i.tokenId).call()
const meta = await axios.get(tokenURI)
const nft = {
price: i.price,
tokenId: i.tokenId,
seller: i.seller,
owner: i.buyer,
image: meta.data.image,
name: meta.data.name,
description: meta.data.description,
}
return nft
} catch(err) {
console.log(err)
return null
}
}))
setNfts(nfts.filter(nft => nft !== null))
setLoadingState('loaded')
}
async function buyNft(nft) {
const web3Modal = new Web3Modal()
const provider = await web3Modal.connect()
const web3 = new Web3(provider)
const networkId = await web3.eth.net.getId();
const marketPlaceContract = new web3.eth.Contract(Marketplace.abi, Marketplace.networks[networkId].address);
const accounts = await web3.eth.getAccounts();
await marketPlaceContract.methods.buyNft(BoredPetsNFT.networks[networkId].address, nft.tokenId).send({ from: accounts[0], value: nft.price });
loadNFTs()
}
if (loadingState === 'loaded' && !nfts.length) {
return (<h1 className="px-20 py-10 text-3xl">No pets available!</h1>)
} else {
return (
<div className="flex justify-center">
<div className="px-4" style={ { maxWidth: '1600px' } }>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 pt-4">
{
nfts.map((nft, i) => (
<div key={i} className="border shadow rounded-xl overflow-hidden">
<img src={nft.image} />
<div className="p-4">
<p style={ { height: '64px' } } className="text-2xl font-semibold">{nft.name}</p>
<div style={ { height: '70px', overflow: 'hidden' } }>
<p className="text-gray-400">{nft.description}</p>
</div>
</div>
<div className="p-4 bg-black">
<p className="text-2xl font-bold text-white">{Web3.utils.fromWei(nft.price, "ether")} ETH</p>
<button className="mt-4 w-full bg-teal-400 text-white font-bold py-2 px-12 rounded" onClick={() => buyNft(nft)}>Buy</button>
</div>
</div>
))
}
</div>
</div>
</div>
)
}
}
Buat file baru dengan nama .env pada folder clients, kemudian isikan value berdasarkan secret dan key IPFS Infura Anda:
NEXT_PUBLIC_IPFS_SECRET=xxx
NEXT_PUBLIC_IPFS_KEY=zzz
Kembali ke folder pages kemudian buat file baru dengan nama create-and-list-nft.js dengan code berikut:
import { useState } from 'react'
import Web3 from 'web3'
import Web3Modal from 'web3modal'
import { useRouter } from 'next/router'
import { create as ipfsHttpClient } from 'ipfs-http-client'
import Marketplace from '../contracts/ethereum-contracts/Marketplace.json'
import BoredPetsNFT from '../contracts/ethereum-contracts/BoredPetsNFT.json'
const projectId = process.env["NEXT_PUBLIC_IPFS_KEY"];
const projectSecret = process.env["NEXT_PUBLIC_IPFS_SECRET"];
const auth =
'Basic ' + Buffer.from(projectId + ':' + projectSecret).toString('base64');
const client = ipfsHttpClient({
host: 'ipfs.infura.io',
port: 5001,
protocol: 'https',
headers: {
authorization: auth,
},
});
export default function CreateItem() {
const [fileUrl, setFileUrl] = useState(null)
const [formInput, updateFormInput] = useState({ price: '', name: '', description: '' })
const router = useRouter()
async function onChange(e) {
// upload image to IPFS
const file = e.target.files[0]
try {
const added = await client.add(
file,
{
progress: (prog) => console.log(`received: ${prog}`)
}
)
const url = `https://jp-nft-marketplace.infura-ipfs.io/ipfs/${added.path}`
setFileUrl(url)
} catch (error) {
console.log('Error uploading file: ', error)
}
}
async function uploadToIPFS() {
const { name, description, price } = formInput
if (!name || !description || !price || !fileUrl) {
return
} else {
// first, upload metadata to IPFS
const data = JSON.stringify({
name, description, image: fileUrl
})
try {
const added = await client.add(data)
console.log('added: ', added)
const url = `https://jp-nft-marketplace.infura-ipfs.io/ipfs/${added.path}`
// after metadata is uploaded to IPFS, return the URL to use it in the transaction
return url
} catch (error) {
console.log('Error uploading file: ', error)
}
}
}
async function listNFTForSale() {
const web3Modal = new Web3Modal()
const provider = await web3Modal.connect()
const web3 = new Web3(provider)
const url = await uploadToIPFS()
const networkId = await web3.eth.net.getId()
// Mint the NFT
const boredPetsContractAddress = BoredPetsNFT.networks[networkId].address
const boredPetsContract = new web3.eth.Contract(BoredPetsNFT.abi, boredPetsContractAddress)
const accounts = await web3.eth.getAccounts()
const marketPlaceContract = new web3.eth.Contract(Marketplace.abi, Marketplace.networks[networkId].address)
let listingFee = await marketPlaceContract.methods.getListingFee().call()
listingFee = listingFee.toString()
boredPetsContract.methods.mint(url).send({ from: accounts[0] }).on('receipt', function (receipt) {
console.log('minted');
// List the NFT
const tokenId = receipt.events.NFTMinted.returnValues[0];
marketPlaceContract.methods.listNft(boredPetsContractAddress, tokenId, Web3.utils.toWei(formInput.price, "ether"))
.send({ from: accounts[0], value: listingFee }).on('receipt', function () {
console.log('listed')
router.push('/')
});
});
}
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 in Eth"
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={listNFTForSale} className="font-bold mt-4 bg-teal-400 text-white rounded p-4 shadow-lg">
Mint and list NFT
</button>
</div>
</div>
)
}
PENTING: Sesuaikan url IPFS DEDICATED GATEWAY SUBDOMAIN Infura Anda
Buat file baru dengan nama my-nfts.js dengan code berikut:
import Web3 from 'web3';
import { useEffect, useState } from 'react'
import axios from 'axios'
import Web3Modal from 'web3modal'
import { useRouter } from 'next/router'
import Marketplace from '../contracts/optimism-contracts/Marketplace.json';
import BoredPetsNFT from '../contracts/optimism-contracts/BoredPetsNFT.json';
export default function MyAssets() {
const [nfts, setNfts] = useState([])
const [loadingState, setLoadingState] = useState('not-loaded')
const router = useRouter()
useEffect(() => { loadNFTs() }, [])
async function loadNFTs() {
const web3Modal = new Web3Modal()
const provider = await web3Modal.connect()
const web3 = new Web3(provider)
const networkId = await web3.eth.net.getId()
const marketPlaceContract = new web3.eth.Contract(Marketplace.abi, Marketplace.networks[networkId].address)
const boredPetsContractAddress = BoredPetsNFT.networks[networkId].address
const boredPetsContract = new web3.eth.Contract(BoredPetsNFT.abi, boredPetsContractAddress)
const accounts = await web3.eth.getAccounts()
const data = await marketPlaceContract.methods.getMyNfts().call({from: accounts[0]})
const nfts = await Promise.all(data.map(async i => {
try {
const tokenURI = await boredPetsContract.methods.tokenURI(i.tokenId).call()
const meta = await axios.get(tokenURI)
let nft = {
price: i.price,
tokenId: i.tokenId,
seller: i.seller,
owner: i.buyer,
image: meta.data.image,
name: meta.data.name,
description: meta.data.description,
tokenURI: tokenURI
}
return nft
} catch(err) {
console.log(err)
return null
}
}))
setNfts(nfts.filter(nft => nft !== null))
setLoadingState('loaded')
}
function listNFT(nft) {
router.push(`/resell-nft?id=${nft.tokenId}&tokenURI=${nft.tokenURI}`)
}
if (loadingState === 'loaded' && !nfts.length) {
return (<h1 className="py-10 px-20 text-3xl">No NFTs owned</h1>);
} else {
return (
<div className="flex justify-center">
<div className="p-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 pt-4">
{
nfts.map((nft, i) => (
<div key={i} className="border shadow rounded-xl overflow-hidden">
<img src={nft.image} className="rounded" />
<div className="p-4">
<p style={ { height: '64px' } } className="text-2xl font-semibold">{nft.name}</p>
<div style={ { height: '70px', overflow: 'hidden' } }>
<p className="text-gray-400">{nft.description}</p>
</div>
</div>
<div className="p-4 bg-black">
<p className="text-2xl font-bold text-white">Price - {Web3.utils.fromWei(nft.price, "ether")} Eth</p>
<button className="mt-4 w-full bg-teal-400 text-white font-bold py-2 px-12 rounded" onClick={() => listNFT(nft)}>List</button>
</div>
</div>
))
}
</div>
</div>
</div>
);
}
}
Buat file baru dengan nama resell-nft.js dengan code berikut:
import { useEffect, useState } from 'react'
import Web3 from 'web3'
import { useRouter } from 'next/router'
import axios from 'axios'
import Web3Modal from 'web3modal'
import Marketplace from '../contracts/optimism-contracts/Marketplace.json'
import BoredPetsNFT from '../contracts/optimism-contracts/BoredPetsNFT.json'
export default function ResellNFT() {
const [formInput, updateFormInput] = useState({ price: '', image: '' })
const router = useRouter()
const { id, tokenURI } = router.query
const { image, price } = formInput
useEffect(() => { fetchNFT() }, [id])
async function fetchNFT() {
if (!tokenURI) {
return
} else {
const meta = await axios.get(tokenURI)
updateFormInput(state => ({ ...state, image: meta.data.image }))
}
}
async function listNFTForSale() {
if (!price) {
return
} else {
const web3Modal = new Web3Modal()
const provider = await web3Modal.connect()
const web3 = new Web3(provider)
const networkId = await web3.eth.net.getId()
const marketPlaceContract = new web3.eth.Contract(Marketplace.abi, Marketplace.networks[networkId].address)
let listingFee = await marketPlaceContract.methods.getListingFee().call()
listingFee = listingFee.toString()
const accounts = await web3.eth.getAccounts()
marketPlaceContract.methods.resellNft(BoredPetsNFT.networks[networkId].address, id, Web3.utils.toWei(formInput.price, "ether"))
.send({ from: accounts[0], value: listingFee }).on('receipt', function () {
router.push('/')
});
}
}
return (
<div className="flex justify-center">
<div className="w-1/2 flex flex-col pb-12">
<input
placeholder="Asset Price in Eth"
className="mt-2 border rounded p-4"
onChange={e => updateFormInput({ ...formInput, price: e.target.value })}
/>
{
image && (
<img className="rounded mt-4" width="350" src={image} />
)
}
<button onClick={listNFTForSale} className="font-bold mt-4 bg-teal-400 text-white rounded p-4 shadow-lg">
List NFT
</button>
</div>
</div>
)
}
Buat file baru dengan nama my-listed-nfts.js dengan code berikut:
import Web3 from 'web3';
import { useEffect, useState } from 'react';
import axios from 'axios';
import Web3Modal from 'web3modal';
import Marketplace from '../contracts/optimism-contracts/Marketplace.json';
import BoredPetsNFT from '../contracts/optimism-contracts/BoredPetsNFT.json';
export default function CreatorDashboard() {
const [nfts, setNfts] = useState([])
const [loadingState, setLoadingState] = useState('not-loaded')
useEffect(() => { loadNFTs() }, [])
async function loadNFTs() {
const web3Modal = new Web3Modal()
const provider = await web3Modal.connect()
const web3 = new Web3(provider)
const networkId = await web3.eth.net.getId()
// Get listed NFTs
const marketPlaceContract = new web3.eth.Contract(Marketplace.abi, Marketplace.networks[networkId].address)
const accounts = await web3.eth.getAccounts()
const listings = await marketPlaceContract.methods.getMyListedNfts().call({from: accounts[0]})
// Iterate over my listed NFTs and retrieve their metadata
const nfts = await Promise.all(listings.map(async i => {
try {
const boredPetsContract = new web3.eth.Contract(BoredPetsNFT.abi, BoredPetsNFT.networks[networkId].address)
const tokenURI = await boredPetsContract.methods.tokenURI(i.tokenId).call();
const meta = await axios.get(tokenURI);
let item = {
price: i.price,
tokenId: i.tokenId,
seller: i.seller,
owner: i.owner,
image: meta.data.image,
}
return item
} catch(err) {
console.log(err)
return null
}
}))
setNfts(nfts.filter(nft => nft !== null))
setLoadingState('loaded')
}
if (loadingState === 'loaded' && !nfts.length) {
return (<h1 className="py-10 px-20 text-3xl">No NFTs listed</h1>)
} else {
return (
<div>
<div className="p-4">
<h2 className="text-2xl py-2">Items Listed</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 pt-4">
{
nfts.map((nft, i) => (
<div key={i} className="border shadow rounded-xl overflow-hidden">
<img src={nft.image} className="rounded" />
<div className="p-4 bg-black">
<p className="text-2xl font-bold text-white">Price - {Web3.utils.fromWei(nft.price, "ether")} Eth</p>
</div>
</div>
))
}
</div>
</div>
</div>
)
}
}
Jalankan Project
Untuk menjalankan project Next, buka terminal kemudian tuliskan perintah berikut:
Project akan berrjalan pada port 3000, buka di browser dengan menuliskan alamat berikut:
http://localhost:3000/
Ketika pertama kali dibuka, maka browser akan meminta mengkoneksikan wallet ke aplikasi kita.
Pastikan terkoneksi dengan Optimism Goerli, jika tidak maka aplikasi tidak akan dapat berjalan, karena semua aktivitas terhububg pada jaringan tersebut.
Mari kita coba menjual NFT pertama kita:
Sampai pada tahap ini Anda sudah berhasil membuat NFT Marketplace dasr dengan Infura menggunakan jaringani Optimism Goerli. Jika Anda ingin deploy ke mainnet, maka Anda bisa mengaturnya lewat truffle-config.js kemudian lakukan migrate dengan –network yang Anda inginkan. Jika ingin mendeploy aplikasi Anda bisa menggunakan Vercel ( layanan gratis hosting ) sudah support untuk Next.js.
Repository:
https://github.com/isonnymichael/pet-shop-dapp.git
Live Demo:
https://nft-marketplace-one-silk.vercel.app/
Jika mengalami kendala, atau butuh API Key Infura untuk testing silahkan hubungi saya di isonnymichael@gmail.com.