如何用14行代码制作一个NFT(附实例)

661 阅读12分钟

如果你是一个对区块链开发感兴趣的开发者,你应该知道一些关于NFTs的事情,即非可兑换代币。因此,在这篇文章中,我们将了解它们背后的工程,以便你可以开始建立你自己的。

在项目结束时,你将拥有自己的以太坊钱包,里面有一个新的NFT。本教程对初学者友好,不需要任何关于以太坊网络或智能合约的知识。

image-46

NFT合约只有14行代码

什么是NFT?

NFT代表的是不可伪造的代币。来自ethereum.org的这段话很好地解释了它。

NFT是代币,我们可以用它来代表独特物品的所有权。他们让我们把艺术品、收藏品、甚至房地产等东西代币化。它们在同一时间只能有一个正式的所有者,并且由以太坊区块链保证 - 没有人可以修改所有权记录或复制/粘贴一个新的NFT存在。

什么是NFT标准或ERC-721?

ERC-721是最常见的NFT标准。如果你的智能合约实现了某些标准化的API方法,它可以被称为ERC-721非熔断代币合约。

这些方法在EIP-721中被指定。像OpenZeppelin这样的开源项目通过实现最常见的ERC标准作为一个可重复使用的库,简化了开发过程。

什么是铸币NFT?

通过铸造NFT,你在区块链上发布一个独特的代币。这个令牌是你的智能合约的一个实例。

每个令牌都有一个独特的tokenURI,它包含符合某些模式的JSON文件中的资产元数据。元数据是你存储关于你的NFT信息的地方,如名称、图像、描述和其他属性。

ERC721元数据模式 "的JSON文件的一个例子看起来是这样的:

{
	"attributes": [
		{
			"trait_type": "Shape",
			"value": "Circle"
		},
		{
			"trait_type": "Mood",
			"value": "Sad"
		}
	],
	"description": "A sad circle.",
	"image": "https://i.imgur.com/Qkw9N0A.jpeg",
	"name": "Sad Circle"
}

有三种主要方式来存储NFT的元数据。

首先,你可以在链上存储信息。换句话说,你可以扩展你的ERC-721并将元数据存储在区块链上,这可能是昂贵的。

第二种方法是使用IPFS。而第三种方法是简单地让你的API返回JSON文件。

第一种和第二种方法通常是首选,因为你不能节制底层的JSON文件。对于这个项目的范围,我们将选择第三种方法。

关于使用NFTs与IPFS的良好教程,请阅读Alchemy团队的这篇文章

我们将建立什么

emotionalshapes

在本教程中,我们将创建和铸造我们自己的NFT。它是对初学者友好的,不需要任何有关以太坊网络或智能合约的知识。不过,对这些概念的良好掌握将有助于你了解幕后的情况。

在即将到来的教程中,我们将建立一个全功能的React网络应用,在那里你可以展示和出售你的NFT。

这个项目是故意用容易理解的代码编写的,不适合生产使用。

先决条件

Metamask

image-32

我们需要一个Ethereum地址来与我们的智能合约互动。我们将使用Metamask作为我们的钱包。它是一个免费的虚拟钱包,可以管理你的Ethereum地址。我们将需要它来发送和接收交易(在这里阅读更多相关内容)。例如,铸造一个NFT就是一个交易。

下载他们的Chrome扩展和他们的移动应用程序。我们将需要这两个,因为Chrome扩展程序不显示你的NFT。

image-34

确保将网络改为 "Ropsten测试网络",用于开发。你将需要一些Eth来支付部署和铸造你的NFT的费用。前往Ropsten Ethereum Faucet并输入你的地址。你应该很快在你的Metamask账户中看到一些测试Eth。

image-35

炼金术

为了与以太坊网络互动,你将需要连接到一个以太坊节点。

运行你自己的Node和维护基础设施是一个独立的项目。幸运的是,有一些节点即服务的供应商为你托管基础设施。有很多选择,如Infura、BlockDaemon和Moralis。我们将使用Alchemy作为我们的节点供应商。

前往他们的网站,创建一个账户,选择Ethereum作为你的网络,并创建你的应用程序。选择Ropsten作为你的网络。

image-36

在你的仪表板上,点击你的应用程序的 "查看详情",然后点击 "查看密钥"。把你的http密钥保存在某个地方,因为我们以后会需要它。

image-38

NodeJS/NPM

我们将在该项目中使用NodeJS。如果你没有安装它,请遵循freeCodeCamp的这个简单教程

初始化该项目

在你的终端,运行这个命令,为你的项目建立一个新的目录:

mkdir nft-project
cd nft-project

现在,让我们在nft-project/ 内建立另一个目录,ethereum/ ,并使用Hardhat初始化它。Hardhat是一个开发工具,它使部署和测试你的Ethereum软件变得容易:

