跳转到主要内容

初学者全栈 Hello World 智能合约

Solidity
hardhat
Alchemy
智能合约
部署
区块浏览器
前端
交易
初学者
nstrike2
2021年10月25日
66 分钟阅读

如果你是区块链开发的新手,不知道从哪里开始或者如何部署并与智能合约交互,那么本指南就是为你准备的。 我们将使用 MetaMask (opens in a new tab)Solidity (opens in a new tab)Hardhat (opens in a new tab)Alchemy (opens in a new tab) 逐步创建一个简单的智能合约并将其部署到 Goerli 测试网上。

你将需要一个 Alchemy 帐户来完成这个教程。 注册一个免费帐户 (opens in a new tab)

如果你在任何时候有任何疑问,欢迎随时在 Alchemy Discord (opens in a new tab) 中联系我们!

第一部分 - 使用Hardhat创建和部署你的智能合约

连接到以太坊网络

有很多方法可以向以太坊链发送请求。 为简单起见,我们将使用 Alchemy 上的免费帐户。Alchemy 是一个区块链开发者平台和 API,让我们在不用自己运行节点的情况下与以太坊链进行通信。 Alchemy 还有着用于监控和分析的开发者工具;我们将在本教程中利用这些工具来深入了解我们智能合约部署中的情况。

创建你的应用和 API 密钥

创建 Alchemy 帐户后,你可以通过创建应用来生成 API 密钥。 这将允许你向 Goerli 测试网发送请求。 如果你不熟悉测试网,可以阅读 Alchemy 关于选择网络的指南 (opens in a new tab)

在 Alchemy 仪表板上,找到位于导航栏的“应用”下拉菜单并点击“创建应用”。

Hello world 创建应用程序

给你的应用命名为“Hello World”并写一个简短的描述。 选择“Staging”作为你的环境以及“Goerli”作为你的网络。

创建应用程序视图 hello world

注意:请确保选择 Goerli,否则本教程将无法正常进行。

点击“Create app”。 你的应用程序应该会出现在下表中。

创建以太坊帐户

你需要一个以太坊帐户来发送和接受交易。 我们将会使用 MetaMask,这是一个浏览器中的虚拟钱包,可供用户管理他们的以太坊帐户地址。

你可以在此处 (opens in a new tab)免费下载和创建 MetaMask 帐户。 在你创建帐户时,或者如果你已经有帐户,请确保切换到右上角的“Goerli 测试网络”(这样我们就不会使用实际货币进行交易)。

步骤 4:从水龙头获取以太币

要将你的智能合约部署到测试网,你需要一些虚拟 ETH。 要在 Goerli 网络上获取 ETH,请前往 Goerli 水龙头并输入你的 Goerli 帐户地址。 请注意,Goerli 水龙头最近可能不太可靠 - 请参阅测试网页面获取可供尝试的选项列表:

注意:由于网络拥堵,这可能需要一些时间。 ``

第 5 步:检查你的余额

为仔细检查 ETH 是否已在你的钱包中,我们来使用 Alchemy 的 composer 工具 (opens in a new tab)发出一个 eth_getBalance (opens in a new tab) 请求。 这将返回我们钱包中的以太币数量。 要了解更多信息,请查看 Alchemy 关于如何使用 composer 工具的简短教程 (opens in a new tab)

输入你的 MetaMask 帐户地址并点击“Send Request”。 你将看到类似以下代码片段的响应。

{ "jsonrpc": "2.0", "id": 0, "result": "0x2B5E3AF16B1880000" }

注意:此结果以 wei 为单位,而非 ETH。 Wei 是以太币的最小计量单位。

哦! 这里显示了我们所有的虚拟货币。

第 6 步:初始化我们的项目

首先,需要为我们的项目创建一个文件夹。 导航到你的命令行并输入以下内容。

mkdir hello-world
cd hello-world

现在我们进入了项目文件夹,我们将使用 npm init 来初始化项目。

如果你尚未安装 npm,请按照这些说明安装 Node.js 和 npm (opens in a new tab)

对于本教程而言,你如何回答初始化问题并不重要。 以下是我们的参考操作方式:

批准 package.json,我们就可以继续了!

第 7 步:下载Hardhat

Hardhat是一个用于编译、部署、测试和调试以太坊软件的开发环境。 它帮助开发者在本地构建智能合约和去中心化应用程序并部署到实时链上。

在我们的 hello-world 项目中运行:

npm install --save-dev hardhat

请查看此页面,详细了解安装说明 (opens in a new tab)

第 8 步:创建Hardhat项目

在我们的 hello-world 项目文件夹中,运行:

npx hardhat

然后应该能看到一条欢迎消息和选项,用于选择你想要做的事情。 选择“创建一个空的 hardhat.config.js”:

这将在项目中生成一个 hardhat.config.js 文件。 我们稍后将在教程中使用它来为我们的项目指定设置。

第 9 步:添加项目文件夹

为了使项目有条理,我们将创建两个新的文件夹。 在命令行中,导航到你的 hello-world 项目的根目录中并输入:

mkdir contracts
mkdir scripts
  • contracts/ 是我们存放 hello world 智能合约代码文件的地方
  • scripts/ 是我们存放部署和交互合约脚本的地方

第 10 步:编写我们的合约

你可能在问自己,到底什么时候才能写代码? 就是现在!

