编程崽

登录

一叶在编程苦海沉沦的扁舟之上,我是那只激情自射的崽

Web3.0开发下-功能代码示例

Web3.0开发下-功能代码示例

以太坊官网:https://ethereum.org/zh/

web3官方教程:https://web3js.readthedocs.io/

以太坊区块链浏览器:https://etherscan.io/

以太坊开发文档:https://ethereum.org/zh/developers/docs/

以太坊JSON-RPC 应用程序接口:https://ethereum.org/zh/developers/docs/apis/json-rpc/

小狐狸(MetaMask)文档:https://docs.metamask.io/

币安钱包文档:https://docs.bnbchain.org/docs/wallet_api

:以下的前端界面使用vite + react开发,每次粘贴的都是片段。

实例化web3对象

实例化Web3的过程,就是给web3.js设置一个 服务提供者

服务提供者就是一个包含节点服务器的 RPC 地址的对象,web3和节点服务交互时,会把所有的请求给这个服务提供者,服务提供者再去和节点服务器交互。

所以实例化Web对象需要先生成服务提供者。

服务提供者

RPC有三种类型:HTTP、WebSocket、IPC,所以创建服务提供者也有三个方法:

但实际这一步可以省略不写,这部分主要是了解内部逻辑,原因见后续内容。

js 复制代码
// HTTP类型的服务提供者
const provider = new Web3.providers.HttpProvider(RPC_HTTP)
// Websocket类型的服务提供者
const provider = new Web3.providers.WebsocketProvider(RPC_WebSocket)
// IPC类型的服务提供者
const provider = new Web3.providers.IpcProvider(RPC_IPC)

使用服务提供者实例化Web3

有了服务提供者,可以实例化web3了:

js 复制代码
// 使用上一步创建的服务提供者实例化 web3
const web3 = new Web3(provider)

但其实,如果是使用RPC,我们不用手动创建服务提供者。

可以直接把RPC传给Web3来进行实例化,Web3会自动判断生成服务提供者并使用:

js 复制代码
const web3 = new Web3(RPC_HTTP)
// or
const web3 = new Web3(RPC_WebSocket)
// or
const web3 = new Web3(RPC_IPC)

使用钱包作为服务提供者

当浏览器安装了小狐狸等钱包插件,那么我们可以直接使用钱包作为服务提供者。

如果刚刚安装了小狐狸,甚至还没有打开、没有配置过,那他默认配置的是以太坊主网络。

js 复制代码
// 小狐狸插件未正常启用
if (!window.ethereum) return

// 可以获取网络 chainId,判断是否是自己想要的网络
console.log(parseInt(window.ethereum.chainId, 16))
// 如果是以太坊主网,则打印数字 5

// 把 window.ethereum 作为服务提供者,实例化 web3
const web3 = new Web3(window.ethereum)

自动获取当前页面的服务提供者

在以太坊兼容的浏览器中使用 web3.js 时,Web3.givenProvider 将由该浏览器设置为当前的本机提供程序,否则为 null。

也就是说,如果浏览器已经安装了小狐狸,那么:

js 复制代码
Web3.givenProvider === window.ethereum

平时可以这样使用:

js 复制代码
// RPC 是自己提前定义好的地址
const web3 = new Web3(Web3.givenProvider || RPC)

但如果这么使用,无法保证小狐狸当前连接的网络是自己需要的,还是要开发者判断。

查看和切换服务提供者

实例化web3后,可以随时查看当前的服务提供者对象:

js 复制代码
const currentProvider = web3.currentProvider

如果当前的服务提供者是自己设置的 RPC,那么 currentProvider 对象为:

js 复制代码
{
    "timeout": 0,
    "connected": false,
    "host": "https://...省略", // 这一项就是 RPC 地址
    "httpsAgent": {}
}

如果当前的服务提供者是钱包,那么 currentProvider 对象其实就是钱包对象:

js 复制代码
console.log(currentProvider === window.ethereum)
// 打印 true

我们可以直接给现有的web3实例切换服务提供者:

js 复制代码
// provider 是新的服务提供者,可以是 RPC,可以是钱包对象,也可以是生成好的 provider 对象
web3.setProvider(provider)

通过小狐狸钱包获取账户地址

现在页面开发,只把钱包作为服务提供者的只是少数,更多的是通过钱包,获取当前用户使用的账户地址,能省的用户手动在页面输入这一步。

下面的代码执行后,小狐狸好会弹出提示框,按提示确认后,即可允许某几个账户连接当前网站。

