Skip to content

在Moonbeam上转账和订阅余额变化

Moonbeam v Ethereum - Transfers API Banner

概览

虽然Moonbeam致力于兼容以太坊Web3 API和EVM,但开发者仍需了解Moonbeam在原生代币(例如:GLMR和MOVR)余额转账方面与以太坊之间的主要差异。

Token持有者有两种方式来启动Moonbeam上的余额转账功能。一方面,用户可以通过MetaMask、MathWallet或其他任何使用以太坊JSON-RPC的工具等应用程序来使用以太坊API。另一方面,用户可以通过Polkadot.js App网站使用Substrate API或直接使用Substrate RPC。

开发者需要注意的是,Token持有者可以利用这两类API来转移原生代币。请注意,这页内容不适用于其他类资产的转账,例如Moonriver或Moonbeam EVM中基于ERC-20的资产。这些资产的转移只能通过以太坊API完成,因为需要与智能合约交互。

本教程将概述围绕这两类API进行余额转账的一些主要差异,以及首次使用Moonbeam时需要了解的事项。

以太坊转账

使用以太坊API的简单余额转账依赖于eth_sendRawTransaction JSON RPC。这可以直接从一个账户到另一个账户,或通过智能合约。

在以太坊上有不同的策略来监听转账或余额变化,本教程中并未涉及。但它们都集中在使用以太坊JSON RPC的不同策略上。

Moonbeam转账

如先前所述,Moonbeam使Token持有者能够通过以太坊和Substrate API执行原生代币转账。在Moonbeam上有多种情况可以触发Token转账。因此,为了监控所有的转账,您应该使用Polkadot.js SDK(Substrate API)。

在介绍不同情况之前,有两个与区块相关的不同要素。

  • Extrinsic —— 指源于系统本身之外的状态变化。最常见的Extrinsic形式是交易。它们是按执行顺序排列的
  • Events —— 指由Extrinsic产生的日志。每个Extrinsic可以有多个事件。它们按执行顺序排列。

不同的转账场景如下:

  • Substrate转账 —— 这将创建一个Extrinsic,balances.transferbalances.transferKeepAlive。这将触发一个balances.Transfer事件
  • Substrate功能 —— 一些原生Substrate功能可以创建Extrinsic,将Token发送至一个地址。例如,Treasury可以创建一个Extrinsic,如treasury.proposeSend,这将触发一个或多个balances.Transfer事件
  • Ethereum转账 —— 这将创建一个ethereum.transactExtrinsic,为一个空白输入值。这将触发一个balances.Transfer事件
  • 通过智能合约进行以太坊转账 —— 这将创建一个ethereum.transactExtrinsic,多个数据成为输入值。这将触发一个或多个balances.Transfer事件

上述所有场景都将能有效地进行原生代币转账。监控它们最简单的方法就是通过balances.Transfer事件。

使用Substrate API来监控所有余额转账

Polkadot.js API程序包为开发人员提供了一种使用Javascript与Substrate链相交互的方式。

以下代码片段使用Polkadot.js循环访问从提供者获取的每个事件。然后,检查是否与一个balances.Transfer事件对应。如果是,这将提取一个转账的fromto以及amount并显示在控制台上。请注意,amount是以最小的单位(Wei)来显示的。您可以在他们的官方文档网站找到关于Polkadot.js和Substrate JSON RPC的所有可用信息。

import { typesBundlePre900 } from "moonbeam-types-bundle";
import { ApiPromise, WsProvider } from "@polkadot/api";

// This script will listen to all GLMR transfers (Substrate & Ethereum) and extract the tx hash
// It can be adapted for Moonriver or Moonbase Alpha

const main = async () => {
  // Define the provider for Moonbeam
  const wsProvider = new WsProvider("wss://wss.api.moonbeam.network");
  // Create the provider using Moonbeam types
  const polkadotApi = await ApiPromise.create({
    provider: wsProvider,
    typesBundle: typesBundlePre900 as any,
  });

  // Subscribe to finalized blocks
  await polkadotApi.rpc.chain.subscribeFinalizedHeads(async (lastFinalizedHeader) => {
    const [{ block }, records] = await Promise.all([
      polkadotApi.rpc.chain.getBlock(lastFinalizedHeader.hash),
      polkadotApi.query.system.events.at(lastFinalizedHeader.hash),
    ]);

    block.extrinsics.forEach((extrinsic, index) => {
      const {
        method: { args, method, section },
      } = extrinsic;

      const isEthereum = section == "ethereum" && method == "transact";

      // Gets the transaction object
      const tx = (args[0] as any);

      // Convert to the correct Ethereum Transaction format
      const ethereumTx = isEthereum && 
        ((tx.isLegacy && tx.asLegacy) ||
        (tx.isEip1559 && tx.asEip1559) ||
        (tx.isEip2930 && tx.asEip2930));

      // Check if the transaction is a transfer
      const isEthereumTransfer = ethereumTx && ethereumTx.input.length === 0 && ethereumTx.action.isCall;

      // Retrieve all events for this extrinsic
      const events = records.filter(
        ({ phase }) => phase.isApplyExtrinsic && phase.asApplyExtrinsic.eq(index)
      );

      // This hash will only exist if the transaction was executed through Ethereum.
      let ethereumHash = "";

      if (isEthereum) {
        // Search for Ethereum execution
        events.forEach(({ event }) => {
          if (event.section == "ethereum" && event.method == "Executed") {
            ethereumHash = event.data[2].toString();
          }
        });
      }

      // Search if it is a transfer
      events.forEach(({ event }) => {
        if (event.section == "balances" && event.method == "Transfer") {
          const from = event.data[0].toString();
          const to = event.data[1].toString();
          const balance = (event.data[2] as any).toBigInt();

          const substrateHash = extrinsic.hash.toString();

          console.log(`Transfer from ${from} to ${to} of ${balance} (block #${lastFinalizedHeader.number})`);
          console.log(`  - Triggered by extrinsic: ${substrateHash}`);
          if (isEthereum) {
            console.log(`  - Ethereum (isTransfer: ${isEthereumTransfer}) hash: ${ethereumHash}`);
          }
        }
      });
    });
  });
};

main();

此外,您可以在此脚本中找到更多余额转账相关具体案例的代码片段。