在你最喜爱的编辑器中打开 hello-world 项目。 智能合约通常使用 Solidity 来编写,我们也将使用它来编写智能合约。‌

  1. 导航到 contracts 文件夹并创建一个名为 HelloWorld.sol 的新文件
  2. 下面是我们将在本教程中使用的示例 Hello World 智能合约。 将下面的内容复制到 HelloWorld.sol 文件中。

注意:务必阅读注释以理解此合约的内容。

这是一个在创建时存储一条消息的基础智能合约。 可以通过调用 update 函数来更新该合约。

第 11 步:将 MetaMask 和 Alchemy 连接到你的项目

我们已经创建了 MetaMask 钱包、Alchemy 帐户并编写了智能合约,现在是时候将这三者连接起来了。

从你的钱包发出的每一笔交易都需要使用你独有的私钥签名。 为了给我们的程序提供此权限,我们可以安全地将私钥存储在一个环境文件中。 我们也会在此存储一个 Alchemy 的 API 密钥。

要了解有关发送交易的更多信息,请查看有关使用 web3 发送交易的本教程 (opens in a new tab)

首先,在项目目录中安装 dotenv 软件包:

npm install dotenv --save

然后,在项目根目录下创建一个 .env 文件。 在文件中添加你的 MetaMask 私钥和 HTTP Alchemy API URL。

你的环境文件必须以 .env 命名,否则不会被识别为环境文件。

不要命名为 process.env.env-custom 或其他名称。

获取 Alchemy API 密钥的动画演练

你的 .env 文件应如下所示:

API_URL = "https://eth-goerli.alchemyapi.io/v2/your-api-key"
PRIVATE_KEY = "your-metamask-private-key"

为了真正将它们连接到我们的代码,我们将在第 13 步的 hardhat.config.js 文件中引用这些变量。

第 12 步:安装 Ethers.js

Ethers.js 是一个库,它通过将标准 JSON-RPC 方法 (opens in a new tab)封装成更方便用户的方法,从而更容易与以太坊互动并提出请求。

Hardhat允许我们集成插件 (opens in a new tab)以获取额外的工具和扩展功能。 我们将利用 Ethers 插件 (opens in a new tab)来进行合约部署。

在你的项目目录中输入:

npm install --save-dev @nomiclabs/hardhat-ethers "ethers@^5.0.0"

第 13 步:更新 hardhat.config.js

到目前为止,我们已经添加了几个依赖项和插件,现在我们需要更新 hardhat.config.js,以便我们的项目了解所有这些依赖项和插件。

将你的 hardhat.config.js 更新为如下所示:

第 14 步:编译我们的合约

为了确保一切正常,我们来编译一下合约。 compile 任务是内置的 hardhat 任务之一。

在命令行中运行:

npx hardhat compile

你可能会看到关于“SPDX license identifier not provided in source file”的警告,但无需担心——希望其他的一切正常! 如果编译不成功,可以随时在 Alchemy discord (opens in a new tab) 中发消息。

第 15 步:编写我们的部署脚本

合约已经写完,配置文件也准备妥当,现在是写合约部署脚本的时候了。

导航到 scripts/ 文件夹并创建一个名为 deploy.js 的新文件,向其中添加以下内容:

Hardhat在他们的合约教程 (opens in a new tab)中极好地解释了每一行代码的作用,我们在此处采用了他们的解释。

const HelloWorld = await ethers.getContractFactory("HelloWorld")

ethers.js 中的 ContractFactory 是用于部署新智能合约的抽象,因此这里的 HelloWorld 是我们 hello world 合约实例的工厂 (opens in a new tab)。 使用 hardhat-ethers 插件时,ContractFactoryContract 实例默认与第一个签名者(所有者)相连。

const hello_world = await HelloWorld.deploy()

ContractFactory 上调用 deploy() 会启动部署,并返回一个解析为 Contract 对象的 Promise。 这个对象包括我们智能合约中每个函数的对应调用方法。

第 16 步:部署我们的合约

我们终于准备好部署我们的智能合约啦! 导航到命令行并运行:

npx hardhat run scripts/deploy.js --network goerli

你会看到类似以下所示的信息:

合约已部署到地址:0x6cd7d44516a20882cEa2DE9f205bF401c0d23570

请保存这个地址。 之后在教程中我们会用到这个地址。

如果我们前往 Goerli etherscan (opens in a new tab) 并搜索我们的合约地址,应该能看到它已成功部署。 交易将类似以下:

在 Etherscan 上部署的智能合约的屏幕截图

From 地址应匹配你的 MetaMask 帐户地址,To 地址将显示合约创建。 如果我们点击进入交易,我们将在 To 字段中看到我们的合约地址。

Etherscan 上交易的屏幕截图

恭喜! 你刚刚在以太坊测试网上部署了一个智能合约。

要了解后台运行情况,我们导航到我们的 Alchemy 仪表板 (opens in a new tab)中的 Explorer 选项卡。 如果你有多个 Alchemy 应用,请确保按应用筛选并选择“Hello World”。

区块浏览器中 Hello World 智能合约的屏幕截图

在这里,你会看到一系列 JSON-RPC 方法,当我们调用 .deploy() 函数时,Hardhat/Ethers 会在后台为我们调用这些方法。 这里有两个重要方法:eth_sendRawTransaction (opens in a new tab) 是将我们的合约写入 Goerli 链的请求,而 eth_getTransactionByHash (opens in a new tab) 是在给定哈希的情况下读取我们交易信息的请求。 要了解有关发送交易的更多信息,请查看我们关于使用 Web3 发送交易的教程

第二部分:与你的智能合约交互