mkdir ethereum
cd ethereum
npm init

无论你怎么回答问题。然后,运行这些命令来制作一个Hardhat项目:

npm install --save-dev hardhat
npx hardhat

你会看到这个提示:

888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

Welcome to Hardhat v2.0.8

? What do you want to do? …
  Create a sample project
❯ Create an empty hardhat.config.js
  Quit

选择创建一个空的hardhat.config.js。这将生成一个空的hardhat.config.js 文件,我们稍后会更新。

对于网络应用,我们将使用Next.js来初始化一个全功能的网络应用。回到根目录nft-project/ ,初始化一个名为web的Next.js模板应用程序:

cd ..
mkdir web
cd web
npx create-next-app@latest

你的项目现在看起来像这样:

nft-project/
	ethereum/
	web/

棒极了!我们已经准备好进入一些真正的编码了。

如何定义我们的.env变量

还记得我们之前从测试项目中获取的Alchemy密钥吗?我们将使用它与我们Metamask账户的公钥和私钥一起与区块链互动。

运行以下命令,在你的ethereum/ 目录中建立一个名为.env 的文件,并安装dotenv。我们将在后面使用它们:

cd ..
cd ethereum
touch .env
npm install dotenv --save

对于你的.env 文件,放上你从Alchemy导出的密钥,并按照这些指示来抓取你的Metamask的私钥。

这是你的.env文件:

DEV_API_URL = YOUR_ALCHEMY_KEY
PRIVATE_KEY = YOUR_METAMASK_PRIVATE_KEY
PUBLIC_KEY = YOUR_METAMASK_ADDRESS

NFTs的智能合约

转到ethereum/ 文件夹,再创建两个目录:合同和脚本。一个简单的硬帽项目包含这些文件夹。

  • contracts/ 包含你的合约的源文件
  • scripts/ 包含用于部署和铸造我们的NFTs的脚本
mkdir contracts
mkdir scripts

然后,安装OpenZeppelin。OpenZeppelin合约是一个开源的库,有预先测试过的可重用代码,使智能合约开发更容易:

npm install @openzeppelin/contracts

最后,我们将为我们的NFT编写智能合约。导航到你的合约目录,创建一个名为EmotionalShapes.sol 的文件。您可以以您认为合适的方式命名您的NFT。

.sol 后缀指的是Solidity语言,我们将用它来为我们的智能合约编程。我们将只用Solidity编写14行代码,所以如果你以前没有见过,不用担心。

这篇文章开始,了解更多关于智能合约语言的信息。你也可以直接跳到这个包含主要语法的Solidity小抄

cd contracts
touch EmotionalShapes.sol

这就是我们的智能合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract EmotionalShapes is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;

    constructor() ERC721("EmotionalShapes", "ESS") {}

    function _baseURI() internal pure override returns (string memory) {
        return "YOUR_API_URL/api/erc721/";
    }

    function mint(address to)
        public returns (uint256)
    {
        require(_tokenIdCounter.current() < 3); 
        _tokenIdCounter.increment();
        _safeMint(to, _tokenIdCounter.current());

        return _tokenIdCounter.current();
    }
}

