Web3-Web3.js核心操作:Metamask、合约调用、事件订阅全指南

我们做了Solidity的合约代码,但是合约仅仅是一个后端逻辑;我们想要让用户来操作你的逻辑还需要做一个基本的网页。如果要做一个基本的网页,我们就要使用到以太坊基金发布的JavaScript库–Web3.js

什么事Web3.js

以太坊网络是由节点组成的,每一个节点都包含了区块链的一份拷贝。当你想要调用一份智能合约的一个方法,你需要从其中一个节点中查找并告诉它:

  1. 智能合约的地址
  2. 你想调用的方法,以及
  3. 你想传入那个方法的参数

以太坊节点只能识别一种叫做 *JSON-RPC* 的语言。这种语言直接读起来并不好懂。当你你想调用一个合约的方法的时候,需要发送的查询语句将会是这样的:

{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0xb60e8dd61c5d32be8058bb8eb970870f07233155","to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","gas":"0x76c0","gasPrice":"0x9184e72a000","value":"0x9184e72a","data":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}],"id":1}

幸运的是 Web3.js 把这些令人讨厌的查询语句都隐藏起来了, 所以你只需要与方便易懂的 JavaScript 界面进行交互即可。

你不需要构建上面的查询语句,在你的代码中调用一个函数看起来将是这样:

CryptoZombies.methods.createRandomZombie("Vitalik Nakamoto 🤔").send({ from: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", gas: "3000000" })

准备工作

// 用 NPM
npm install web3// 用 Yarn
yarn add web3// 用 Bower
bower install web3// ...或者其他。

你可以从 github 直接下载压缩后的 .js 文件 然后包含到你的项目文件中:

<script language="javascript" type="text/javascript" src="web3.min.js"></script>

Web3提供者

现在项目中有了Web3.js, 我们需要初始化它然后和区块链对话。首先我们需要 *Web3 Provider*.

要记住,以太坊是由共享同一份数据的相同拷贝的 节点 构成的。 在 Web3.js 里设置 Web3 的 Provider(提供者) 告诉我们的代码应该和 哪个节点 交互来处理我们的读写。这就好像在传统的 Web 应用程序中为你的 API 调用设置远程 Web 服务器的网址。

你可以运行你自己的以太坊节点来作为 Provider。 不过,有一个第三方的服务,可以让你的生活变得轻松点,让你不必为了给你的用户提供DApp而维护一个以太坊节点— *Infura*.

Infura

Infura 是一个服务,它维护了很多以太坊节点并提供了一个缓存层来实现高速读取。你可以用他们的 API 来免费访问这个服务。 用 Infura 作为节点提供者,你可以不用自己运营节点就能很可靠地向以太坊发送、接收信息。

你可以通过这样把 Infura 作为你的 Web3 节点提供者:

var web3 = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));

不过,因为我们的 DApp 将被很多人使用,这些用户不单会从区块链读取信息,还会向区块链 入信息,我们需要用一个方法让用户可以用他们的私钥给事务签名。

注意: 以太坊 (以及通常意义上的 blockchains )使用一个公钥/私钥对来对给事务做数字签名。把它想成一个数字签名的异常安全的密码。这样当我修改区块链上的数据的时候,我可以用我的公钥来 证明 我就是签名的那个。但是因为没人知道我的私钥,所以没人能伪造我的事务。

加密学非常复杂,所以除非你是个专家并且的确知道自己在做什么,你最好不要在你应用的前端中管理你用户的私钥。

不过幸运的是,你并不需要,已经有可以帮你处理这件事的服务了: *Metamask*.

Metamask

Metamask 是 Chrome 和 Firefox 的浏览器扩展, 它能让用户安全地维护他们的以太坊账户和私钥, 并用他们的账户和使用 Web3.js 的网站互动(如果你还没用过它,你肯定会想去安装的——这样你的浏览器就能使用 Web3.js 了,然后你就可以和任何与以太坊区块链通信的网站交互了)

作为开发者,如果你想让用户从他们的浏览器里通过网站和你的DApp交互(就像我们在 CryptoZombies 游戏里一样),你肯定会想要兼容 Metamask 的。

注意: Metamask 默认使用 Infura 的服务器做为 web3 提供者。 就像我们上面做的那样。不过它还为用户提供了选择他们自己 Web3 提供者的选项。所以使用 Metamask 的 web3 提供者,你就给了用户选择权,而自己无需操心这一块。

使用Metamask的Web3的提供者

Metamask 把它的 web3 提供者注入到浏览器的全局 JavaScript对象web3中。所以你的应用可以检查 web3 是否存在。若存在就使用 web3.currentProvider 作为它的提供者。

这里是一些 Metamask 提供的示例代码,用来检查用户是否安装了MetaMask,如果没有安装就告诉用户需要安装MetaMask来使用我们的应用。

window.addEventListener('load', function() {// 检查web3是否已经注入到(Mist/MetaMask)if (typeof web3 !== 'undefined') {// 使用 Mist/MetaMask 的提供者web3js = new Web3(web3.currentProvider);} else {// 处理用户没安装的情况, 比如显示一个消息// 告诉他们要安装 MetaMask 来使用我们的应用}// 现在你可以启动你的应用并自由访问 Web3.js:startApp()})

你可以在你所有的应用中使用这段样板代码,好检查用户是否安装以及告诉用户安装 MetaMask。

注意: 除了MetaMask,你的用户也可能在使用其他他的私钥管理应用,比如 Mist 浏览器。不过,它们都实现了相同的模式来注入 web3 变量。所以我这里描述的方法对两者是通用的。

和合约对话

我们已经用 MetaMask 的 Web3 提供者初始化了 Web3.js。接下来就让它和我们的智能合约对话。

Web3.js 需要两个东西来和你的合约对话: 它的 地址 和它的 *ABI*

合约地址

在写完智能合约后,我们需要去编译合约然后把合约部署到以太坊。部署后的合约会生成一个合约地址,就是以太坊上的永久地址。

合约 ABI

另一个 Web3.js 为了要和你的智能合约对话而需要的东西是 *ABI*

ABI 意为应用二进制接口(Application Binary Interface)。 基本上,它是以 JSON 格式表示合约的方法,告诉 Web3.js 如何以合同理解的方式格式化函数调用。

当你编译你的合约向以太坊部署时, Solidity 编译器会给你 ABI,所以除了合约地址,你还需要把这个也复制下来。

因为我们这一课不会讲述部署,所以现在我们已经帮你编译了 ABI 并放在了名为cryptozombies_abi.js,文件中,保存在一个名为 cryptoZombiesABI 的变量中。

如果我们将cryptozombies_abi.js 包含进我们的项目,我们就能通过那个变量访问 CryptoZombies ABI 。

var myContract = new web3js.eth.Contract(myABI, myContractAddress);

调用合约函数

Web3.js 有两个方法来调用我们合约的函数: call and send.

Call

call 用来调用 viewpure 函数。它只运行在本地节点,不会在区块链上创建事务。

复习: viewpure 函数是只读的并不会改变区块链的状态。它们也不会消耗任何gas。用户也不会被要求用MetaMask对事务签名。

使用 Web3.js,你可以如下 call 一个名为myMethod的方法并传入一个 123 作为参数:

myContract.methods.myMethod(123).call()
Send

send 将创建一个事务并改变区块链上的数据。你需要用 send 来调用任何非 view 或者 pure 的函数。

注意: send 一个事务将要求用户支付gas,并会要求弹出对话框请求用户使用 Metamask 对事务签名。在我们使用 Metamask 作为我们的 web3 提供者的时候,所有这一切都会在我们调用 send() 的时候自动发生。而我们自己无需在代码中操心这一切,挺爽的吧。

使用 Web3.js, 你可以像这样 send 一个事务调用myMethod 并传入 123 作为参数:

myContract.methods.myMethod(123).send()

语法几乎 call()一模一样。

MetaMask账户

MetaMask 允许用户在扩展中管理多个账户。

我们可以通过这样来获取 web3 变量中激活的当前账户:

var userAccount = web3.eth.accounts[0]

因为用户可以随时在 MetaMask 中切换账户,我们的应用需要监控这个变量,一旦改变就要相应更新界面。

我们可以通过 setInterval 方法来做:

var accountInterval = setInterval(function() {// 检查账户是否切换if (web3.eth.accounts[0] !== userAccount) {userAccount = web3.eth.accounts[0];// 调用一些方法来更新界面updateInterface();}
}, 100);

这段代码做的是,每100毫秒检查一次 userAccount 是否还等于 web3.eth.accounts[0] (比如:用户是否还激活了那个账户)。若不等,则将 当前激活用户赋值给 userAccount,然后调用一个函数来更新界面。

发送事务

现在我们来看看用 send 函数来修改我们智能合约里面的数据。

相对 call 函数,send 函数有如下主要区别:

  1. send 一个事务需要一个 from 地址来表明谁在调用这个函数(也就是你 Solidity 代码里的 msg.sender )。 我们需要这是我们 DApp 的用户,这样一来 MetaMask 才会弹出提示让他们对事务签名。

  2. send 一个事务将花费 gas

  3. 在用户 send 一个事务到该事务对区块链产生实际影响之间有一个不可忽略的延迟。这是因为我们必须等待事务被包含进一个区块里,以太坊上一个区块的时间平均下来是15秒左右。如果当前在以太坊上有大量挂起事务或者用户发送了过低的 gas 价格,我们的事务可能需要等待数个区块才能被包含进去,往往可能花费数分钟。

    所以在我们的代码中我们需要编写逻辑来处理这部分异步特性。

我们的函数 send 一个事务到我们的 Web3 提供者,然后链式添加一些事件监听:

  • receipt 将在合约被包含进以太坊区块上以后被触发,这意味着僵尸被创建并保存进我们的合约了。
  • error 将在事务未被成功包含进区块后触发,比如用户未支付足够的 gas。我们需要在界面中通知用户事务失败以便他们可以再次尝试。

注意:你可以在调用 send 时选择指定 gasgasPrice, 例如: .send({ from: userAccount, gas: 3000000 })。如果你不指定,MetaMask 将让用户自己选择数值。

调用 Payable 函数

attack, changeName, 以及 changeDna 的逻辑将非常雷同,所以本课将不会花时间在上面。

实际上,在调用这些函数的时候已经有了非常多的重复逻辑。所以最好是重构代码把相同的代码写成一个函数。(并对txStatus使用模板系统——我们已经看到用类似 Vue.js 类的框架是多么整洁)

我们来看看另外一种 Web3.js 中需要特殊对待的函数 — payable 函数。

function levelUp(uint _zombieId) external payable {require(msg.value == levelUpFee);zombies[_zombieId].level++;
}

和函数一起发送以太非常简单,只有一点需要注意: 我们需要指定发送多少 wei,而不是以太。

啥是 Wei?

一个 wei 是以太的最小单位 — 1 ether 等于 10^18 wei

太多0要数了,不过幸运的是 Web3.js 有一个转换工具来帮我们做这件事:

// 把 1 ETH 转换成 Wei
web3js.utils.toWei("1", "ether");

在我们的 DApp 里, 我们设置了 levelUpFee = 0.001 ether,所以调用 levelUp 方法的时候,我们可以让用户用以下的代码同时发送 0.001 以太:

cryptoZombies.methods.levelUp(zombieId)
.send({ from: userAccount, value: web3js.utils.toWei("0.001","ether") })

订阅事件

通过 Web3.js 和合约交互非常简单直接——一旦你的环境建立起来, call 函数和 send 事务和普通的网络API并没有多少不同。

还有一点东西我们想要讲到——订阅合约事件

在 Web3.js里, 你可以 订阅 一个事件,这样你的 Web3 提供者可以在每次事件发生后触发你的一些代码逻辑:

cryptoZombies.events.NewZombie()
.on("data", function(event) {let zombie = event.returnValues;console.log("一个新僵尸诞生了!", zombie.zombieId, zombie.name, zombie.dna);
}).on('error', console.error);
使用 indexed

为了筛选仅和当前用户相关的事件,我们的 Solidity 合约将必须使用 indexed 关键字,就像我们在 ERC721 实现中的Transfer 事件中那样:

event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);

在这种情况下, 因为_from_to 都是 indexed,这就意味着我们可以在前端事件监听中过滤事件

cryptoZombies.events.Transfer({ filter: { _to: userAccount } })
.on("data", function(event) {let data = event.returnValues;// 当前用户更新了一个僵尸!更新界面来显示
}).on('error', console.error);

看到了吧, 使用 eventindexed 字段对于监听合约中的更改并将其反映到 DApp 的前端界面中是非常有用的做法。

查询过去的事件

我们甚至可以用 getPastEvents 查询过去的事件,并用过滤器 fromBlocktoBlock 给 Solidity 一个事件日志的时间范围(“block” 在这里代表以太坊区块编号):

cryptoZombies.getPastEvents("NewZombie", { fromBlock: 0, toBlock: 'latest' })
.then(function(events) {// events 是可以用来遍历的 `event` 对象 // 这段代码将返回给我们从开始以来创建的僵尸列表
});

因为你可以用这个方法来查询从最开始起的事件日志,这就有了一个非常有趣的用例: 用事件来作为一种更便宜的存储

若你还能记得,在区块链上保存数据是 Solidity 中最贵的操作之一。但是用事件就便宜太多太多了。

这里的短板是,事件不能从智能合约本身读取。但是,如果你有一些数据需要永久性地记录在区块链中以便可以在应用的前端中读取,这将是一个很好的用例。这些数据不会影响智能合约向前的状态。

举个栗子,我们可以用事件来作为僵尸战斗的历史纪录——我们可以在每次僵尸攻击别人以及有一方胜出的时候产生一个事件。智能合约不需要这些数据来计算任何接下来的事情,但是这对我们在前端向用户展示来说是非常有用的东西。

Web3.js 事件 和 MetaMask

上面的示例代码是针对 Web3.js 最新版1.0的,此版本使用了 *WebSockets* 来订阅事件。

但是,MetaMask 尚且不支持最新的事件 API (尽管如此,他们已经在实现这部分功能了, 点击这里 查看进度)

所以现在我们必须使用一个单独 Web3 提供者,它针对事件提供了WebSockets支持。 我们可以用 Infura 来像实例化第二份拷贝:

var web3Infura = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));
var czEvents = new web3Infura.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);