现在我们已经成功地将智能合约部署到 Goerli 网络,让我们学习如何与它交互。

创建一个 interact.js 文件

这是我们将在其中编写交互脚本的文件。 我们将使用在第一部分中安装的 Ethers.js 库。

scripts/文件夹中,新建一个文件,命名为 interact.js,添加以下代码:

// interact.js

const API_KEY = process.env.API_KEY
const PRIVATE_KEY = process.env.PRIVATE_KEY
const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS

更新你的 .env 文件

我们将使用新的环境变量,因此需要在我们之前创建.env 文件中定义这些变量。

我们需要为 Alchemy API_KEY 和部署你的智能合约的 CONTRACT_ADDRESS 添加定义。

你的 .env 文件应该如下所示:

# .env

API_URL = "https://eth-goerli.alchemyapi.io/v2/<your-api-key>"
API_KEY = "<your-api-key>"
PRIVATE_KEY = "<your-metamask-private-key>"
CONTRACT_ADDRESS = "0x<your contract address>"

获取你的合约 ABI

我们的合约 是与我们的智能合约交互的接口。 Hardhat自动生成 ABI,并将其保存在 HelloWorld.json 中。 为了使用该接口,我们需要通过在我们的 interact.js 文件中添加以下代码行来解析内容:

// interact.js
const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")

如果你想查看 ABI,可以将其打印到控制台:

console.log(JSON.stringify(contract.abi))

要查看输出到控制台的 ABI,请导航至你的终端并运行:

npx hardhat run scripts/interact.js

创建合约实例

为了与我们的合约进行交互,我们需要在代码中创建一个合约实例。 要使用 Ethers.js 实现,我们需要使用三个概念:

  1. 提供者 - 为你提供区块链读写访问权限的节点提供者
  2. 签名者 - 代表可以给交易签名的以太坊帐户
  3. 合约 - 代表部署在链上的特定合约的 Ethers.js 对象

我们将使用上一步中的合约 ABI 来创建我们的合约实例:

ethers.js 文档 (opens in a new tab)获取更多关于提供者、签名者和合约的信息。

读取初始消息

还记得我们用 initMessage = "Hello world!" 部署合约时的情况吗? 我们现在要读取存储在智能合约中的消息,并将其输出到控制台。

在 JavaScript 中,与网络交互时会使用异步函数。 要了解有关异步函数的更多信息,请阅读这篇 Medium 文章 (opens in a new tab)

使用下面的代码来调用智能合约中的 message 函数,并读取初始消息:

在终端使用 npx hardhat run scripts/interact.js 运行文件后,我们应该看到如下响应:

消息是:Hello world!

恭喜! 你刚刚成功从以太坊区块链读取了智能合约数据,好样的!

更新消息

除了读取消息,我们还可以使用 update 函数更新保存在我们智能合约中的消息! 很酷,对吗?

要更新消息,我们可以直接在实例化的合约对象上调用 update 函数:

请注意,在第 11 行,我们对返回的交易对象调用了 .wait()。 这确保了脚本在退出函数前等待交易在区块链上完成出块。 如果不包含 .wait() 调用,脚本可能不会看到合约中更新后的 message 值。

读取新消息

你应该能够重复前面的步骤来读取更新后的 message 值。 花点时间,看看是否可以进行必要的更改以输出新值!

如果你需要提示,你的 interact.js 文件现在应如下所示:

现在只需运行脚本,你应该能够看到旧消息、更新状态和输出到终端的新消息!

npx hardhat run scripts/interact.js --network goerli

消息是:Hello World!
正在更新消息...
新消息是:This is the new message.

当运行该脚本时,你可能会发现“Updating the message...”步骤需要一段时间才能加载完成,然后再加载新消息。 这是由挖矿过程导致的;如果你希望在对交易进行挖矿的同时追踪交易,可以访问 Alchemy 内存池 (opens in a new tab)查看交易的状态。 如果交易被丢弃,可以访问 Goerli Etherscan (opens in a new tab) 并搜索你的交易哈希值。

第三部分:将你的智能合约发布到 Etherscan

你已经完成了实现智能合约相关的所有艰苦工作,现在是时候与世界分享了!

通过在 Etherscan 上验证你的智能合约,任何人都可以查看其源代码,并与智能合约进行交互。 让我们开始吧!

第 1 步:在 Etherscan 帐户中生成 API 密钥

需要 Etherscan API 密钥来验证你是否拥有你正在尝试发布的智能合约。

如果你还没有 Etherscan 帐户,请注册一个帐户 (opens in a new tab)

登录后,在导航栏中找到你的用户名,将鼠标悬停在用户名上,然后选择“My profile”按钮。

在你的个人资料页面上,应该可以看到一个侧边导航栏。 从侧边导航栏中,选择“API Keys”。 接下来,按“Add”按钮创建一个新的 API 密钥,将你的应用程序命名为 hello-world,然后按“Create New API Key”按钮。

你的新 API 密钥应出现在 API 密钥表中。 将 API 复制到剪贴板。

接下来,我们需要将 Etherscan API 密钥添加到我们的 .env 文件中。

添加完成后,你的 .env 文件应如下所示:

API_URL = "https://eth-goerli.alchemyapi.io/v2/your-api-key"
PUBLIC_KEY = "your-public-account-address"
PRIVATE_KEY = "your-private-account-address"
CONTRACT_ADDRESS = "your-contract-address"
ETHERSCAN_API_KEY = "your-etherscan-key"