有的教程使用的可能是 window.ethereum.enable() 这个方法,这个方法已废弃,不再建议使用。

js 复制代码
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' })
console.log(accounts)
// ['0x53c985340d30535C0a4E9513F1b009ACA6A690c2']
// 打印的是用户允许连接此网站的账户数组,正常是只有一项,如果是多项,那第一项是当前用户要使用的账户地址。

注意:钱包会为钱包中的每个账户,都记录「已连接的网站」列表,当用户允许某网站连接账户后,会自动把此网站地址放入「已连接的网站」列表中。

日后,这个页面再请求账户列表,小狐狸会直接返回一个数组,数组中只有一项,就是允许连接此网站的某个账户地址,优先返回用户当前正在使用的账户地址。

window.ethereum 作为服务提供者实例化 web3 后,可以使用 web3 的方法读取这个数组,如果没有账户连接过此页面,那得到的是空数组。

如果是使用 RPC 实例化的 web3,则只能获取到一个空数组。

js 复制代码
const accounts = await web3.eth.getAccounts()

监听小狐狸事件

监听小狐狸这个插件的一些事件,以便当用户修改了账户、断开了网站连接时做应对处理。

有些事件可以不连接小狐狸,页面就能监听触发;而有些在需要连接后再操作小狐狸,页面才能监听到。

js 复制代码
// 小狐狸MetaMask的事件监听
const events = {
  accountsChanged(accounts) {
    // 指的是此页面连接的账户,连接小狐狸后再切换账户,此事件才会触发
    console.log('切换了账户', accounts)
  },
  chainChanged(chainId) {
    // 16转10进制
    chainId = parseInt(chainId, 16)
    console.log('切换了链', chainId)
  },
  connect(connectInfo) {
    console.log('连接合约成功', connectInfo)
  },
  disconnect(error) {
    console.log('断开连接', error)
  },
  message(message) {
    console.log('一些消息', message)
  },
}

// 添加小狐狸的事件监听
export function addEvent() {
  if (!window.ethereum) return [false, '小狐狸插件未正常启用']
  window.ethereum.on('accountsChanged', events.accountsChanged)
  window.ethereum.on('chainChanged', events.chainChanged)
  window.ethereum.on('connect', events.connect)
  window.ethereum.on('disconnect', events.disconnect)
  window.ethereum.on('message', events.message)
  return [true]
}

// 移除小狐狸的事件监听
export function removeEvent() {
  if (!window.ethereum) return
  window.ethereum.removeListener('accountsChanged', events.accountsChanged)
  window.ethereum.removeListener('chainChanged', events.chainChanged)
  window.ethereum.removeListener('connect', events.connect)
  window.ethereum.removeListener('disconnect', events.disconnect)
  window.ethereum.removeListener('message', events.message)
}

调用小狐狸方法

调用小狐狸方法文档:RPC API

这里只列举两个可能常用的方法。

切换网络

可能用户当前连接的网络不是自己页面需要的,则需要调用小狐狸方法来切换。

小狐狸会弹出确认提示框,用户确认后切换成功。

注意,如果用户的小狐狸没有这个网络,则会报错,error.code 为 4902。

js 复制代码
// 以太坊主网络的 chainId 为 1,这里需要转为 16 进制
const chainId = `0x${Number(1).toString(16)}`
await window.ethereum.request({
  method: 'wallet_switchEthereumChain',
  params: [{ chainId }],
})

添加网络

可能用户的小狐狸钱包中,没有我们页面需要的网络,此时我们可以调小狐狸方法来添加。

小狐狸同样会弹框询问用户是否允许添加,添加完成后会要求用户切换到此网络。

下面的添加是不会成功的,因为小狐狸已经内置了这个官方的测试网络,下面的网络配置只是演示:

js 复制代码
// goerli 测试网络的配置
const goerliTestnet = {
  chainId: `0x${Number(5).toString(16)}`, // 网络的 chainId
  chainName: 'Goerli 测试网络', // 网络的名称,展示给用户看的,自定义即可
  nativeCurrency: {
    name: 'GoerliETH', // 主要币种的名称
    symbol: 'GoerliETH', // 主要币种的符号
    decimals: 18, // 主要币种的精度
  },
  rpcUrls: ['https://goerli.infura.io/v3/'], // 节点服务的 RPC 地址
  blockExplorerUrls: ['https://goerli.etherscan.io'], // 区块链浏览器地址
}