让我们浏览一下代码,了解一下正在发生的事情:

  1. 在文件的顶部,我们指定了要导入哪个OpenZeppelin模块。我们需要ERC721合约,因为它是我们智能合约的 "基础"。它已经实现了EIP-721中规定的所有方法,所以我们可以安全地使用它。
  2. 计数器对于为我们的NFT生成增量ID很有用。我们将该变量命名为_tokenIdCounter
  3. 在构造函数中,我们用它的名字和符号初始化了我们的ERC721。我选择了EmotionalShapes和ESS。
  4. 我们通过返回我们自己的函数来覆盖默认的_baseURI 。我们将在一秒钟内建立这个函数。总之,它是将作为 "前缀 "添加到我们所有tokenURIs中的URL。在上面的例子中,我们的NFT的元数据将存在于一个JSON文件中,地址是 YOUR_API_URL/[api/erc721/](https://e110-99-121-58-31.ngrok.io/api/erc721/)1.
  5. 我们实现了'mint'函数:它是让你在区块链上发布这个智能合约实例的函数。我要求_tokenIdCounter 变量小于3,因为我将只创建我的NFT的三个实例。如果你想铸币更多,你可以去掉这一点。
  6. 最后,在铸币函数里面,我们将_tokenIdCounter 变量递增1,所以我们的id将是1,接着是2,接着是3。然后,我们调用OpenZeppelin提供的函数_safeMint ,发布代币。

如果你感到迷茫,不要担心。你可以参加由freeCodeCamp的志愿者领导的研讨会,在那里我们邀请技术水平相似的开发者一起构建东西,包括这个NFT项目。

这些活动是免费的,而且是远程的,所以你可以直接问任何问题。你可以在这里注册。座位是有限的,所以你会被邀请参加下一次的活动。

如前所述,有三种主要方式来存储你的tokenURI。我们将建立一个简单的API端点,将我们的NFT的信息解析为JSON。

我们的Next.js项目为我们提供了一种开发API路由的方便方法。进入web/ 文件夹,在pages/ 文件夹中找到api/ 文件夹,并在erc721/ 文件夹中建立我们的动态[id].js 路由(在这里阅读更多关于路由的内容)。

// web/pages/api/erc721/[id].js

const metadata = {
  1: {
    attributes: [
      {
        trait_type: "Shape",
        value: "Circle",
      },
      {
        trait_type: "Mood",
        value: "Sad",
      },
    ],
    description: "A sad circle.",
    image: "https://i.imgur.com/Qkw9N0A.jpeg",
    name: "Sad Circle",
  },
  2: {
    attributes: [
      {
        trait_type: "Shape",
        value: "Rectangle",
      },
      {
        trait_type: "Mood",
        value: "Angry",
      },
    ],
    description: "An angry rectangle.",
    image: "https://i.imgur.com/SMneO6k.jpeg",
    name: "Angry Rectangle",
  },
  3: {
    attributes: [
      {
        trait_type: "Shape",
        value: "Triangle",
      },
      {
        trait_type: "Mood",
        value: "Bored",
      },
    ],
    description: "An bored triangle.",
    image: "https://i.imgur.com/hMVRFoJ.jpeg",
    name: "Bored Triangle",
  },
};

export default function handler(req, res) {
  res.status(200).json(metadata[req.query.id] || {});
}

为了这个项目,我尽可能地使代码容易理解。这绝对不适合用于生产(请不要为你的NFT使用Imgur网址)。请确保为所有你打算铸币的NFT定义元数据。

现在,进入Web目录,用这个命令启动你的Next.js应用程序:

npm run dev

你的应用程序应该在localhost:3000上运行。为了确保我们的端点工作,去http://localhost:3000/api/erc721/1,它应该用你的第一个NFT元数据的JSON对象来解析。

由于你的应用程序被托管在本地,其他应用程序不能访问它。使用像ngrok这样的工具,我们可以将我们的本地主机暴露给一个可公开访问的URL。

image-39

  1. 前往ngrok.com并完成注册过程
  2. 解压下载的软件包
  3. 在你的终端,确保你cd到你解压ngrok包的文件夹中。
  4. 按照你的仪表盘上的指示,运行
./ngrok authtoken YOUR_AUTH_TOKEN

5.然后,运行这个命令,创建一个隧道到你的网络应用,托管在localhost:3000上

./ngrok http 3000

6.你就快成功了!在你的终端上,你应该看到像这样的东西:

ngrok by @inconshreveable                                                                            (Ctrl+C to quit)
                                                                                                                     
Session Status                online                                                                                 
Account                       YOUR_ACCOUNT (Plan: Free)                                                                       
Version                       2.3.40                                                                                 
Region                        United States (us)                                                                     
Web Interface                 http://127.0.0.1:4040                                                                  
Forwarding                    http://YOUR_NGROK_ADDRESS -> http://localhost:3000                             
Forwarding                    https://YOUR_NGROK_ADDRESS -> http://localhost:3000                             

转到YOUR_NGROK_ADDRESS/api/erc721/1 ,以确保你的端点工作正常。

如何部署我们的NFT

现在我们已经完成了所有的基础工作(oof),让我们回到我们的ethereum/ 文件夹,准备部署我们的NFT。

改变你的ethreum/contracts/YOUR_NFT_NAME.sol 文件中的_baseURI 函数 ,以返回你的ngrok地址:

// ethereum/conrtacts/EmotionalShapes.sol

contract EmotionalShapes is ERC721 {
...
	function _baseURI() internal pure override returns (string memory) {
		return "https://YOUR_NGROK_ADDRESS/api/erc721/";
	}
...
}

为了部署我们的NFT,我们首先需要使用Hardhat编译它。为了使这个过程更容易,我们将安装ethers.js

npm install @nomiclabs/hardhat-ethers --save-dev

让我们更新我们的hardhat.config.js:

require("dotenv").config();
require("@nomiclabs/hardhat-ethers");

module.exports = {
  solidity: "0.8.0",
  defaultNetwork: "ropsten",
  networks: {
    hardhat: {},
    ropsten: {
      url: process.env.DEV_API_URL,
      accounts: [`0x${process.env.PRIVATE_KEY}`],
    },
  },
};

要了解更多关于hardhat配置文件的信息,请看一下他们的文档。我们已经用我们的Alchemy URL配置了ropsten网络,并向它提供了你的metamask账户的私钥。

最后,运行:

npx hardhat compile

这让hardhat为每个编译的合同生成两个文件。我们应该看到一个新创建的artifacts/ 文件夹,其中包含你在contracts/ 文件夹中的编译合同。要了解更多关于如何工作的信息,请阅读Hardhat团队的这个教程

现在,让我们写一个脚本,最终将我们的NFT部署到测试网络上。在你的scripts/ 文件夹中,创建一个名为deploy.js 的文件:

// ethereum/scripts/deploy.js

async function main() {
  const EmotionalShapes = await ethers.getContractFactory("EmotionalShapes");
  const emotionalShapes = await EmotionalShapes.deploy();

  console.log("EmotionalShapes deployed:", emotionalShapes.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

这段代码的灵感来自hardhat部署教程

ethers.js中的ContractFactory 是一个用于部署新智能合约的抽象,所以这里的EmotionalShapes 是我们的代币合约实例的工厂。在一个ContractFactory 上调用deploy() 将开始部署,并返回一个Promise ,它可以解析为一个Contract 。这是为你的每个智能合约功能提供方法的对象。

如何查看区块链上的NFT

运行部署脚本:

node ./scripts/deploy.js

你应该在你的终端看到EmotionalShapes deployed: SOME_ADDRESS 。这是你的智能合约在ropsten测试网络上部署的地址。

如果你前往https://ropsten.etherscan.io/address/SOME_ADDRESS ,你应该看到你新鲜部署的NFT。是的!你成功了!

如果你在教程的某个地方卡住了,或者感到迷茫,你可以加入我们的现场研讨会,我们将在Zoom电话中一起建立这个项目。

如何铸造你的NFT

现在你已经部署了你的NFT,是时候为你自己造币了!在你的scripts/文件夹中创建一个名为mint.js 的新文件。我们将使用ethers.js来帮助我们。

首先添加ethers.js 包:

npm install --save ethers

然后,填充到mint.js 文件中:

require("dotenv").config();
const { ethers } = require("ethers");

const contract = require("../artifacts/contracts/EmotionalShapes.sol/EmotionalShapes.json");
const contractInterface = contract.abi;

// https://docs.ethers.io/v5/api/providers
const provider = ethers.getDefaultProvider("ropsten", {
  alchemy: process.env.DEV_API_URL,
});

// https://docs.ethers.io/v5/api/signer/#Wallet
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

//https://docs.ethers.io/v5/api/contract/contract
const emotionalShapes = new ethers.Contract(
  YOUR_NFT_ADDRESS,
  contractInterface,
  wallet
);

const main = () => {
  emotionalShapes
    .mint(process.env.PUBLIC_KEY)
    .then((transaction) => console.log(transaction))
    .catch((e) => console.log("something went wrong", e));
};

main();

我已经留下了注释,在那里你可以找到关于不同方法的更多信息。我们首先抓住合约的接口(ABI)。来自ethereum.org。

应用二进制接口,或ABI,是在以太坊生态系统中与合约互动的标准方式,包括从区块链外部和合约与合约之间的互动。

你的ABI定义了其他人如何与你的合约互动。然后,我们用Alchemy创建了我们的提供者(记得关于节点即服务)。最后,我们用我们的私钥初始化我们的钱包。

main() 函数调用我们刚刚部署的智能合约中的mint 方法。mint 方法只需要一个参数,即to ,它表示代币的接收者。由于我们是为自己造币,我们把我们的Metamask账户的公共地址放进去。

如果一切顺利,你应该看到交易记录在你的终端上。抓住hash 属性并转到https://ropsten.etherscan.io/tx/YOUR_HASH 。你应该在那里看到铸币交易!

你需要先下载移动版的Metamask。然后,登录你的账户。

你应该看到一个NFTs标签,以及一个添加NFT的按钮。点击该按钮,并输入你的智能合约的地址,以及你所铸造的ID。如果你已经遵循了教程,你应该从一个ID开始,1

IMG_0376

在你的Metamask钱包中查看NFTs

总结

恭喜你!你刚刚铸造了你自己的钱包。你刚刚铸造了你自己的NFT。在项目的下一部分,我们将建立前端的React应用程序,与我们的合同互动。最终的目标是建立一个功能齐全的网络应用,在那里你可以出售你自己的NFTs。

最后,你可以参加我们与来自freeCodeCamp的志愿者的现场研讨会,在那里我们将与其他开发者一起建立这个项目。

这些活动对全世界的人都是免费的,邀请函是先到先得的。如果你想领导研讨会,请在Twitter上给我发信息,我们很欢迎你的加入我们还组织其他类型的活动,如招聘会和社交聚会。

让我知道你想建立什么。NFTs仍处于起步阶段,我们非常欢迎新颖的想法。迫不及待地想看看你有什么疯狂的想法!