Hardhat部署的智能合约

安装 hardhat-etherscan

使用Hardhat将你的合约发布到 Etherscan 非常简单。 首先需要安装 hardhat-etherscan 插件。 hardhat-etherscan 会在 Etherscan 上自动验证智能合约的源代码和 ABI。 为了添加此插件,你需要在 hello-world 目录中运行:

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

安装完成后,在你的 hardhat.config.js 文件中添加下面的语句,并添加 Etherscan 配置选项:

在 Etherscan 上验证你的智能合约

确认所有文件都已保存,且所有 .env 变量都已正确配置。

运行 verify 任务,传入合约地址以及要部署到的网络:

npx hardhat verify --network goerli DEPLOYED_CONTRACT_ADDRESS 'Hello World!'

确保 DEPLOYED_CONTRACT_ADDRESS 是你在 Goerli 测试网络上部署的智能合约的地址。 此外,最后一个参数 ('Hello World!') 必须是你在第一部分的部署步骤中使用的相同字符串。

如果一切正常,你会在终端中看到以下消息:

成功提交合约源代码
contracts/HelloWorld.sol:HelloWorld at 0xdeployed-contract-address
正在 Etherscan 上验证。等待验证结果...


在 Etherscan 上成功验证合约 HelloWorld。
https://goerli.etherscan.io/address/<contract-address>#contracts

恭喜! 你的智能合约代码已在 Etherscan 上!

在 Etherscan 上查看你的智能合约!

当你进入终端中给出的链接时,你应该会看到你的智能合约代码和 ABI 已在 Etherscan 上发布!

哇哦 - 你成功了! 现在所有人都可以调用或写入你的智能合约! 我们已经等不及看你接下来会做什么了!

第四部分 - 将你的智能合约与前端集成

本教程结束时,你将知道如何:

  • 将 MetaMask 钱包连接到你的去中心化应用程序
  • 使用 Alchemy Web3 (opens in a new tab) API 从你的智能合约中读取数据
  • 使用 MetaMask 对以太坊交易签名

对于此去中心化应用程序,我们会使用 React (opens in a new tab) 作为前端框架;然而,需要注意的是,我们不会花很多时间来分解其基本内容,而是会聚焦于将 Web3 功能引入我们的项目。

作为前提条件,你需要对 React 有基本的了解。 否则,建议你完成官方的 React 入门教程 (opens in a new tab)

克隆启动文件

首先,到 hello-world-part-four GitHub 存储库 (opens in a new tab)中获取项目的初始文件,并将此存储库克隆到你的本地计算机中。

在本地打开克隆的存储库。 你会注意到它包含两个文件夹:starter-filescompleted

  • starter-files - 我们会在这个目录中工作,我们会将用户界面连接到你的以太坊钱包以及我们在第三部分中发布到 Etherscan 的智能合约。
  • completed 包含已完成的整个教程,并只应用作出现问题时的参考。

下面,用你喜欢的代码编辑器打开 starter-files,然后进入 src 文件夹。

我们将编写的所有代码都将存放在 src 文件夹下。 我们将编辑 HelloWorld.js 组件和 util/interact.js JavaScript 文件,为我们的项目提供 Web3 功能。

查看初始文件

在我们开始编写之前,让我们看看初始文件为我们提供了什么。

运行你的 React 项目

首先在浏览器中运行 React 项目。 React 的美妙之处在于,一旦我们的项目在浏览器中运行,保存的任何更改都会在浏览器中实时更新。

要让项目运行,浏览到 starter-files 文件夹的根目录,然后在终端运行 npm install 以安装项目的依赖项:

cd starter-files
npm install

安装完成后,在终端中运行 npm start

npm start

这样做应该会在你的浏览器中打开 http://localhost:3000/ (opens in a new tab),在这里你会看到我们项目的前端界面。 它应该包含一个字段(一个更新存储在智能合约中的消息的地方),一个“Connect Wallet”按钮,以及一个“Update”按钮。

如果你试图点击这些按钮,你会发现它们都不起作用——这是因为我们仍然需要对其功能进行编程。

HelloWorld.js 组件

让我们在编辑器中返回 src 文件夹并打开 HelloWorld.js 文件。 理解该文件中的所有内容非常重要,因为它是我们将要处理的主要 React 组件。

在该文件开头,你会发现我们有几条 import 语句,这些语句是我们项目运行所必须的,它们包括 React 库,useEffect 和 useState 钩子,一些来自 ./util/interact.js 的项(我们之后还会更详细的说明它们!),以及 Alchemy 徽标。

下面,我们来定义将在特定事件后更新的状态变量。

// HelloWorld.js

//State variables
const [walletAddress, setWallet] = useState("")
const [status, setStatus] = useState("")
const [message, setMessage] = useState("No connection to the network.")
const [newMessage, setNewMessage] = useState("")

以下是每个变量的含义:

  • walletAddress - 存储用户钱包地址的字符串
  • status- 用于存储有用消息,指导用户如何与去中心化应用程序交互的字符串
  • message - 存储智能合约中当前消息的字符串
  • newMessage - 存储将要写入智能合约中的新消息的字符串