// 调用方法添加
await window.ethereum.request({
  method: 'wallet_addEthereumChain',
  params: [goerliTestnet],
})

添加代币

可能用户的小狐狸钱包中,没有我们需要的某种代币,此时我们可以调小狐狸方法来给这个账户添加。

无论此账户当前是否有这种代币,调用方法后,小狐狸必然会弹框请求用户添加这一代币。

js 复制代码
// goerli 测试网络的配置
const CoinConfig = {
  type: 'ERC20',
  options: {
    address: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
    symbol: 'FOO',
    decimals: 18,
    image: 'https://foo.io/token-image.svg',
  },
}

// 调用方法添加
await window.ethereum.request({
  method: 'wallet_watchAsset',
  params: CoinConfig,
})

提前准备的工具方法js

下面两个方法在展示余额、发起交易时,非常常用。

因为以太坊世界的数量、余额使用的单位是wei,但平时展示给用户的都是以某币种为单位的,需要使用这个币种的精度来转换一下。

js 复制代码
// @/utils/index.js 文件
import Decimal from 'decimal.js'

/**
 * wei 为单位值转为币种单位的字符串值
 * @param {*} amount 从区块链中取的的以 wei 为单位余额
 * @param {*} decimals 此币种的精度
 * @returns {String} 币种为单位的字符串值
 */
export function format2Balance(amount, decimals) {
  if (!amount || !decimals) return amount
  const decimalsFlag = Math.pow(10, decimals)
  const amountDec = new Decimal(amount);
  const balance = amountDec.div(decimalsFlag).toString()
  return balance
}

/**
 * 币种单位的值转为 wei 为单位的字符串值
 * @param {*} balance 币种为单位的值
 * @param {*} decimals 此币种的精度
 * @returns {String} wei为单位的字符串值
 */
export function format2Amount(balance, decimals) {
  if (!balance || !decimals) return balance
  const decimalsFlag = Math.pow(10, decimals)
  const balanceDec = new Decimal(balance);
  const amount = balanceDec.mul(decimalsFlag).toString()
  return amount
}

获取一些账户、余额、网络信息

js 复制代码
import { format2Balance } from '@/utils'
const web3 = '...' // 已实例化完成

// 获取账户地址
async function getAccount() {
  const accountArr = await web3.eth.getAccounts()
  const account = accountArr[0]
}
// 获取余额
async function getBalance(account) {
  const amount = await web3.eth.getBalance(account)
  const balance = format2Balance(amount, 18) // ETH 的精度是 18
}
// 获取链id
async function getChainId() {
  const chainId = await web3.eth.getChainId()
  // or
  const chainId = parseInt(window.ethereum.chainId, 16)
}
// 获取网络id
async function getNetworkID() {
  const netId = await web3.eth.net.getId()
}
// 获取网络类型/名称,比如 goerli
async function getNetType() {
  const netType = await web3.eth.net.getNetworkType()
}

网络ID(NetworkID),主要用来在网络层标识当前的区块链网络,NetworkId 不一致的两个节点无法建立连接。

以太坊本来只有网络ID,并没有链id(ChainId)。

但以太坊发生了分叉,为了避免一个交易在签名之后被重复在不同的链上提交,在EIP155引入了ChainID,通过在签名信息中加入ChainID避免了bug。

创建账户

创建账户,拿到新账户地址和私钥。

js 复制代码
const web3 = '...' // 已实例化完成

async function createAccount() {
  // 可选参数 entropy:增加熵的随机字符串。如果给出,它应该至少为 32 个字符。如果没有给出一个随机字符串,将使用randomhex生成
  // 下面我就没有传参数
  const result = await web3.eth.accounts.create()
  console.log('账户地址', result.address)
  console.log('账户私钥', result.privateKey)
}

判断一个地址是否符合规范

当进行转账、查询余额时,偶尔需要用户输入地址,作为参数和区块链交互。

web3.js包含工具对象,可以使用里面的方法来校验地址是否合法,方法返回Boolean值。

因为这个工具对象和区块链无关,可以直接使用未实例化的 Web3 类。

web3 工具类的文档地址:web3.utils

js 复制代码
const address = '一个地址'
// 直接从未实例化的 Web3 类中读取工具方法
const isAddress = Web3.utils.isAddress(address)
// 或者从已经实例化后的 web3 中读取工具方法
const isAddress = web3.utils.isAddress(address)

签名消息字符串