然后我们将使用 czEvents.events.Transfer 来监听事件,而不再使用 cryptoZombies.events.Transfer。我们将继续在课程的其他部分使用 cryptoZombies.methods

将来,在 MetaMask 升级了 API 支持 Web3.js 后,我们就不用这么做了。但是现在我们还是要这么做,以使用 Web3.js 更好的最新语法来监听事件。

总结

Web3.js是以太坊官方JavaScript库,简化了与智能合约的交互过程。本文介绍了Web3.js的核心功能与使用方法:1) 通过Web3提供者(如Infura或Metamask)连接以太坊节点,2) 使用合约地址和ABI与智能合约对话,3) 区分call和send方法调用合约函数。重点讲解了如何集成Metamask进行用户账户管理,包括检测账户变更和事务签名。该指南为开发者提供了Web3.js的基本操作框架,包括初始化、合约交互和事件处理等关键环节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/85023.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/85023.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/85023.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

四色(定理/猜想)染色算法小软件Version1.11

四色(定理/猜想)染色算法小软件Version1.11 2025.6.16 开发者&#xff1a;路人甲/打酱油 增加了【自思肯普法】 为什么加上【自思】两字&#xff1f;因为我也看不明英文的PDF的四色定理证明文档&#xff0c;分什么成千上百种类来证明。我就是百度下&#xff0c;看相关介绍&am…