在状态变量后,你会发现五个还未实现的函数:useEffectaddSmartContractListeneraddWalletListenerconnectWalletPressed 以及 onUpdatePressed。 我们会在下面解释它们的作用:

  • useEffect (opens in a new tab) - 这是一个在你的组件渲染后被调用的 React 钩子。 因为向它传入了一个空的数组 [] 属性(见第 4 行),它只会在组件的_第一次_渲染时被调用。 在这里我们会加载智能合约中存储的当前消息,调用智能合约和钱包监听器,并更新用户界面来反映钱包是否已连接。
  • addSmartContractListener - 这个函数设置了一个监听器,这个监听器会监视 HelloWorld 合约中的 UpdatedMessages 事件,并在智能合约中的消息变化时更新用户界面。
  • addWalletListener - 这个函数设置了一个监听器,来检测用户的 MetaMask 钱包的状态变化,比如用户断开他们的钱包或切换地址时。
  • connectWalletPressed - 这个函数用于将用户的 MetaMask 钱包连接到我们的去中心化应用程序。
  • onUpdatePressed - 这个函数会在用户更新智能合约中存储的消息时被调用。

在接近该文件末尾处,我们获得我们组件的用户界面。

如果你细致地检查这段代码,就会发现我们在用户界面中的哪里使用了各种状态变量:

  • 在第 6-12 行,如果用户的钱包已连接(即 walletAddress.length > 0),我们就在 ID 为“walletButton”的按钮中显示用户 walletAddress 的截短版;否则我们就只显示“Connect Wallet”。
  • 在第 17 行,我们显示在 message 字符串中获取的智能合约中存储的当前消息。
  • 在第 23-26 行,我们使用一个受控组件 (opens in a new tab),以用于在文本字段中输入更改时更新我们的 newMessage 状态变量。

除了状态变量之外,你还将看到,在分别单击 ID 为 publishButtonwalletButton 的按钮时,会调用 connectWalletPressedonUpdatePressed

最后,我们来看看 HelloWorld.js 组件是在哪里添加的。

如果你打开 App.js 文件,将会看到我们的 HelloWorld.js 组件是在第 7 行添加的。此文件是 React 中的主要组件,作为所有其他组件的容器。

最后,让我们再看看另一个为你提供的文件,interact.js

interact.js 文件

因为我们建议采用 M-V-C (opens in a new tab) 规范,我们需要一个单独的文件包含用来管理我们的去中心化应用程序的逻辑、数据和规则的所有函数,然后我们就能将这些函数导出到前端(我们的 HelloWorld.js 组件)。

👆🏽这就是 interact.js 文件的确切目的!

进入 src 目录中的 util 文件夹,然后你就会发现我们引入了 interact.js 文件,它包含所有的智能合约交互以及钱包函数和变量。

你会发现在文件的开头,我们注释了 helloWorldContract 对象。 在之后的教程里,我们会删除这个对象的注释,并且在这个变量中实例化智能合约,然后我们可以将其导出到 HelloWorld.js 组件。

helloWorldContract 对象之后的四个未实现函数会发挥以下作用:

  • loadCurrentMessage - 这个函数处理加载智能合约中存储的消息的逻辑。 它会使用 Alchemy Web3 API (opens in a new tab),向 Hello World 智能合约发起一个_读取_调用。
  • connectWallet - 这个函数会将用户的 MetaMask 连接到我们的去中心化应用程序。
  • getCurrentWalletConnected - 这个函数会在页面加载时检查是否已经有以太坊帐户连接到我们的去中心化应用程序,并且相应更新我们的用户界面。
  • updateMessage - 这个函数会更新智能合约中存储的消息。 它会向 Hello World 智能合约发起一个_写入_调用,所以用户的 MetaMask 钱包需要签署一个以太坊交易来更新此消息。

现在我们已经了解所操作的对象,让我们看看如何从智能合约中读取吧!

第 3 步:从你的智能合约中读取

为了从智能合约中读取,你需要成功设置以下内容:

  • 一个到以太坊链的 API 连接
  • 一个已加载的智能合约实例
  • 一个用来调用智能合约函数的函数
  • 一个监听器,用于监听智能合约中你正读取的数据出现变化时的更新

听起来步骤可能很多,但是不要担心! 我们会引导你逐步完成这些步骤! :)

建立到以太坊链的 API 连接

还记得在本教程的第 2 部分中,我们是如何使用 Alchemy Web3 密钥从我们的智能合约中读取 (opens in a new tab)的吗? 你在去中心化应用程序中也需要一个 Alchemy Web3 密钥来从链上读取。

如果你还没有,首先安装 Alchemy Web3 (opens in a new tab),导航到 starter-files 的根目录,然后在你的终端运行以下命令:

npm install @alch/alchemy-web3

Alchemy Web3 (opens in a new tab)Web3.js (opens in a new tab) 的包装器,提供增强的 API 方法和其他重要优势,让 Web3 开发者的工作更轻松。 它设计成只需经过最少的配置即可使用,因此你可以直接在你的应用程序中开始使用它!

然后,在你的项目目录中安装 dotenv (opens in a new tab) 程序包,这样我们就有了一个在获取我们 API 密钥后安全存储它的地方。

npm install dotenv --save

对于我们的去中心化应用程序,我们将不再使用 HTTP API 密钥,而是改用 Websockets API 密钥,因为它会允许我们设置一个监测智能合约中消息变化的监听器。

在你获得 API 密钥后,在你的根目录中创建 .env 文件,并在其中添加你的 Alchemy Websockets URL。 在这之后,你的 .env 文件应该如下所示:

REACT_APP_ALCHEMY_KEY = wss://eth-goerli.ws.alchemyapi.io/v2/<key>