签名消息是为了加密,签名的过程会使用账户地址和账户私钥进行加密,事后可以通过反向计算,算得该条加密字符串是否是某账户的私钥签名的。

调用钱包方法

连接小狐狸后,直接使用小狐狸进行消息签名,会调起小狐狸进行确认:

js 复制代码
// 签名消息
async function sign() {
  const message = '这是要签名的消息'
  const account = '...' // 要签名的账户,也就是当前已连接网站的小狐狸中的当前的账户地址
  const signature = await window.ethereum.request({
    method: "personal_sign",
    params: [message, account],
  })
}

调用web3.js方法

这个需要先连接小狐狸,也是会调起小狐狸,和直接调用小狐狸的签名方法效果一样。

web3.js只是把钱包的签名方法进行了包装兼容而已。

js 复制代码
// 签名消息
async function sign() {
  const message = '这是要签名的消息'
  const account = '...' // 要签名的账户,也就是当前已连接网站的小狐狸中的当前的账户地址
  const signature = await web3.eth.personal.sign(message, account)
}

币安钱包中的币安链(BEP2)网络的签名

币安链钱包比较特殊,币安链钱包中的BSC链(也是ERC20网络,币安钱包对ETH的兼容)可以仍然使用上面的 web3 调用。

但币安链BEP2网络,需要直接调用币安钱包对象的 BinanceChain.bnbSign 方法来进行签名(可至钱包文档页查看更详细内容):

js 复制代码
const message = '这是要签名的消息'
const account = '...' // 要签名的账户,也就是当前已连接网站的小狐狸中的当前的账户地址
// 直接调用币安钱包对象的方法来签名
// 返回的 signature 和 publicKey 可能在验证时需要
let { signature, publicKey } = await BinanceChain.bnbSign(account, message)

验证签名

验证签名,需要拿到被加密的原信息字符串加密后的signature,使用web3.js的方法,能得到签名时的账户地址

判断这个账户地址是否是预期的账户,就可以验证了。

js 复制代码
// dataThatWasSigned 被加密的原信息字符串
// signature 加密后的信息
const account = await web3.eth.personal.ecRecover(dataThatWasSigned, signature)

使用智能合约

以太坊中的所有代币、NFT,本质都是智能合约。

WETH这个代币,本质就是一个符合 ERC-20 的智能合约,它的地址就是合约地址。

哪个用户拥有多少WETH,都是这个合约去保存的,想要转账,也需要通知这个合约去交互。

连接合约

连接合约最简单,需要下面两个参数:

  • 合约地址:比如WETH就是这个代币的地址,也就是合约地址。
  • 合约ABI的json对象:合约中提供了很多属性、方法,这个ABI类似一个 TypeScript 中的接口文件,查看这个abi,就能看到这个合约支持那些属性、方法,方法要传入的参数和类型,已经返回的参数和类型。
js 复制代码
import web3 from '@/web3'
// 引入连接合约需要的地址和ABI
import { WETH_ABI, WETH_address } from '@/config'

// 连接合约,实例化WETH的合约对象
const WETH_Contract = new web3.eth.Contract(WETH_ABI, WETH_address)

调用合约方法简单示意

合约的方法都在 methods 中。

调用非上链方法

非上链方法基本都是只读方法,不需要上链、记账,调用起来比较简单,调用后直接再调用 .call() 即可。

比如 balanceOf 就是查询某用户拥有的代币余额的方法。

js 复制代码
const balanceWei = await WETH_contract.methods.balanceOf(account).call()

其他非上链方法同理,调用时传入响应的参数是,最后再调用 call() 即可。

调用上链方法

链上方法需要调用 send() 方法发送到链上。

需要给 send 传入参数,告知这笔交易的发起方、发送目标等,手续费也可以在这里配置,具体参数可见:methods.myMethod.send

因为上链方法比较复杂,且方式很多,下面依次举例介绍。

使用合约发出交易

下面就转账功能,进行具体的代码示例,调用合约的其他上链方法也都类似。

想要把1个WETH这种代币转给A账户地址,需要:

  1. 先连接节点服务器

  2. 再连接WETH的智能合约

  3. 再调用这个合约的转账方法,创建一笔把1个WETH转给A地址的交易申请。

  4. 把这个交易申请发送到链上,配置参数:

    • from:发起者的账户地址,也就是要扣哪个地址的钱
    • to:这个交易申请发送到的合约地址,也就是由哪个合约去执行
    • data:上面那笔交易申请编译生成的code,包含交易信息。
    • ...其他的类似 gasPrice、gasLimit等参数。
  5. 钱包会弹出签名确认框,用户点击确认后,会扣除对应的1WETH,和作为手续费的一定量的ETH。