【Linux驱动开发 ---- 2_深入理解内核模块】

Linux驱动开发 ---- 2_深入理解内核模块 目录 Linux驱动开发 ---- 2_深入理解内核模块学习目标时间安排建议 理论学习1. 什么是内核模块&#xff1f;2. 模块加载与卸载3. 内核模块开发基础 实践任务任务1&#xff1a;准备开发环境任务2&#xff1a;编写简单内核模块任务3&#…

Linux SUID提权 案例以及知识点提高分析

目录 &#x1f4da; Linux SUID 提权原理与红队实践 &#x1f680; 概述 &#x1f6e0;️ SUID 提权原理 核心机制 技术栈 &#x1f50d; 案例背景&#xff1a;sudo -l 与 .monit 脚本 案例信息 脚本内容 &#x1f9ea; 提权步骤分解 &#x1f4cb; 1. 检查 sudo 权限…

S参数与串扰-信号完整性分析

S参数与串扰: 四端口网络S参数中&#xff0c;仍表示反射&#xff0c;表示信号的传输。根据S参数的定义&#xff0c;和两个参数的含义为 当只有端口1有正弦信号激励源时&#xff0c;从端口3和端口4出来的正弦信号只能是互连结构内部耦合过来的&#xff0c;因此表示的是近端串扰…