现在,我们已经准备好在我们的去中心化应用程序中设置 Alchemy Web3 端点了! 让我们回到嵌套在 util 文件夹中的 interact.js,并在文件的开头加上以下代码:

// interact.js

require("dotenv").config()
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(alchemyKey)

//export const helloWorldContract;

在上面,我们先从 .env 文件中导入了 Alchemy 密钥,然后将 alchemyKey 传递给 createAlchemyWeb3 以建立 Alchemy Web3 端点。

准备好此端点后,我们就可以加载智能合约了!

加载你的 Hello World 智能合约

要加载你的 Hello World 智能合约,你需要它的合约地址和 ABI,如果你完成了本教程的第 3 部分,这两者都可以在 Etherscan 上找到。

如何从 Etherscan 获取你的合约 ABI

如果你跳过了本教程的第 3 部分,你可以使用地址为 0x6f3f635A9762B47954229Ea479b4541eAF402A6A (opens in a new tab) 的 HelloWorld 合约。 其 ABI 可以在这里 (opens in a new tab)找到。

在指定合约将要调用的函数,以及确保函数以你期望的格式返回数据时,合约的 ABI 必不可少。 复制合约 ABI 后,让我们将其保存到 src 目录,保存为文件名为 contract-abi.json 的 JSON 文件。

你的 contract-abi.json 文件应存储在 src 文件夹。

有了合约地址、ABI 和 Alchemy Web3 端点,我们就可以使用合约方法 (opens in a new tab)来加载智能合约的实例。 将你的合约 ABI 导入 interact.js 文件,然后添加你的合约地址。

// interact.js

const contractABI = require("../contract-abi.json")
const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"

现在,我们终于可以取消 helloWorldContract 变量的注释,并用 AlchemyWeb3 端点加载智能合约了:

// interact.js
export const helloWorldContract = new web3.eth.Contract(
  contractABI,
  contractAddress
)

概括一下,你的 interact.js 文件的前 12 行应该如下所示:

我们现在已经加载了合约,可以实现 loadCurrentMessage 函数了!

在你的 interact.js 文件中实现 loadCurrentMessage

这个函数非常简单。 我们将通过一个简单的异步 web3 调用来从我们的合约中读取信息。 我们的函数会返回智能合约中存储的消息:

interact.js 文件中的 loadCurrentMessage 更新为如下:

// interact.js

export const loadCurrentMessage = async () => {
  const message = await helloWorldContract.methods.message().call()
  return message
}

因为我们希望在用户界面中显示该智能合约,让我们将 HelloWorld.js 组件中的 useEffect 函数更新如下:

// HelloWorld.js

//called only once
useEffect(async () => {
  const message = await loadCurrentMessage()
  setMessage(message)
}, [])

注意,我们只希望在组件第一次渲染时调用 loadCurrentMessage 函数一次。 我们很快会实现 addSmartContractListener 以在智能合约中的消息变化后自动更新用户界面。

在我们深入研究监听器之前,让我们看看现在已经做了什么! 保存你的 HelloWorld.jsinteract.js 文件,然后访问 http://localhost:3000/ (opens in a new tab)

你会发现当前的消息不再是“No connection to the network.” 相反,它反映了智能合约中存储的消息。 酷!

你的用户界面现在应反映智能合约中存储的消息

说到监听器……

实现 addSmartContractListener

回想我们在本系列教程的第 1 部分 (opens in a new tab)中编写的 HelloWorld.sol 文件,你会记得有一个名为 UpdatedMessages 的智能合约事件,这是在调用我们的智能合约的 update 函数后触发的(参见第 9 行和第 27 行):

智能合约事件是你的合约向你的前端应用程序传达区块链上发生的事情(即:有一个_事件_)的一种方式,你的前端应用程序可以“监听”特定事件,并在事件发生时采取行动。

addSmartContractListener 函数将专门监听我们 Hello World 智能合约的 UpdatedMessages 事件,并更新用户界面来显示新消息。

addSmartContractListener 修改为如下内容:

让我们分析一下当监听器检测到事件时会发生什么:

  • 如果事件触发时出现错误,它将通过我们的 status 状态变量反映在用户界面中。
  • 反之,我们就可以使用返回的 data 对象。 data.returnValues 是一个以 0 为起始索引的数组,其中数组的第一个元素保存的是之前的消息,而第二个元素则保存了更新后的新消息。 总体来说,在成功触发事件的情况下,我们会将 message 字符串设置为更新后的消息,同时清除 newMessage 字符串,并且更新 status 状态变量以表明已在智能合约上发布一条新的消息。

最终,我们应在 useEffect 函数中调用这个监听器,确保它在 HelloWorld.js 组件初次渲染时即被初始化。 综上所述,我们的 useEffect 函数应当如下所示:

// HelloWorld.js

useEffect(async () => {
  const message = await loadCurrentMessage()
  setMessage(message)
  addSmartContractListener()
}, [])

现在我们已经掌握了从智能合约读取消息的方法,那么接下来了解如何向智能合约中写入消息那就太棒了! 然而,要向我们的去中心化应用执行写入,我们首先必须有一个连接到该去中心化应用程序的以太坊钱包。

接下来,我们将着手设置以太坊钱包(MetaMask),然后将其连接到我们的去中心化应用程序!

第 4 步:设置你的以太坊钱包

为了向以太坊链上写入任何数据,用户必须使用其虚拟钱包的私钥来签署交易。 在本教程中,我们将使用 MetaMask (opens in a new tab),这是一个浏览器中的虚拟钱包,用于管理你的以太坊帐户地址,因为它让终端用户签署交易变得极其简单。

