怎麼快速用Moralis建立一個NFT Marketplace(Next.js)- 上篇

FuhuiChen
Insight Software
Published in
17 min readJun 2, 2022

隨著對 NFT(非同質化代幣,non-fungible tokens)的關注日益增加,現在比以往任何時候都更應該深入地研究 NFT 市場並更多地了解它們的含義。 NFT 市場是數位藝術家和開發人員可以向大眾展示他們的作品的空間。 此外,隨著平台在市場上積累和展示藝術品,潛在買家更容易獲得它。借助 NFT,您現在可以在區塊鏈上開始您的創意之旅圖像文件,如 jpeg、音頻文件、文本或推文記錄,甚至代碼語言本身 — 任何常規檔案—都可放上NFT市場,它們將成為具有明確來源或出處記錄的獨特收藏品,可以在數字市場上出售或購買它們。

除了藝術創新之外,NFT 還具有改變遊戲的潛力。 NFT 的一些最實用和最有利可圖的應用可以在遊戲中找到。開發人員和遊戲玩家都可以從 NFT 中獲利,NFT 通常作為稀有物品、獎品或收藏品嵌入遊戲中。

本文將針對NFT的起源及技術原理做簡單的介紹,並用一個簡單的範例說明如何利用Moralis提供的平台及API建立NFT Marketplace。利用Moralis提供的平台及API,即使是Web3.0的新手也能很快的開發出DApp。

NFT的起源

ERC-20標準屬於「同質化代幣(FT:Fungible Token)」,意思是發行的每一枚貨幣都是「本質相同」可以替換的,就像我們使用的鈔票一樣,你手上的100元和我手上的100元雖然不是同一張紙,但是它們是等值的可以替換的。而後來推出的ERC-721 或 ERC1155標準屬於「非同質化代幣(NFT:Non Fungible Token)」,意思是發行的每一枚貨幣都是「本質不同」獨一無二的,如果一個東西在世界上是獨一無二的,那它就是不可替換的,

CryptoPunks 由Larva Labs開發,是最早在Ethereum區塊鏈上的NFT專案之一,該專案由 10,000 張演算法生成的24x24 圖元肖像組成,具有隨機屬性,例如戴帽子人或抽煙斗人。

那麼CryptoPunks是如何使用ERC-20達到類似非同質化代幣的效果呢?

我們可以看一下它的合約片段,合約中註明發幣量為10000

由於將圖片檔太多,因此將10000張圖片合成的圖片hash記在合約之中

然後每個圖片擁有者的位址也會紀錄在合約之後

在進行交易時會去確認每個合約的擁有者是否正確

因為我們可以看到,圖片(hash)跟使用者地址都紀錄在鏈上,是確定而不能更改的,圖片及擁有者的從屬關系很明確,這樣的方式也啟發了ERC-721標準的產生,裡面定義了智能合約所必須實作及可選的函式(具體內容參考ERC-721)。

基於ERC-721的NFT範例

2021年,Larva Labs的另一個基於ERC-721的專案Meebits上線(這裡可以看到合約內容),其中片段如下

NFT相关的内容存储在IPFS(星際檔案系統,一種分布式儲存協定)之中:

所以可以看到會將內容的hash存在鏈上,以保證不可更改,然後服務層提供一個關於內容的描述 。接下來將教你建立自己的NFT

建立ERC-721合約

OpenZeppelin 是一個開源的智能合約倉庫,上面已經有許許多多各種各樣的智能合約範本,包括 ERC721 智能合約。因此我們只需要基於 OpenZeppelin 的現有合約,擴展我們自己特定的功能即可。

Remix 是 Ethereum 官方提供的 IDE 。包含完整的編譯器、執行合約、發佈合約等等的功能。無須安裝,只要用瀏覽器開啟即可。