Android OkHttp 框架超时设置详解

OkHttp 提供了四种不同的超时设置&#xff0c;每种针对网络请求的不同阶段&#xff1a; 1. callTimeout (调用超时) 作用&#xff1a;控制整个调用从开始到结束的总时间&#xff0c;包括所有重定向和重试 默认值&#xff1a;0 (不超时) 场景&#xff1a;当你希望限制整个请求…

如何让LLM通过反思来提升生成sql的正确率 - 以Gemini生成sql为例

什么是Agent Reflection 通常指 “智能体反思”&#xff0c;即让 AI 系统通过自我反思机制优化决策或任务处理过程&#xff0c;类似人类通过复盘改进策略。 创建一个 SQL Agent 导入相关的库 import openai import pandas as pd import sqlite3 from util.get_schema impor…

数字IC学习笔记(二)

数字IC学习笔记&#xff08;二&#xff09; 宏定义&#xff0c;异步FIFO, 时钟来源&#xff0c;复位信号 文章目录 数字IC学习笔记&#xff08;二&#xff09;1. define宏定义的使用2&#xff0c;异步FIFO原理3&#xff0c;时钟来源4&#xff0c;复位参考资料 1. define宏定义的…

日志输出功能

当程序运行出现问题时&#xff0c;日志记录是一种非常有用的工具&#xff0c;它可以帮助我们追踪和定位问题。在 MicroPython 中&#xff0c;可以使用 log 模块来记录程序运行中的信息。本文将介绍 log 模块的使用方法和常见功能。 日志级别 log.DEBUG 常量&#xff0c;用来…