如果你想进一步了解以太坊交易的运作方式,请查看以太坊基金会的这个页面

下载 MetaMask

你可以在此处 (opens in a new tab)免费下载和创建 MetaMask 帐户。 在你创建帐户时,或者如果你已经有帐户,请确保切换到右上角的“Goerli 测试网络”(这样我们就不会使用实际货币进行交易)。

通过水龙头获取以太币

为了在以太坊区块链上签署交易,我们需要一些虚拟 Eth。 要获取 Eth,你可以前往 FaucETH (opens in a new tab) 并输入你的 Goerli 帐户地址,单击“Request funds”,然后在下拉菜单中选择“Ethereum Testnet Goerli”,最后再次单击“Request funds”按钮。 你应该会很快在你的 MetaMask 帐户中看到以太币!

检查你的余额

为了仔细检查我们的余额,让我们使用 Alchemy 的 composer 工具 (opens in a new tab) 发出一个 eth_getBalance (opens in a new tab) 请求。 这将返回我们钱包中的以太币数量。 输入你的 MetaMask 帐户地址并点击“Send Request”后,你应该会看到这样的响应:

{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}

**注意:**此结果以 wei 为单位,而不是 eth。 Wei 是以太币的最小计量单位。 从 wei 到 eth 的转换是:1 eth = 10¹⁸ wei。 因此,如果我们将 0xde0b6b3a7640000 转换为十进制,我们会得到 1*10¹⁸,它等于 1 eth。

哦! 我们的虚拟以太币都在那里了! 🤑

第 5 步:将 MetaMask 连接到你的 UI

既然我们的 MetaMask 钱包已经设置好了,我们将我们的去中心化应用程序与之连接!

connectWallet 函数

在我们的 interact.js 文件中,我们实现了 connectWallet 函数,然后我们可以在 HelloWorld.js 组件中调用它。

让我们将 connectWallet 函数修改为以下形式:

那么,这一大段代码究竟是做什么的呢?

首先,该函数会检查你的浏览器是否启用了 window.ethereum

window.ethereum 是由 MetaMask 和其他钱包提供商注入的全局 API,允许网站请求用户的以太坊帐户。 如果被批准,它可以读取用户连接的区块链上的数据,并建议用户签署消息和交易。 有关详细信息,请查看 MetaMask 文档 (opens in a new tab)

如果 window.ethereum _不_存在,则意味着 MetaMask 未安装。 这将导致返回一个 JSON 对象,其中返回的 address 是一个空字符串,而 status JSX 对象则会提示用户必须安装 MetaMask。

现在,如果 window.ethereum _是_存在的,事情就变得有趣了。

