查看此 GitHub 仓库 以获取本项目的
源代码。您可以随意克隆仓库并试玩,或跟着编码。
理解 Web3 Actions
智能合约允许您在事件触发、函数被调用或按周期时执行自定义代码。 Tenderly 通过 Web3 Actions 帮助您简化这一过程。您可以将 Web3 Actions 写在一个 JavaScript 文件中,或作为 NodeJS 项目部署它们。 Web3 Actions 还允许您执行通常使用 NodeJS 可以完成的任何操作。本教程将向您展示如何使用 Tenderly CLI 部署您的 Web3 Actions,并确保它们在满足特定条件时运行。了解更多关于 Web3 Actions 的信息,并通过 Web3 Actions 基础
了解如何在您的项目中使用它们。
项目概览
本项目的目的是向您展示如何部署智能合约以及编写多个 JavaScript 函数,Tenderly 会在事件发生时调用这些函数。您将学习如何使用 Tenderly CLI 将您的 Web3 Actions 部署到 Tenderly 的基础设施。 以下是执行此操作的分步概述:- 将智能合约部署到测试网络
- 使用 Tenderly CLI 设置运行 Web3 Actions 所需的 JavaScript 和配置文件
- 开发响应游戏事件的函数
- 使用 CLI 将这些函数部署到 Tenderly 的基础设施
0:部署智能合约
如果您正在跟着编码,可以跳过此步骤并使用我们 在
Tenderly 中部署并验证的合约。
- 游戏开始
- 玩家加入游戏
- 玩家落子
- 游戏结束
前提条件
创建两个账户 —— 将智能合约部署到任何 Tenderly 支持的网络。执行此操作最方便的方法是使用 Remix 和 Metamask 钱包插件。您需要两个具有正 Ether 余额的账户。这些账户将代表两位玩家。出于本教程的目的,我们使用 Sepolia。如果您计划按照本教程操作,可以使用 Sepolia faucet 向账户添加 Ether。 编译合约 - 使用 Remix IDE 编译智能合约。创建一个新的合约文件并添加 此处 找到的代码。.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=6f20b3090790b771b014692cdcf7be9a)
在 Remix IDE 中编译合约
Injected Web3。确认账户引用与 Metamask 中的账户相符后,点击 “Deploy”。
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=a8cfef3f43f563adbb3dfdd62177b50f)
将合约部署到测试网络
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=4a2339153a44fa17921bce22e01e703b)
复制合约地址
0x133...)并将其存储起来,因为我们在后续步骤中会用到它。
现在前往 Tenderly Dashboard 以验证合约。这将使我们能够在教程后面与合约进行交互。
1:通过 Tenderly CLI 设置 Web3 Actions
要继续本教程,您需要在您的计算机上安装 Tenderly CLI。按照 此指南 了解如何设置并验证访问。 安装 CLI 后,创建一个新目录并cd 进入其中。通过运行 tenderly actions init 命令初始化您的 Web3 Actions:
example
tenderly actions init --language javascript。
package.json 保存 npm 依赖项,当您部署 Web3
Action 时可用。tsconfig.json 文件保存与
actions 目录中 .ts 文件相关的 Typescript
配置。1.1. 添加井字棋合约的 ABI
在深入代码之前,请将编译器生成的 ABI 复制到 actions 目录。在我们的示例中,这是tdly-actions 目录。
在 Remix 中,前往 files/artifacts/TicTacToe.json,复制/粘贴文件内容,并将其粘贴到您项目的 TicTacToe.json 文件中。
1.2. 配置 Typescript 以将 JSON 文件作为模块导入
默认情况下,Typescript 不允许您将 JSON 文件作为模块导入。您需要配置 Typescript,以便能够将TicTacToe.json 文件作为模块导入并以对象形式访问。
前往您的 tsconfig.json 文件,在 compilerOptions 条目下包含以下两个配置:
example
2:编写处理新游戏事件的函数
编写一个在新游戏开始时执行的函数。让我们将 Typescript 文件重命名为更具描述性的名称:example
players 对象中。
2.1. 添加 Action
example.ts
example.ts
GameCreated 事件。
查看智能合约,我们可以看到只有一个事件是从 newGame 函数触发的,因此我们只关心第一个日志条目。我们可以使用 ethers.js 通过基于 TicTacToe.abi 解码 txEvent.logs[0].data 来获取 result,即 GameCreated 事件。
在这里我们可以访问在此特定游戏创建时关联的 ID:result.gameId。我们想使用全新的 Game 实例跟踪该特定游戏的数据:玩家及其所落的子。我们使用此命令将表示新游戏的对象保存到 Storage 中:context.storage.putJson(gameId, game)。
有了一个空的棋盘,我们为持久化未来的变化奠定了基础,这些变化将由其他 actions 处理。
您还可以编写自动化测试来验证 Web3 Action 的行为。仓库中提供了一些测试,但这超出了本教程的范围。
2.2. 指定 New Game Action 的调用
打开 tenderly.yaml。在specs 部分,定义 newGame(任意名称)的规范以调用函数 newGameAction。您可以像这样定义:newGameAction:newGameAction,首先定义包含函数的文件名,然后是函数名。
接下来,指定 Tenderly 需要监听以调用 action 的 trigger。这是一个 transaction 触发器,将在区块被打包时运行。我们为 network 3 上从指定地址合约发出 NewGame 事件时执行此操作。
将 TTT_CONTRACT_ADDRESS 替换为您的智能合约的实际地址。如果您想将合约部署到 Sepolia 以外的网络,请将该网络的 ID 指定为 network 的值。
将
YOUR_USERNAME 和
YOUR_PROJECT_SLUG 替换为您的 Tenderly 用户名和
项目 slug。您可以从 Dashboard URL 中复制它们:
https://dashboard.tenderly.co/{YOUR_USERNAME}/{YOUR_PROJECT_SLUG}/transactionsexample.yaml
2.3. 在 Tenderly 上验证您的井字棋智能合约
在部署合约之前,您需要在 Tenderly Dashboard 中验证它。 如果需要,可以 通过浏览器上传合约。上传后,选择 TicTacToe 合约、您部署到的网络以及合约地址。 点击 Add Contract 并按下图所示填写编译器选项:.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=01b598bfc2a6fbdf63ddb1533f10d40c)
验证合约并将其添加到 Tenderly
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=e99c1eb47857101c573bddf8131cf40d)
添加合约编译器信息
2.4. 将 Web3 Action 部署到 Tenderly
要部署您的 Web3 Action,请使用 Tenderly CLI 执行deploy 命令:
example
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=265837ec27e0378acfbc192074661c47)
Web3 Action 执行输出
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=c35821d0a7d044029d8e1047cfa89ff6)
打开 Web3 Action 部署信息
2.5. 尝试加入新游戏 🎉
要验证一切是否正常工作,请回到 Remix 并创建一个新游戏或多个游戏。 打开 TicTacToe 合约并点击newGame。Metamask 应提示您确认 Web3 Action 的执行。
事务提交到链上后,Remix 应产生类似以下的输出:
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=a0aad1196ca95ba6470b8b5f0edcee12)
在 Remix 中创建新游戏
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=7b2dedf972e0e4a6f9bc11fe27a33f85)
为您的 Web3 Action 打开执行历史选项卡
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=ec2d6994130b322e35d858124d104898)
执行历史数据
0xb,意味着这是该合约启动的第 11 个游戏。
2.6. 检查您 Web3 Actions 的 Storage
点击 “Go To Storage” 按钮以查看 Storage 的内容。每个启动的游戏都会在此键值映射中获得自己的存储槽。 当您打开我们刚刚创建的 ID 为 11 的游戏时,您会看到所有字段都是零,这意味着还没有玩家玩过该游戏。.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=88f11391c94122ab621cac47f2bc8bdd)
打开您 Web3 Action 的 Storage
3:添加一个 Web3 Action 处理新玩家加入游戏
玩家加入游戏时,应通过注册玩家的地址和其回合(1 或 2)来处理。 除了帮助我们检索事件数据的样板代码之外,playerJoinAction.ts 文件还包含允许我们执行以下操作的代码:
- 使用游戏 ID 从存储中读取当前游戏状态(
storage.getJson)。 - 检索玩家的地址并存储他们的回合:
game.players[player] = playerNumber - 使用
storage.putJson将更新的游戏对象保存到 Web3 Action 的存储中。
example.ts
3.1. 指定 PlayerJoinedGame Action 的调用
我们还需要扩展tenderly.yaml 文件中的 specs,以包括调用 Web3 Action 所需的规范。
example.yaml
3.2. 将 action 部署到 Tenderly
通过运行deploy 命令部署您的 Web3 Action。此命令还将重新部署之前部署的 Web3 Actions。
example.js
3.3. 尝试加入新游戏
前往 Remix 加入新游戏,将日志中的游戏编号添加到newGame 输入字段。要提交事务,点击 joinGame 按钮:
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=b775e77378e6a2706fe73eed98a3d79d)
在 Remix 中加入新游戏
playerNumber: 1。
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=c528321ee8ce731435000c561537e60a)
执行日志
joinGame。事务完成后,您将在 Execution History 中看到类似的日志输出。
3.4. 检查您 Web3 Actions 的 Storage
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=0e7db1401134a1407e2857804a9ec87e)
打开您 Web3 Action 的执行
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=d98b4d0220c38499bde2925eabcc1a6c)
打开 Storage 键值
3.5. 添加一个 Action 处理玩家落子
使用 Ethers,我们获取游戏 ID 并从存储中加载游戏实例。接下来,用玩家的输入game.players[player] 更新第 result.boardRow 行和第 result.boardCol 列的字段。
processNewGameState 函数会将棋盘记录到控制台,但您也可以在落子时发送推文、在链上触发新的事务,或以任何其他方式使用它。
example.ts
example.yaml
3.6. 落子玩游戏
要落子,请使用 Metamask 切换到加入游戏的第一位玩家。接下来,输入您收到的游戏编号以及棋盘上的行和列(0, 1)。.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=d87ef8ceb06a0bbd9474af3eb12c2169)
在 Remix 中落子
.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=781967b49bfe714430a3558cbdb95f5d)
Web3 Action 执行输出
步骤 4. 添加一个 Web3 Action 处理游戏结束
以下是GameOver 事件的代码。当玩家的落子赢得游戏或棋盘已满时,会触发 GameOver 事件。此事件在 PlayerMadeMove 事件之后触发。在 txEvent.logs 列表中,GameOver 事件是第二个元素。
获取 GameOver 事件的一种简单方法是通过 txEvent.logs[1] 访问它。不过,我们将实现一个更加健壮的解决方案,不依赖于事件触发的顺序和数量。
首先,您需要使用 ethers 通过 iface.getEventTopics 获取 gameOverTopic。这将为您提供相应的十六进制值。接下来,您需要在 txEvent.logs 中查找其 topics 列表包含 gameOverTopic 的日志条目。这就是我们的 GameOver 事件日志,我们可以使用 Ethers 解码它。
example.js
4.1. 指定 GameOver Action 的调用
最后,我们需要通过扩展tenderly.yaml 中的 specs 来添加调用 GameOver action 的规范:
example.yaml
4.2. 玩游戏
继续玩,直到有一位玩家赢得游戏或棋盘完全被填满。游戏结束时,Execution History 应如下所示:.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=eb6b64f3d40bbc512205c312c51d4c04)
游戏结束时的执行历史