---
# System prepended metadata

title: 怎麼快速用Moralis建立一個NFT Marketplace(Next.js)- 上篇
tags: [開發者路徑]

---

# 怎麼快速用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)    