打開Remix後,再Contracts中新增一個檔案MyOwnNFT.sol:

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
contract MyOwnNFT is ERC721, ERC721Enumerable, ERC721URIStorage {
using SafeMath for uint256;
uint public constant mintPrice = 0;

function _beforeTokenTransfer(address from, address to,
uint256 tokenId)
internal
override(ERC721, ERC721Enumerable)
{
super._beforeTokenTransfer(from, to, tokenId);
}
function _burn(uint256 tokenId) internal override(ERC721,
ERC721URIStorage) {
super._burn(tokenId);
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
constructor() ERC721("MyOwnNFT", "MON") {}
function mint(string memory _uri) public payable {
uint256 mintIndex = totalSupply();
_safeMint(msg.sender, mintIndex);
_setTokenURI(mintIndex, _uri);
}
}

首先,在NFT中我們要constructor() ERC721(“MyOwnNFT”, “MON”) 這行,可以自由改成自己想要的名稱。

Mint在NFT的世界中意思為”鑄造”,也就是將數位內容鑄造成區塊鏈中獨一無二的代幣,因為在NFT市場中,第一手購買就是使用智能合約的mint(),所以我們需要了解mint()函式做了什麼。

鑄幣時我們會傳入uri做為參數,然後透過totalSupply()可取得新代幣的id,並透過_safeMint將其指定給一個新的NFT代幣,最後將uri設定給這個NFT代幣。

接著再來按下Compile

接著選到Deploy Tab,ENVIRONMENT選擇”Injected web3", CONTRACT選擇剛才新增的檔案,然後確認與你的MetaMask連接並選擇Ropsten測試網路,確認你的帳戶中有代幣可以支付gas fee,然後點下 “Deploy”並在MetaMask確認。

我們可以看到合約已成功Deploy並可以與之互動

設定Moralis Server及Next.js專案

如何新增Moralis Server(Ropsten Testnet) 與建立Next.js專案,設定環境變數及連接MetaMask請參考上一篇

設定合約資訊

在根目錄下建立一個contract.js,然後參考Remix中的資訊

合約布署的位址(Deploy頁面),複制到參數contractAddress

ABI (Application Binary Interface),複制到參數contractABI

contract.js檔案如下

export const contractAddress = "0x783955a43c9b6e7f9193d230F7FE87b9803Cdb44";
export const contractABI = [
{
inputs: [],
stateMutability: "nonpayable",
type: "constructor",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "owner",
type: "address",
},
{
indexed: true,
internalType: "address",
name: "approved",
type: "address",
},
{
indexed: true,
internalType: "uint256",
name: "tokenId",
type: "uint256",
},
],
name: "Approval",
type: "event",
},
//以下省略
];

建立簡單的Sidebar

首先,修改pages下的_app.tsx如下

import { MoralisProvider } from "react-moralis";
import "../styles/globals.css";
import Link from 'next/link';
import { useRouter } from 'next/router';
const menuItems = [
{href:"/dashboard",title:"Mint NFT"},
{href:"/nftlist",title:"List NFTs"}
]
function MyApp({ Component, pageProps }) {
const { asPath, pathname } = useRouter();
console.log(pathname)
return (
<MoralisProvider
appId={process.env.NEXT_PUBLIC_APP_ID}
serverUrl={process.env.NEXT_PUBLIC_SERVER_URL}
>
<div className='flex flex-row h-screen w-screen' >
{pathname!='/'?<aside className='h-screen
bg-indigo-500 md:w-60 p-2'>
<nav className='h-full'>
<ul>
{menuItems.map(({ href, title }) => (
<li className='m-2' key={title}>
<Link href={href}>
<a
className={`flex p-2 bg-fuchsia-200 rounded
hover:bg-fuchsia-400 cursor-pointer`} >
{title}
</a>
</Link>
</li>
))}
</ul>
</nav>
</aside>:null}
<Component {...pageProps} />
</div>
</MoralisProvider>
);
}
export default MyApp;