注意,上面值转账某种代币,如果想转账此链的主币,比如ETH,则不需要使用合约,请见下一条「账户间转账ETH」教程。

js 复制代码
// 第1步已完成,引入已连接好小狐狸的web3
import web3 from '@/web3'
// 引入连接合约需要的地址和ABI
import { WETH_address, WETH_ABI } from '@/config'
import { format2Amount } from '@/utils'

async function transfer(myAccount, toAccount, value) {
  // 第2步,连接合约,得到WETH的合约对象,上面ABI定义的属性、方法,可以通过这个对象访问
  const WETH_contract = new web3.eth.Contract(WETH_ABI, WETH_address)

  // 第3步,创建一笔把1个WETH转给A地址的交易申请
  // 获取精度
  const decimals = await WETH_contract.methods.decimals().call() - 0
  // 得到以wei为单位的数量
  const amount = format2Amount(value, decimals)
  // 生成交易函数对象
  const transaction = WETH_contract.methods.transfer(toAccount, amount)

  // 第4步,发出交易到链上,接着第5步,小狐狸自动弹出签名确认框
  // 下面 send 第二个参数,也就是获取交易hash的方法,是非必传的
  // 有的公司业务要求及时保存每笔交易的交易hash,可以用上
  // 先组装发送到链上的配置
  const config = {
    from: myAccount, // 我的账户地址,这个交易是我发起的
    to: WETH_address, // 代币合约地址,因为这个交易需要这个合约进行,所以要发送给这个合约
  }
  const result = await transaction.send(config, (error, hash) => {
    if (error) {
      message.warn('出错了!')
    } else {
      // 得到了这个交易hash,可以轮询调用web3方法,去链上查询这个交易hash的结果
      // 也可以等待上面的 await,结果一样的
      console.log(hash)
    }
  })
}

使用web3.js发出交易

上面的第4步,发出交易,可以使用web3.js的 sendTransaction 方法替换,只不过需要把 transaction 这个交易单生成code,入参也有些变化。

sendTransaction 和合约方法无关,**sendTransaction **只是把某些请求发送到链上,基本所有上链操作都可以使用它。

具体可见教程:web3.eth.sendTransaction

以下代码,删除了前3步多余的注释:

js 复制代码
import web3 from '@/web3'
import { WETH_address, WETH_ABI } from '@/config'
import { format2Amount } from '@/utils'

function transfer(myAccount, toAccount, value) {
  const WETH_contract = new web3.eth.Contract(WETH_ABI, WETH_address)
  const decimals = await WETH_contract.methods.decimals().call() - 0
  const amount = format2Amount(value, decimals)
  const transaction = WETH_contract.methods.transfer(toAccount, amount)

  // 第4步,发出交易到链上,使用 web3.eth.sendTransaction 方法
  const config = {
    from: myAccount,
    to: WETH_address,
    data: transaction.encodeABI(), // 这里需要把上面的交易的code放在这里
  }
  const result = await web3.eth.sendTransaction(config, (error, hash) => {
    if (error) {
      message.warn('出错了!')
    } else {
      console.log(hash)
    }
  })
}

使用钱包的 request 发出交易

钱包基本都会提供直接调用 JSON-RPC 的方法,小狐狸插件的话,就是使用小狐狸的 window.ethereum.request 方法,去调用 JSON-RPCeth_sendTransaction 方法。

小狐狸钱包的方法文档:window.ethereum.request(args)

JSON-RPC 的 eth_sendTransaction 文档:eth_sendTransaction

js 复制代码
import web3 from '@/web3'
import { WETH_address, WETH_ABI } from '@/config'
import { format2Amount } from '@/utils'

function transfer(myAccount, toAccount, value) {
  const WETH_contract = new web3.eth.Contract(WETH_ABI, WETH_address)
  const decimals = await WETH_contract.methods.decimals().call() - 0
  const amount = format2Amount(value, decimals)
  const transaction = WETH_contract.methods.transfer(toAccount, amount)

  // 第4步,发出交易到链上,使用钱包插件小狐狸的 window.ethereum.request 方法
  const config = {
    from: myAccount,
    to: WETH_address,
    data: transaction.encodeABI(),
  }
  // 注意这里,等用户点击同意后,这里会立即拿到交易hash,需要自己使用 web3 方法轮询查询交易结果
  const hash = await window.ethereum.request({
    method: 'eth_sendTransaction',
    params: [config],
  })
}

