---
title: "NFT minter 教程：如何创建全栈 dApp"
description: "通过我们全面的 NFT Minter 教程，探索 NFT 的世界。轻松学习创建全栈 dApp，立即开始铸造你的数字资产！"
---

# NFT minter 教程：如何创建全栈 dApp

在本教程中，你将构建一个 NFT 铸造器，学习如何使用 Metamask 和 Web3 工具将智能合约连接到 React 前端，从而创建一个全栈 dApp。

此外，别忘了看看我们 NFT 教程系列中的其他文章：

- 🌟  [如何使用 Web3.js 铸造 NFT](https://www.alchemy.com/blog/how-to-mint-an-nft-using-web3-js)
- 🪄  [如何使用 Ether.js 铸造 NFT](https://www.alchemy.com/blog/how-to-mint-an-nft-with-ethers-js)
- 👛  [如何在手机钱包中查看你的 NFT](https://www.alchemy.com/blog/how-to-view-your-nft-in-your-mobile-wallet)
- 💸  [如何为 NFT 设置价格](https://www.alchemy.com/blog/how-to-set-a-price-on-an-nft)

对于来自 Web2 背景的开发者来说，最大的挑战之一就是搞清楚如何将智能合约连接到前端项目并与其交互。

通过构建一个 NFT 铸造器——一个简单的 UI，你可以在其中输入数字资产的链接、标题和描述——你将学习如何：

- 通过前端项目连接 Metamask
- 从前端调用智能合约方法
- 使用 Metamask 签署交易

在本教程中，我们将使用 [React](https://reactjs.org/) 作为前端框架。由于本教程主要专注于 [Web3 开发](https://www.alchemy.com/overviews/how-to-learn-web3-development)，我们不会花太多时间讲解 React 的基础知识，而是专注于为项目添加功能。

<CalloutBlock>

作为前提条件，你应该具备初级水平的 React 理解——知道组件、props、useState/useEffect 以及基本的函数调用是如何工作的。如果你从未听说过这些术语，可以先看看这篇 [React 入门教程](https://reactjs.org/tutorial/tutorial.html)。对于更偏好视觉学习的人，我们强烈推荐 Net Ninja 出品的这套优秀的 [Full Modern React Tutorial](https://www.youtube.com/playlist?list=PL4cUxeGkcC9gZD-Tvwfod2gaISzfRiP9d) 视频系列。

</CalloutBlock>

废话不多说，让我们开始吧！

## 第 0 步：制作 NFT 101

在开始看代码之前，先理解制作 NFT 的原理很重要。它包含两个步骤：

1\) 你在 Ethereum 区块链上发布一个 NFT 智能合约。通常这是一个 [ERC-721](https://eips.ethereum.org/EIPS/eip-721) 或 [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155) 智能合约。

<CalloutBlock>

这两种 NFT 智能合约标准最大的区别在于，ERC-1155 是一种多代币标准，包含批量功能；而 ERC-721 是单代币标准，因此每次只能转移一个代币。

</CalloutBlock>

2\) 你调用该 NFT 智能合约上的铸造函数来铸造 NFT。*铸造*就是在区块链上发布你的非同质化代币的唯一实例这一行为。

通常，这个铸造函数要求你传入两个变量作为参数：第一个是接收者，指定将接收你新铸造的 NFT 的地址；第二个是 NFT 的 tokenURI，一个解析为描述 NFT 元数据的 JSON 文档的字符串。

<CalloutBlock>

NFT 的元数据才是真正赋予其生命的东西，让它拥有名称、描述、图片（或其他数字资产）等属性。这里有[一个 tokenURI 的示例](https://gateway.pinata.cloud/ipfs/QmSvBcb4tjdFpajGJhbFAWeK3JAxCdNQLQtr6ZdiSi42V2)，其中包含了一个 NFT 的元数据。

</CalloutBlock>

在本教程中，我们将专注于第 2 部分——使用我们的 React UI 调用一个已有 NFT 智能合约的铸造函数。

[这里](https://sepolia.etherscan.io/address/0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE)是我们在本教程中将要调用的 ERC-721 NFT 智能合约的链接。如果你想了解我们是如何制作它的，强烈推荐你查看我们的另一篇教程《[如何创建一个 NFT](https://www.alchemy.com/blog/how-to-create-an-nft)》。

好了，既然我们已经了解了制作 NFT 的原理，那就克隆一下我们的初始文件吧！

## 第 1 步：克隆初始文件

首先，前往 [nft-minter-tutorial](https://github.com/alchemyplatform/nft-minter-tutorial) github 仓库获取该项目的初始文件。将该仓库克隆到你的本地环境。

<CalloutBlock>

不知道如何克隆仓库？查看 Github 的[这篇指南](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository)。

</CalloutBlock>

打开克隆下来的 **nft-minter-tutorial** 仓库后，你会注意到它包含两个文件夹：**minter-starter-files** 和 **nft-minter**。

- **minter-starter-files** 包含本项目的初始文件（本质上是 React UI）。在本教程中，**我们将在这个目录中工作**，学习如何通过将其连接到 Ethereum 钱包和 NFT 智能合约，把这个 UI 变成可用的产品。
- **nft-minter** 包含整个完成版的教程，**如果你遇到困难**，可以作为**参考**。

接下来，在你喜欢的代码编辑器中打开你的 **minter-starter-files** 副本（在 Alchemy，我们很喜欢用 [VSCode](https://code.visualstudio.com/download)），然后进入 **src** 文件夹：

我们将在 "src" 文件夹内工作

<ImageBlock
  src="https://media.alchemy.com/1704629644-open-minter-starter-files-in-editor.png"
  alt="VSCode 中打开了 minter-starter-files 项目，显示 src 文件夹"
  width={2880}
  height={1798}
  caption="在你选择的编辑器中打开 minter-starter-files"
/>

我们编写的所有代码都将放在 **src** 文件夹下。我们将编辑 **Minter.js** 组件，并编写额外的 javascript 文件，为项目赋予 Web3 功能。

## 第 2 步：查看初始文件

在开始编码之前，先看看初始文件里已经为我们提供了哪些内容，这很重要。

#### 让你的 react 项目运行起来

我们先在浏览器中运行 React 项目。React 的好处在于，一旦项目在浏览器中运行起来，保存的任何改动都会实时更新到浏览器中。

要让项目运行起来，进入 **minter-starter-files** 文件夹的根目录，然后在终端运行 `npm install` 来安装项目的依赖：

安装完成后，在终端运行 `npm start`：

这样应该会在浏览器中打开 [http://localhost:3000/](http://localhost:3000/)，你会在这里看到项目的前端界面。它应该包含 3 个字段：输入 NFT 资产链接的地方、输入 NFT 名称的地方，以及输入描述的地方。

<ImageBlock
  src="https://media.alchemy.com/1704629829-frontend-for-our-project.png"
  alt="NFT 铸造前端，包含资产链接、名称和描述字段"
  width={2876}
  height={1606}
  caption="项目 UI 应有的样子"
/>

如果你尝试点击 "Connect Wallet" 或 "Mint NFT" 按钮，会发现它们不起作用——这是因为我们还没有编写它们的功能实现！:\)

#### minter.js 组件

<CalloutBlock>

**注意：**确保你在 **minter-starter-files** 文件夹里，而不是 **nft-minter** 文件夹！

</CalloutBlock>

让我们回到编辑器中的 **src** 文件夹，打开 **Minter.js** 文件。理解这个文件中的所有内容非常重要，因为它是我们要处理的主要 React 组件。

在这个文件的顶部，我们有一些状态变量，这些变量会在特定事件后被更新。

<CalloutBlock>

没听说过 React 状态变量或 state hooks？查看[这些](https://reactjs.org/docs/hooks-state.html)文档。

</CalloutBlock>

下面是各个变量代表的含义：

- **walletAddress** - 一个存储用户钱包地址的字符串
- **status** - 一个包含要在 UI 底部显示的消息的字符串
- **name** - 一个存储 NFT 名称的字符串
- **description** - 一个存储 NFT 描述的字符串
- **url** - 一个指向 NFT 数字资产的链接字符串

在状态变量之后，你会看到三个尚未实现的函数：`useEffect`、`connectWalletPressed` 和 `onMintPressed`。你会注意到这些函数都是 **async** 的，因为我们将在其中发起异步 API 调用！它们的名称与其功能是对应的：

- [**useEffect**](https://reactjs.org/docs/hooks-effect.html)——这是一个 React hook，在组件渲染后被调用。由于它传入了一个空数组 **\[\]** 作为 prop（见第 3 行），它只会在组件的*首次*渲染时被调用。这里我们会调用钱包监听器和另一个钱包函数，用来更新 UI 以反映钱包是否已经连接。
- **connectWalletPressed** - 这个函数会在需要将用户的 Metamask 钱包连接到我们的 dApp 时被调用。
- **onMintPressed** - 这个函数会在需要铸造用户的 NFT 时被调用。

在这个文件靠近末尾的地方，是我们组件的 UI。如果你仔细扫一遍这段代码，会注意到当对应文本字段的输入发生变化时，我们会更新 `url`、`name` 和 `description` 这几个状态变量。

你还会看到，当 ID 为 **mintButton** 和 **walletButton** 的按钮被点击时，会分别调用 `connectWalletPressed` 和 `onMintPressed`。

最后，让我们看看这个 Minter 组件是在哪里被添加的。

如果你打开 **App.js** 文件——它是 React 中作为所有其他组件容器的主组件——你会看到我们的 Minter 组件在第 7 行被引入。

**在本教程中，我们只会编辑 Minter.js 文件，并在 src 文件夹中添加文件。**

现在我们已经了解了要处理的内容，接下来设置一下我们的 Ethereum 钱包吧！

## 第 3 步：设置你的 Ethereum 钱包

为了让用户能够与你的智能合约交互，他们需要将自己的 Ethereum 钱包连接到你的 dApp。

#### 下载 MetaMask

在本教程中，我们将使用 Metamask，一个浏览器中的虚拟钱包，用于管理你的 Ethereum 账户地址。如果你想更深入了解 Ethereum 上的交易是如何运作的，可以查看 Ethereum foundation 的[这个页面](https://ethereum.org/en/developers/docs/transactions/)。

你可以在[这里](https://metamask.io/download.html)免费下载并创建一个 Metamask 账户。在创建账户时，或者如果你已经有账户，请确保在右上角切换到 "Ropsten Test Network"（这样我们就不会用到真实的资金）。

<ImageBlock
  src="https://media.alchemy.com/1704630094-sample-metamask-wallet.png"
  alt="MetaMask 钱包已切换到 Ropsten 测试网络"
  width={712}
  height={1194}
  caption="MetaMask 钱包示例"
/>

#### 从水龙头获取 ether

为了铸造我们的 NFT（或在 Ethereum 区块链上签署任何交易），我们需要一些测试用的 Eth。要获取 Eth，可以前往 [Sepolia faucet](/faucets/ethereum-sepolia)，输入你的 Ropsten 账户地址，然后点击 "Send Ropsten Eth"。之后不久你就应该能在 Metamask 账户中看到 Eth 了！

#### 检查你的余额

为了再次确认余额确实到账，我们来用 [Alchemy 的 composer 工具](https://www.alchemy.com/composer/?composer_state=%7B%22network%22%3A0%2C%22methodName%22%3A%22eth_getBalance%22%2C%22paramValues%22%3A%5B%22%22%2C%22latest%22%5D%7D)发起一个 `eth_getBalance` 请求。这会返回我们钱包中的 Eth 数量。在输入你的 Metamask 账户地址并点击 "Send Request" 后，你应该会看到类似这样的响应：

<CalloutBlock>

**注意：**这个结果的单位是 wei，而不是 eth。wei 是 ether 的最小单位。wei 与 eth 的换算关系是：1 eth = 10¹⁸ wei。所以如果我们把 0xde0b6b3a7640000 转换成十进制，得到的是 1\*10¹⁸，也就是 1 eth。

</CalloutBlock>

呼！我们的测试资金都到账了！🤑

## 第 4 步：将 MetaMask 连接到你的 UI

现在我们的 Metamask 钱包已经设置好了，接下来把 dApp 连接到它！

由于我们希望遵循 [M-V-C](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) 范式，我们将创建一个单独的文件，用来存放管理 dApp 逻辑、数据和规则的函数，然后再将这些函数传递给我们的前端（也就是 Minter.js 组件）。

#### connectWallet 函数

为此，让我们在 **src** 目录中新建一个名为 **utils** 的文件夹，并在其中添加一个名为 **interact.js** 的文件，该文件将包含我们所有钱包和智能合约交互相关的函数。

在 **interact.js** 文件中，我们将编写一个 `connectWallet` 函数，之后会在 **Minter.js** 组件中导入并调用它。

在你的 **interact.js** 文件中，添加以下内容

我们来拆解一下这段代码的作用：

首先，我们的函数检查浏览器中是否启用了 `window.ethereum`。

<CalloutBlock>

window.ethereum 是 Metamask 以及其他钱包提供商注入的一个全局 API，允许网站请求用户的 Ethereum 账户。如果获得批准，它可以从用户连接的区块链中读取数据，并建议用户对消息和交易进行签名。查看 [Metamask 文档](https://docs.metamask.io/guide/ethereum-provider.html#table-of-contents)了解更多信息！

</CalloutBlock>

如果 `window.ethereum` *不*存在，那说明 Metamask 没有安装。这会导致返回一个 JSON 对象，其中返回的 **address** 是一个空字符串，而 **status** JSX 对象会提示用户必须安装 Metamask。

<CalloutBlock>

**我们编写的大多数函数都会返回 JSON 对象，我们可以用它们来更新状态变量和 UI。**

</CalloutBlock>

现在如果 `window.ethereum ` *确实*存在，那事情就变得有意思了。

我们会使用一个 try/catch 循环，尝试通过调用 [`window.ethereum.request\(\{ method: "eth\_requestAccounts" \}\);`](https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts)` ` 来连接 Metamask。调用这个函数会在浏览器中打开 Metamask，用户会被提示将钱包连接到你的 dApp。

- 如果用户选择连接，`method: "eth_requestAccounts"` 会返回一个数组，其中包含所有连接到该 dApp 的用户账户地址。总体来说，我们的 `connectWallet` 函数会返回一个 JSON 对象，其中包含该数组中的*第一个* `address`（见第 9 行）以及一个提示用户向智能合约写入消息的 `status` 消息。
- 如果用户拒绝连接，那么 JSON 对象中返回的 `address` 会是空字符串，而 `status` 消息则会反映用户拒绝了连接。

#### 将 connectWallet 函数添加到你的 minter.js UI 组件

既然我们已经写好了这个 `connectWallet` 函数，接下来把它连接到我们的 **Minter.js** 组件。

首先，我们需要在 **Minter.js** 文件顶部添加导入语句，把这个函数导入进来。

在 Minter.js 文件顶部加上 `import \{ connectWallet \} from "./utils/interact.js"`**;**。此时 **Minter.js** 的前 11 行应该是这样的：

然后，在我们的 `connectWalletPressed` 函数内部，我们会调用导入的 `connectWallet` 函数，像这样：

注意到了吗？我们大部分的功能都已经从 **Minter.js** 组件中抽离出来，放到了 **interact.js** 文件里。这正是为了遵循 M-V-C 范式！

在 `connectWalletPressed` 中，我们只是对导入的 `connectWallet` 函数进行了一次 await 调用，然后根据其响应，通过状态钩子更新 `status` 和 `walletAddress` 变量。

现在，保存这两个文件（**Minter.js** 和 **interact.js**），测试一下我们目前的 UI。

在浏览器中打开 [http://localhost:3000/](http://localhost:3000/) 页面，然后点击页面右上角的 "Connect Wallet" 按钮。

如果你安装了 Metamask，应该会被提示将钱包连接到 dApp。接受这个连接请求。

你应该会看到钱包按钮现在显示你的地址已连接！太棒了 🔥

接下来，试着刷新页面……奇怪的是，尽管钱包已经连接，我们的钱包按钮仍然在提示我们连接 Metamask……

<ImageBlock
  src="https://media.alchemy.com/1704630515-the-problem-on-page-reload.gif"
  alt="页面重新加载后，钱包按钮错误地提示重新连接 MetaMask"
  width={946}
  height={480}
  caption="页面重新加载时出现的问题"
/>

不过别担心！我们只需实现一个叫 `getCurrentWalletConnected` 的函数就能轻松解决，它会检查是否已经有地址连接到我们的 dApp，并相应地更新 UI！

#### getCurrentWalletConnected 函数

在你的 **interact.js** 文件中，添加以下 `getCurrentWalletConnected` 函数：

这段代码和我们刚才写的 `connectWallet` 函数*非常*相似。

主要区别在于，这里我们调用的不是会打开 Metamask 让用户连接钱包的 `eth_requestAccounts` 方法，而是调用 `eth_accounts` 方法，它只是简单地返回一个数组，包含当前连接到我们 dApp 的 Metamask 地址。

要看到这个函数的效果，我们把它放到 **Minter.js** 组件的 `useEffect` 函数中调用。

和我们处理 `connectWallet` 时一样，我们必须从 **interact.js** 文件中把这个函数导入到 **Minter.js** 文件里，像这样：

现在，我们只需在 `useEffect` 函数中调用它：

注意，我们利用对 `getCurrentWalletConnected` 调用的响应来更新 `walletAddress` 和 `status` 状态变量。

添加完这段代码后，试着刷新浏览器窗口。按钮应该会显示你已连接，并展示你已连接钱包地址的预览——即使刷新之后也是如此！😅

#### 实现 addWalletListener

我们 dApp 钱包设置的最后一步，是实现钱包监听器，这样当钱包状态发生变化时（比如用户断开连接或切换账户），UI 就能相应更新。

在你的 **Minter.js** 文件中，添加一个函数 `addWalletListener`，它看起来像这样：

我们快速拆解一下这里发生了什么：

首先，我们的函数检查 `window.ethereum` 是否启用（即是否安装了 Metamask）。

- 如果没有启用，我们只需将 `status` 状态变量设置为一个 JSX 字符串，提示用户安装 Metamask。
- 如果启用了，我们会在第 3 行设置监听器 `window.ethereum.on("accountsChanged")`，用来监听 Metamask 钱包中的状态变化，包括用户向 dApp 连接了额外账户、切换账户，或断开某个账户的连接。如果至少有一个账户处于连接状态，`walletAddress` 状态变量会被更新为监听器返回的 `accounts` 数组中的第一个账户。否则，`walletAddress` 会被设置为空字符串。

最后，我们必须在 `useEffect` 函数中调用它：

大功告成！我们已经完成了所有钱包功能的编程！既然我们的钱包已经设置好了，接下来看看如何铸造我们的 NFT！

## 第 5 步：NFT 元数据 101

还记得我们在本教程第 0 步中提到的 NFT 元数据吗——它赋予了 NFT 生命，使其拥有数字资产、名称、描述等属性。

我们需要将这个元数据配置为一个 JSON 对象并存储起来，这样在调用智能合约的 `mintNFT` 函数时，就可以将它作为 `tokenURI` 参数传入。

"Link to Asset"、"Name"、"Description" 这几个字段中的文本，将构成我们 NFT 元数据的不同属性。我们会将这些元数据格式化为一个 JSON 对象，但关于把这个 JSON 对象存储在哪里，我们有几个选择：

- 我们可以把它存储在 Ethereum 区块链上；但由于 Ethereum 的特性，这样做会非常昂贵（成本可能高达数百美元）。❌
- 我们可以把它存储在像 AWS 或 Firebase 这样的中心化服务器上。但这样就违背了我们去中心化的理念。❌
- 我们可以使用 IPFS，一种去中心化协议和点对点网络，用于在分布式文件系统中存储和共享数据。由于这个协议是去中心化且免费的，它是我们的最佳选择！✅

为了将元数据存储到 IPFS 上，我们将使用 [Pinata](https://pinata.cloud/)，一个方便的 IPFS API 和工具集。在下一步中，我们会具体解释该怎么做！

## 第 6 步：使用 Pinata 将你的元数据固定（pin）到 IPFS

如果你还没有 Pinata 账户，可以在[这里](https://app.pinata.cloud/register)免费注册一个账户，并完成邮箱和账户验证的步骤。

#### 创建你的 Pinata API key

前往 [https://app.pinata.cloud/developers/api-keys](https://app.pinata.cloud/developers/api-keys) 页面，然后点击顶部的 "New Key" 按钮，将 Admin 选项设置为启用，并为你的 key 命名。

<ImageBlock
  src="https://media.alchemy.com/1704630785-create-your-pinata-api-key.png"
  alt="Pinata API 密钥页面：启用 Admin 选项创建新密钥"
  width={2880}
  height={1644}
  caption="创建你的 Pinata API 密钥"
/>

之后会弹出一个包含你的 API 信息的弹窗。请务必把它保存在安全的地方。

<ImageBlock
  src="https://media.alchemy.com/1704630855-save-your-api-key-and-secret.png"
  alt="请务必妥善保存你的 API 密钥和 Secret"
  width={2880}
  height={1650}
  caption="请务必妥善保存你的 API 密钥和 Secret"
/>

既然我们的 key 已经设置好了，接下来把它添加到项目中以便使用。

#### 创建一个 .env 文件

我们可以把 Pinata 的 key 和 secret 安全地存储在一个环境文件中。让我们在项目目录中安装 [dotenv 包](https://www.npmjs.com/package/dotenv)。

在终端中打开一个新标签页（与运行本地服务的标签页分开），确保你处于 **minter-starter-files** 文件夹中，然后在终端运行以下命令：

接下来，通过在命令行输入以下内容，在 **minter-starter-files** 的根目录中创建一个 **.env** 文件：

这会用 vim（一个文本编辑器）打开你的 **.env** 文件。要保存并退出，请依次按下键盘上的 "esc" \+ ":" \+ "q"。

接下来，在 VSCode 中打开你的 .env 文件，把你的 Pinata API key 和 API secret 添加进去，像这样：

保存文件，然后就可以开始编写将 JSON 元数据上传到 IPFS 的函数了！

#### 实现 pinJSONToIPFS

幸运的是，Pinata 提供了一个[专门用于将 JSON 数据上传到 IPFS 的 API](https://docs.pinata.cloud#PinJSONToIPFS)，还有一个使用 axios 的便捷 JavaScript 示例，我们可以拿来稍作修改直接使用。

在你的 utils 文件夹中，创建另一个名为 pinata.js 的文件，然后像这样从 .env 文件中导入我们的 Pinata secret 和 key：

接下来，把下面的额外代码粘贴到你的 pinata.js 文件中。别担心，我们会解释每部分的含义！

那么这段代码究竟做了什么呢？

首先，它导入了 [axios](https://www.npmjs.com/package/axios)，一个基于 promise 的浏览器和 node.js HTTP 客户端，我们将用它向 Pinata 发起请求。

然后我们有一个异步函数 `pinJSONToIPFS`，它接收一个 `JSONBody` 作为输入，并在请求头中携带 Pinata 的 api key 和 secret，向 `pinJSONToIPFS` API 发起一个 POST 请求。

- 如果这个 POST 请求成功，我们的函数会返回一个 JSON 对象，其中 `success` 布尔值为 true，以及元数据被固定（pin）后所在的 `pinataUrl`。我们会把返回的这个 `pinataUrl` 用作智能合约 mint 函数的 `tokenURI` 输入。
- 如果这个 POST 请求失败，我们的函数会返回一个 JSON 对象，其中 `success` 布尔值为 false，以及一个说明错误的 `message` 字符串。

和我们的 `connectWallet` 函数返回类型一样，我们返回的是 JSON 对象，这样就能用它们的参数来更新状态变量和 UI。

## 第 7 步：加载你的智能合约

现在我们已经有了通过 pinJSONToIPFS 函数将 NFT 元数据上传到 IPFS 的方法，接下来我们需要一种方式来加载智能合约的实例，这样才能调用它的 mintNFT 函数。

<CalloutBlock>

正如我们之前提到的，在本教程中我们将使用[这个已有的 NFT 智能合约](https://sepolia.etherscan.io/address/0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE)；不过，如果你想了解我们是如何制作它的，或者想自己做一个，强烈推荐你查看我们的另一篇教程《[如何创建一个 NFT](https://www.alchemy.com/blog/how-to-create-an-nft)》。

</CalloutBlock>

#### 合约 ABI

如果你仔细查看过我们的文件，会注意到在 **src** 目录中有一个 **contract-abi.json** 文件。ABI 是必需的，它用于指定合约将调用哪个函数，同时确保该函数会以你期望的格式返回数据。

我们还需要一个 Alchemy API key 以及 Alchemy Web3 API，用来连接到 Ethereum 区块链并加载我们的智能合约。

#### 创建你的 Alchemy API key

如果你还没有 Alchemy 账户，可以[在这里免费注册](https://alchemy.com/?r=affiliate:d8895272-924f-412b-b63c-60124deb9357)。

创建好 Alchemy 账户后，你可以通过创建一个 app 来生成一个 API key。这样我们就能向 Ropsten 测试网络发起请求了。

在 Alchemy Dashboard 中，将鼠标悬停在导航栏的 "Apps" 上，然后点击 "Create App"，进入 "Create App" 页面

<ImageBlock
  src="https://media.alchemy.com/1704631152-create-a-new-app.jpg"
  alt="Alchemy 控制台：从 Apps 菜单创建新应用"
  width={2880}
  height={1640}
  caption="创建新应用"
/>

为你的 app 命名（我们选择了 "My First NFT!"），填写一段简短描述，在 Environment 中选择 "Staging"（用于你 app 的记录管理），然后为网络选择 "Ropsten"。

<ImageBlock
  src="https://media.alchemy.com/1704631219-configure-your-app-details.png"
  alt="Alchemy 控制台：配置应用名称、描述和 Ropsten 网络"
  width={2880}
  height={1640}
  caption="配置应用详情"
/>

点击 "Create app"，就完成了！你的 app 应该会出现在下方的表格中。

太好了，现在我们已经创建好了 HTTP Alchemy API URL，把它复制到剪贴板，像这样……

<ImageBlock
  src="https://media.alchemy.com/1704631290-copy-your-alchemy-api-key.gif"
  alt="Alchemy 控制台：复制应用的 HTTP API URL 用于 .env 文件"
  width={843}
  height={480}
  caption="复制你的 Alchemy API 密钥"
/>

……然后把它添加到我们的 **.env** 文件中。总的来说，你的 .env 文件现在应该是这样的：

既然我们已经有了合约 ABI 和 Alchemy API key，就可以使用[ Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3) 来加载我们的智能合约了。

#### 设置你的 Alchemy Web3 端点和合约

首先，如果你还没有安装 [Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3)，需要在终端中进入根目录 **nft-minter-tutorial** 来安装它：

接下来我们回到 **interact.js** 文件。在文件顶部，添加以下代码，从 .env 文件中导入你的 Alchemy key，并设置好你的 Alchemy Web3 端点：

<CalloutBlock>

[Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3) 是对 [Web3.js](https://web3js.readthedocs.io/en/v1.2.9/) 的一层封装，提供了增强的 API 方法以及其他重要好处，让你作为 web3 开发者的工作更轻松。它的设计目标是只需最少的配置，就能让你立刻在应用中使用它！

</CalloutBlock>

接下来，让我们把合约 ABI 和合约地址添加到文件中。

一旦有了这两样东西，我们就可以开始编写 mint 函数的代码了！

## 第 8 步：实现 mintNFT 函数

在你的 **interact.js** 文件中，定义我们的函数 **mintNFT**，顾名思义，它将用于铸造我们的 NFT。

由于我们将发起多次异步调用（向 Pinata 请求将元数据固定到 IPFS、向 Alchemy Web3 请求加载智能合约、向 Metamask 请求签署交易），我们的函数也将是异步的。

这个函数的三个输入分别是数字资产的 `url`、`name` 和 `description`。在 `connectWallet` 函数下方添加以下函数签名：

#### 输入错误处理

很自然地，在函数一开始就加入某种输入错误处理是合理的，这样如果输入参数不正确，我们就可以直接退出该函数。在函数内部，添加以下代码：

简单来说，如果任何一个输入参数是空字符串，我们就返回一个 JSON 对象，其中 `success` 布尔值为 false，`status` 字符串则提示 UI 中的所有字段都必须填写完整。

#### 将元数据上传到 IPFS

一旦确认元数据格式正确，下一步就是把它包装成一个 JSON 对象，并通过我们写的 `pinJSONToIPFS` 上传到 IPFS！

为此，我们首先需要把 `pinJSONToIPFS` 函数导入到 **interact.js** 文件中。在 **interact.js** 的最顶部，添加：

回想一下，`pinJSONToIPFS` 接收一个 JSON body。所以在调用它之前，我们需要先把 `url`、`name` 和 `description` 这几个参数格式化成一个 JSON 对象。

让我们更新代码，创建一个名为 `metadata` 的 JSON 对象，然后用这个 `metadata` 参数去调用 `pinJSONToIPFS`：

注意，我们把调用 `pinJSONToIPFS(metadata)` 得到的响应存储在 `pinataResponse` 对象中，然后解析这个对象，检查是否存在错误。

如果有错误，我们返回一个 JSON 对象，其中 `success` 布尔值为 false，`status` 字符串提示调用失败。否则，我们从 `pinataResponse` 中提取出 `pinataURL`，并将其存储为我们的 `tokenURIvariable`。

现在是时候使用我们在文件顶部初始化的 Alchemy Web3 API 来加载智能合约了。在 `mintNFT` 函数的末尾添加以下代码行，将合约设置到 `window.contract` 全局变量中：

我们在 `mintNFT` 函数中要添加的最后一部分是我们的 Ethereum 交易：

如果你已经熟悉 Ethereum 交易，会发现这个结构和你见过的很相似。

首先，我们设置交易参数。

- `to` 指定接收地址（我们的智能合约）
- `from` 指定交易的签署者（用户连接到 Metamask 的地址：`window.ethereum.selectedAddress``\)`）
- `data` 包含对智能合约 `mintNFT` 方法的调用，该方法接收我们的 `tokenURI` 和用户的钱包地址 `window.ethereum.selectedAddress` 作为输入

然后，我们进行一次 await 调用，`window.ethereum.request`，请求 Metamask 对交易进行签名。注意，在这个请求中，我们指定了 eth 方法（`eth\_SentTransaction`），并传入了我们的 `transactionParameters`。此时，Metamask 会在浏览器中弹出，提示用户签署或拒绝该交易。

- 如果交易成功，函数会返回一个 JSON 对象，其中布尔值 `success` 被设置为 true，`status` 字符串会提示用户去 Etherscan 查看有关该交易的更多信息。
- 如果交易失败，函数会返回一个 JSON 对象，其中 `success` 布尔值被设置为 false，`status` 字符串则传达错误信息。

综合起来，我们的 `mintNFT` 函数应该是这样的：

好大一个函数！现在，我们只需要把 `mintNFT` 函数连接到我们的 **Minter.js** 组件……

## 第 9 步：把 mintNFT 连接到我们的 minter.js 前端

打开你的 **Minter.js** 文件，把文件顶部的 `import \{ connectWallet \} from "./utils/interact.js"`; 这一行更新为：

最后，实现 `onMintPressed` 函数，对我们导入的 `mintNFT` 函数进行 await 调用，并更新 `status` 状态变量，以反映我们的交易是成功还是失败：

## 第 10 步：将你的 NFT 部署到一个正式上线的网站

准备好让你的项目上线、供用户交互了吗？查看这篇[教程](https://app.gitbook.com/@alchemyapi/s/alchemy/tutorials/nft-minter/how-do-i-deploy-nfts-online)，了解如何把你的 Minter 部署到一个正式上线的网站：

## 第 11 步：横扫区块链世界 🚀

开玩笑的，不过你确实完成了整篇教程！回顾一下，通过构建一个 NFT 铸造器，你成功学会了如何：

- 通过前端项目连接 Metamask
- 从前端调用智能合约方法
- 使用 Metamask 签署交易

想必你也想在钱包里展示一下通过你的 dApp 铸造出来的 NFT——所以别忘了看看我们的快速教程[《如何在你的钱包中查看 NFT》](/blog/how-to-view-your-nft-in-your-mobile-wallet)！

一如既往，如果你有任何问题，欢迎访问 [Alchemy 支持中心](https://www.alchemy.com/support)。我们迫不及待想看看你会如何把本教程中的概念应用到你未来的项目中！🧙‍♂️