這樣我們可以看到進入/dashboard時顯示如下

建立簡單的Sidebar

接著我們修改/dashboard下的index.js如下

import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useMoralis } from "react-moralis";
import Moralis from "moralis";
import { contractABI, contractAddress } from "../../contract";import
import Web3 from "web3"
const web3 = new Web3(Web3.givenProvider);
function Index() {
const { isAuthenticated, logout, user } = useMoralis();
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [file, setFile] = useState(null);
const router = useRouter();
const onSubmit = async (e) => {

};
useEffect(() => {
if (!isAuthenticated) router.replace("/");
}, [isAuthenticated]);
return (
<div className="flex w-screen h-screen
items-center justify-center">
<form onSubmit={onSubmit}>
<div>
<input
type="text"
className="border-[1px] p-2 text-lg border-black w-full"
value={name}
placeholder="Name"
onChange={(e) => setName(e.target.value)}
/>
</div>
<div className="mt-3">
<input
type="text"
className="border-[1px] p-2 text-lg border-black w-full"
value={description}
placeholder="Description"
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<div className="mt-3">
<input
type="file"
className="border-[1px] p-2 text-lg border-black"
onChange={(e) => setFile(e.target.files[0])}
/>
</div>
<button
type="submit"
className="mt-5 w-full p-5 bg-green-700
text-white text-lg rounded-xl animate-pulse">
Mint now!
</button>
<button
onClick={logout}
className="mt-5 w-full p-5 bg-red-700 text-white
text-lg rounded-xl">
Logout
</button>
</form>
</div>
);
}
export default Index;

這裡可以看到,我們多引入了一個模組”web3",因為Moralis目前只支援唯讀的合約,接著透過這行連接MataMask,const web3 = new Web3(Web3.givenProvider);。

接著填入onSummit的內容

const onSubmit = async (e) => {
e.preventDefault();
try {
// Attempt to save image to IPFS
const file1 = new Moralis.File(file.name, file);
await file1.saveIPFS();
const file1url = file1.ipfs();
// Generate metadata and save to IPFS
const metadata = {
name,
description,
image: file1url,
};
const file2 = new Moralis.File(`${name}metadata.json`,
{base64:Buffer.from(JSON.stringify(metadata)).toString("base64"),
});
await file2.saveIPFS();
const metadataurl = file2.ipfs();
// Interact with smart contract
const contract = new web3.eth.Contract(contractABI,
contractAddress);
const response = await contract.methods
.mint(metadataurl)
.send({ from: user.get("ethAddress") });
// Get token id
const tokenId = response.events.Transfer.returnValues.tokenId;
// Display alert
alert(
`NFT successfully minted. Contract address -
${contractAddress} and Token ID - ${tokenId}`
);
} catch (err) {
console.error(err);
alert("An error occured!");
}
};

首先,這裡我們透過Moralis將兩個檔案上傳到IPFS,圖片檔本身,然後將圖片位置與名稱說明等參數寫到資源描述檔,也上傳到IPFS,接著我們就可以將資源檔的位置作為參數,呼叫合約的mint函數,在MetaMask點下確認,然後就可以新增我們的NFT代幣了。

下一篇將會延續這篇,去建立一個NFT Market Place。

關於兌心科技 Insight Software

最懂你的雲端合作夥伴 — We know what you want

兌心科技的專業橫跨多個領域,雲端架構、遊戲製作、區塊鏈技術、去中心化金融、醫療資訊和物聯網服務等,致力於成為企業Web2.0到Web3.0的領航員,提供雲服務、線路、區塊鏈、金庫等解決方案。

不僅孵化區塊鏈新創,且經過嚴謹的過濾驗證,將值得信任、可靠的項目加入生態系中,進而推廣給使用者。

聯絡我們 :

官方網站 ▸ https://www.insight-software.com/

社群 ▸
Youtube
Telegram
Twitter
Instagram
HackMD

--

--