使用 try/catch 循环,我们将通过调用 window.ethereum.request({ method: \"eth_requestAccounts\" }); (opens in a new tab) 来尝试连接到 MetaMask。 调用此函数将在浏览器中打开 MetaMask,提示用户将他们的钱包连接到你的去中心化应用程序。

  • 如果用户选择连接,method: "eth_requestAccounts" 将返回一个数组,其中包含连接到去中心化应用程序的用户的所有帐户地址。 总而言之,我们的 connectWallet 函数将返回一个 JSON 对象,其中包含此数组中的_第一个_ address(见第 9 行)和一条提示用户向智能合约写入消息的 status 消息。
  • 如果用户拒绝连接,则 JSON 对象将包含一个空字符串作为返回的 address,以及一条反映用户拒绝连接的 status 消息。

现在我们已经编写了这个 connectWallet 函数,下一步是调用它到我们的 HelloWorld.js 组件中。

connectWallet 函数添加到你的 HelloWorld.js 用户界面组件

导航到 HelloWorld.js 文件中的 connectWalletPressed 函数,并将其更新为以下内容:

// HelloWorld.js

const connectWalletPressed = async () => {
  const walletResponse = await connectWallet()
  setStatus(walletResponse.status)
  setWallet(walletResponse.address)
}

注意到我们的大部分功能是如何从 interact.js 文件中的 HelloWorld.js 组件中抽象出来的吗? 这就是我们遵守 M-V-C 规范的原因!

connectWalletPressed 中,我们只需对导入的 connectWallet 函数进行 await 调用,并使用其响应通过它们的状态钩子来更新 statuswalletAddress 变量。

现在,让我们保存两个文件(HelloWorld.jsinteract.js),并测试一下我们目前的用户界面。

打开浏览器,在地址栏中输入 http://localhost:3000/ (opens in a new tab),然后按页面右上角的“Connect Wallet”按钮。

如果你安装了 MetaMask,系统会提示你将钱包连接到去中心化应用程序。 接受邀请并连接。

你会看到钱包按钮现在反映你的地址已连接! 太棒了 🔥!

接下来,尝试刷新页面…… 这很奇怪。 我们的钱包按钮提示我们连接 MetaMask,尽管它已经连接......

然而,不要害怕! 我们可以轻松解决这个问题(明白了吗?) 通过实现 getCurrentWalletConnected,它将检查一个地址是否已经连接到我们的去中心化应用程序,并相应地更新我们的用户界面!

getCurrentWalletConnected 函数

interact.js 文件中的 getCurrentWalletConnected 函数更新如下:

这段代码与我们刚刚在上一步中编写的 connectWallet 函数非常相似。

主要区别在于,我们这里调用的方法是 eth_accounts,它只是返回一个包含当前连接到我们去中心化应用程序的 MetaMask 地址的数组,而不是调用会打开 MetaMask 供用户连接钱包的 eth_requestAccounts 方法。

为了看看这个函数在实际应用中的效果,让我们在 HelloWorld.js 组件的 useEffect 函数中调用它:

请注意,我们使用调用 getCurrentWalletConnected 的响应来更新我们的 walletAddressstatus 状态变量。

现在我们已经添加了这段代码,让我们刷新浏览器窗口,看看最新的效果如何。

很不错! 按钮应显示你已连接,并显示已连接钱包地址的预览 — 即使在你刷新后也是如此!

实现 addWalletListener

我们的去中心化应用程序钱包设置的最后一步是实现钱包监听器,以便我们的用户界面在钱包状态发生变化时更新,例如当用户断开或切换帐户时。

在你的 HelloWorld.js 文件中,按照以下方式修改你的 addWalletListener 函数:

我敢打赌,到了这一步你可能已经无需我们帮助就能理解这里发生的情况了,但为了确保详尽无遗,我们还是快速梳理一下:

  • 首先,我们的函数会检查 window.ethereum 是否已启用(即 MetaMask 是否已安装)。
    • 如果未启用,我们只需将 status 状态变量设置为提示用户安装 MetaMask 的 JSX 字符串。
    • 如果启用,我们会在第 3 行设置监听器 window.ethereum.on("accountsChanged") 监听 MetaMask 钱包中的状态变化,变化包括用户将其他帐户连接到去中心化应用程序、切换帐户或断开帐户。 如果至少连接了一个帐户,walletAddress 状态变量将更新为监听器返回的 accounts 数组中的第一个帐户。 否则,walletAddress 将设置为空字符串。

最后但同样重要的一点是,我们必须在 useEffect 函数中调用它:

就这些! 我们已经成功完成了所有钱包功能的编程! 现在,我们来完成最后一个任务:更新智能合约中存储的消息!

第 6 步:实现 updateMessage 函数

好嘞,伙计们,我们已经来到最后阶段了! 在 interact.js 文件中的 updateMessage 函数中,我们将执行以下操作:

  1. 确保我们想要在智能合约中发布的消息有效
  2. 使用 MetaMask 钱包签署每项交易
  3. HelloWorld.js 前端组件调用这个函数

这不会太耗时;我们把这个去中心化应用程序做完!

输入错误处理

显然,我们在函数开头加入一些输入错误处理代码是有意义的做法。

如果未安装 MetaMask 扩展,或者钱包尚未连接(即传入的 address 为空字符串),亦或是 message 为空字符串,我们希望函数能够提前返回。 让我们在 updateMessage 函数中添加以下错误处理代码:

现在,我们已经实现了正确的输入错误处理,接下来就是通过 MetaMask 来签署交易的时候了!

签署我们的交易

如果你已经对传统的 web3 以太坊交易驾轻就熟,那么接下来我们要编写的代码将会非常熟悉。 在输入错误处理代码下方,向 updateMessage 函数添加以下内容:

让我们来详细解析下这些代码的工作原理。 首先,我们设置了交易参数,具体内容如下:

  • to 指定接收者地址(我们的智能合约)
  • from 指定交易的签名者,即我们传入函数的 address 变量
  • data 包含对我们的 Hello World 智能合约中 update 方法的调用,其中将 message 字符串变量作为输入

接下来,我们进行对 window.ethereum.request 进行异步调用,请求 MetaMask 对交易进行签名。 请注意,在第 11 和 12 行中,我们指定了以太坊方法 eth_sendTransaction 并传入了我们的 transactionParameters

此时,MetaMask 将在浏览器中打开,并提示用户签署或拒绝交易。

  • 如果交易成功,该函数将返回一个 JSON 对象,其中 status JSX 字符串会提示用户前往 Etherscan 查看更多关于他们交易的信息。
  • 如果交易失败,该函数将返回一个 JSON 对象,其中 status 字符串会传递错误消息。

综上所述,我们的 updateMessage 函数应如下所示:

最后但同样重要的是,我们需要将 updateMessage 函数与我们的 HelloWorld.js 组件进行连接。

updateMessage 连接到 HelloWorld.js 前端

我们的 onUpdatePressed 函数应当通过异步调用导入的 updateMessage 函数,并根据交易成功或失败的结果来修改 status 状态变量:

// HelloWorld.js

const onUpdatePressed = async () => {
  const { status } = await updateMessage(walletAddress, newMessage)
  setStatus(status)
}

这个实现非常干净且简单。 你猜怎么着... 你的去中心化应用程序终于完工了!

现在就去测试一下 Update 按钮吧!

制作你自己的自定义去中心化应用程序

哇哦,你成功完成了本教程的全部内容! 回顾一下,你已经学习了如何:

  • 使用 MetaMask 钱包连接你的去中心化应用程序项目
  • 使用 Alchemy Web3 (opens in a new tab) API 从你的智能合约中读取数据
  • 使用 MetaMask 对以太坊交易签名

现在,你已经完全掌握本教程中的技能,可以着手开发属于自己的个性化去中心化应用程序项目了! 一如既往,如果你有任何问题,欢迎随时在 Alchemy Discord (opens in a new tab) 频道联系我们寻求帮助。 🧙‍♂️

完成本教程后,请在 Twitter 上标记我们 @alchemyplatform (opens in a new tab),告诉我们你的体验如何,或者你是否有任何反馈!

页面最后更新: 2026年4月3日

这篇教程对您有帮助吗?