无需钱包-使用私钥签名再发送交易

发送交易很容易,合约、web3.js和钱包插件都有对应的方法,最关键的一步是签名,需要使用私钥签名的那一步。

之所以要连接钱包的一大原因,就是需要使用钱包让用户签名,方便用户操作。

但一些运行在服务端的服务,私钥就掌握在我们自己手里,这是就可以只用用私钥签名,无需钱包了,当然在前端页面里面也行,只要有私钥。

web3.js提供了 web3.eth.accounts.signTransaction 方法,允许用户传入交易对象和账户私钥来对交易进行签名。

但这个方法传入的交易对象中必填 gas 字段,我们可以自己配置,也可以调用 web3.eth.estimateGas 来进行一个 模拟调用,发起一个假的交易申请,方法会返回完成这笔交易需要花费的 gas,直接把这个 gas 添加到交易对象中,再去进行签名即可。

此外,因为要发送的交易已经签名过了,所以需要使用 web3.eth.sendSignedTransaction 方法来发送交易。

js 复制代码
import web3 from '@/web3'
import { WETH_address, WETH_ABI, myPrivateKey } from '@/config'
import { format2Amount } from '@/utils'

function transfer(myAccount, toAccount, value) {
  const WETH_contract = new web3.eth.Contract(WETH_ABI, WETH_address)
  const decimals = await WETH_contract.methods.decimals().call() - 0
  const amount = format2Amount(value, decimals)
  const transaction = WETH_contract.methods.transfer(toAccount, amount)

  // 第4步,发出交易到链上
  const config = {
    from: myAccount,
    to: WETH_address,
    data: transaction.encodeABI(),
  }
  // 调用模拟调用方法,得到 gas 值
  const newGas = await web3.eth.estimateGas({...config})
  // 赋值 gas
  config.gas = newGas
  // 调用签名方法进行签名
  const signResult = await web3.eth.accounts.signTransaction(
    config,
    myPrivateKey, // 账户的私钥
  )

  // 发送交易
  const result = await web3.eth.sendSignedTransaction(signResult.rawTransaction, (error, hash) => {
    if (error) {
      message.warn('出错了!')
    } else {
      console.log(hash)
    }
  })
}

账户间转账ETH

上面使用的是合约转账,转的都是代币,需要连接代币的智能合约,让智能合约去操作。

但如果想转账此网络的官方主币,比如ETH,就不用那么麻烦了,流程如下:

  1. 先连接节点服务器。
  2. 这个交易申请发送到链上,配置参数:
    • from:发起者的账户地址,也就是要扣哪个地址的钱
    • to:这个交易申请发送到的合约地址,也就是由哪个合约去执行
    • value:打算转账的ETH量,单位需要转为 wei。
    • ...其他的类似 gasPrice、gasLimit等参数。
  3. 钱包会弹出签名确认框,用户点击确认后,会扣除对应的ETH + 要花费的作为手续费的少量ETH。
js 复制代码
// 第1步已完成,引入已连接好小狐狸的web3
import web3 from '@/web3'
import { format2Amount } from '@/utils'

function transfer(myAccount, toAccount, value) {
  const amount = format2Amount(value, 18) // ETH 的精度固定是18,这里可是写死

  // 第2步,发出交易到链上,使用 web3.eth.sendTransaction 方法
  const config = {
    from: myAccount,
    to: toAccount,
    value: amount,
  }
  const result = await web3.eth.sendTransaction(config, (error, hash) => {
    if (error) {
      message.warn('出错了!')
    } else {
      console.log(hash)
    }
  })
}

和调用合约的上链方法类似,上面的第2步也可以换用其他的多种方式。

根据交易hash查询交易结果

交易hash在发起交易时,通过传入的第二个参数回调函数来获取,也可以等待交易结果,在交易结果中的 transactionHash 就是此次交易的交易hash。

这个方法会得到当前这个hash对应的交易的状态,如果交易刚刚发生,可能查到的是null。

所以我们需要自己写轮询,半秒或一秒调一次,直到得到一个对象,读取对象中的 status 来判断交易是否成功。

js 复制代码
import web3 from '@/web3'

const result = await web3.eth.getTransactionReceipt(hash)
if (!result) console.log('交易尚未完成')
else if (result.status) console.log('交易完成')
else console.log('交易失败')