# 怎麼快速用Moralis建立一個NFT Marketplace(Next.js)- 上篇 ###### tags: `開發者路徑` ###### 兌心科技 Insight Software : 怎麼快速用Moralis建立一個NFT Marketplace(Next.js)- 上篇 # ![](https://i.imgur.com/oe6UROw.png) 隨著對 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 圖元肖像組成,具有隨機屬性,例如戴帽子人或抽煙斗人。 ![](https://i.imgur.com/fnOQJj5.png) 那麼CryptoPunks是如何使用ERC-20達到類似非同質化代幣的效果呢? 我們可以看一下它的合約片段,合約中註明發幣量為10000 ![](https://i.imgur.com/Cqtj3ty.png) 由於將圖片檔太多,因此將10000張圖片合成的圖片hash記在合約之中 ![](https://i.imgur.com/i4jAY7e.png) 然後每個圖片擁有者的位址也會紀錄在合約之後 ![](https://i.imgur.com/FzHeUt6.png) 在進行交易時會去確認每個合約的擁有者是否正確 ![](https://i.imgur.com/SgyLXac.png) 因為我們可以看到,圖片(hash)跟使用者地址都紀錄在鏈上,是確定而不能更改的,圖片及擁有者的從屬關系很明確,這樣的方式也啟發了ERC-721標準的產生,裡面定義了智能合約所必須實作及可選的函式(具體內容參考ERC-721)。 ### 基於ERC-721的NFT範例 2021年,Larva Labs的另一個基於ERC-721的專案Meebits上線(這裡可以看到合約內容),其中片段如下 ![](https://i.imgur.com/UvWese3.png) NFT相关的内容存储在IPFS(星際檔案系統,一種分布式儲存協定)之中: ![](https://i.imgur.com/vb4gpZ2.png) ![](https://i.imgur.com/lRaJF9h.png) 所以可以看到會將內容的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 ![](https://i.imgur.com/KXU74Va.png) 接著選到Deploy Tab,ENVIRONMENT選擇”Injected web3", CONTRACT選擇剛才新增的檔案,然後確認與你的MetaMask連接並選擇Ropsten測試網路,確認你的帳戶中有代幣可以支付gas fee,然後點下 “Deploy”並在MetaMask確認。 ![](https://i.imgur.com/Ax2uPzj.png) 我們可以看到合約已成功Deploy並可以與之互動 ![](https://i.imgur.com/A1U1FWm.png) ### 設定Moralis Server及Next.js專案 如何新增Moralis Server(Ropsten Testnet) 與建立Next.js專案,設定環境變數及連接MetaMask請參考上一篇。 ### 設定合約資訊 在根目錄下建立一個contract.js,然後參考Remix中的資訊 合約布署的位址(Deploy頁面),複制到參數contractAddress ![](https://i.imgur.com/w0rZPgI.png) ABI (Application Binary Interface),複制到參數contractABI ![](https://i.imgur.com/9TaOZka.png) 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時顯示如下 ![](https://i.imgur.com/oGTgdR6.png) ### 建立簡單的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代幣了。 ![](https://i.imgur.com/rx1Kpy2.png) ![](https://i.imgur.com/xjkJ9vz.png) 下一篇將會延續這篇,去建立一個NFT Market Place。 # ###### About Insight Software ###### 兌心科技的專業橫跨多個領域,雲端架構、遊戲製作、區塊鏈技術、去中心化金融、醫療資訊和物聯網服務等,致力於成為企業Web2.0到Web3.0的領航員,提供**雲服務、線路、區塊鏈技術、金庫**等解決方案。 # ### 聯絡方式 兌心聯絡信箱 : service@insight-software.com ### 社群 [Website](https://www.insight-software.com/) [Medium](https://medium.com/insight-software) [Youtube](https://www.youtube.com/@insight-software) [Telegram](https://t.me/Insight_software) [Twitter](https://twitter.com/insightcapital_) [Instagram](https://www.instagram.com/insight.capital/) ![](https://i.imgur.com/MRSPRzH.png)