【JVM】- 类加载与字节码结构1

类文件结构 ClassFile {u4 magic;u2 minor_version;u2 major_version;u2 constant_pool_count;cp_info constant_pool[constant_pool_count-1];u2 access_flags;u2 this_class;u2 …

Android及Harmonyos实现图片进度显示效果

鸿蒙Harmonyos实现&#xff0c;使用ImageKnife自定义transform来实现图片进度效果 import { Context } from ohos.abilityAccessCtrl; import { image } from kit.ImageKit; import { drawing } from kit.ArkGraphics2D; import { GrayScaleTransformation, PixelMapTransform…

linux 中的自动化之systemd

linux | 自动化之systemd linux 中的自动化之systemd 学习总是循序渐进的。 linux 中程序的自动化&#xff0c;包括早期手动启动&#xff0c;查看启动后的日志、分析启动情况&#xff0c;再到后面封装脚本(大致要求启动后检查是否挂了&#xff0c;挂了拉起&#xff0c;没挂跳过…

【编译工具】CodeRider 2.0:驭码 CodeRider 2.0 全流程智能研发协作平台深度技术测评报告

目录 前言&#xff1a;CodeRider 2.0 简介 &#xff08;1&#xff09;核心功能 &#xff08;2&#xff09;适用场景 &#xff08;3&#xff09;优势 一、产品概述与技术架构 &#xff08;1&#xff09;产品定位与核心价值 &#xff08;2&#xff09;技术架构解析 &…

抓包 TCP 四次挥手报文

文章目录 抓包 TCP 三次握手报文一、第一次挥手二、第二次挥手三、第三次挥手四、第四次挥手 抓包 TCP 三次握手报文 抓包 TCP 三次握手报文 一、第一次挥手 二、第二次挥手 三、第三次挥手 四、第四次挥手

KMP(Kotlin Multiplatform)发布Web版本乱码

一、背景 最近用KMP尝试运行在Android、iOS、desktop都成功了&#xff0c;网络数据访问也正常。 可是当运行wasmJs的时候遇到了2个较大的问题。 中文字体出现乱码。 出现了跨域问题。 首先贴一下每个平台的运行截图&#xff1a; Android iOS Desktop 二、问题 当web跑起…

一个应用程序或移动网站项目提供最佳UI解决方案

一个应用程序或移动网站项目提供最佳UI解决方案 此套件是用大量的爱和辛勤工作制作的&#xff0c;为您的下一个应用程序或移动网站项目提供最佳解决方案。120个完全可编辑的界面&#xff0c;分为10个类别和2种文件格式&#xff0c;Photoshop和AI。简单易用的结构将允许您以所…

Android studio打包生成jar包文件

Android studio打包生成jar包文件 一 项目配置1.修改 app/build.gradle2.修改 AndroidManifest.xml 二 打 Jar 包1.修改 app/build.gradle2.编译生成 Jar 包 一 项目配置 1.修改 app/build.gradle 将com.android.application改成com.android.library注释掉applicationId 2.…

JAVA类加载机制(jdk8)

三句话总结JDK8的类加载机制&#xff1a; 类缓存&#xff1a;每个类加载器对他加载过的类都有一个缓存。双亲委派&#xff1a;向上委托查找&#xff0c;向下委托加载。沙箱保护机制&#xff1a;不允许应用程序加载JDK内部的系统类。 JDK8的类加载体系 类加载器的核心方法 //…

更进一步深入的研究ObRegisterCallBack

引入 我们如果想hook对象的回调,在上篇文章里我们已经知道了对象回调函数存在一个列表里面&#xff0c;我们通过dt可以看见&#xff0c;这里他是一个LIST_ENTRY结构&#xff0c;但是实际调用的时候&#xff0c;这样是行不通的&#xff0c;说明它结构不对 0: kd> dt _OBJEC…

Nginx-3 Nginx 的负载均衡策略

Nginx-3 Nginx 的负载均衡策略 Nginx 的负载均衡其实就是指将请求按照一定的策略转发给服务集群中的一台&#xff0c;提高了服务集群的可用性&#xff0c;解决数据流量过大、网络负荷过重的问题。 AKF 扩展立方体 分为 3 个方向负载&#xff1a; x 轴&#xff1a;增加实例数…