Skip to content

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

概览

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

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

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

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

以太坊转账

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

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

Moonbeam转账

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

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

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

不同的转账场景如下:

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

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

监控原生Token余额转账

以下代码示例将演示如何使用Polkadot.js API库Substrate API Sidecar监听通过Substrate或Ethereum API发送的两种类型的原生Token转账。下方代码片段仅用于演示目的,请将其进行调整并进一步测试后再用于生产环境。

使用Polkadot.js API

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

以下代码片段使用subscribeFinalizedHeads订阅最新确认的区块头,并循环访问之中的每个事件。然后,检查是否与一个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();

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

使用Substrate API Sidecar

开发者也可以使用Substrate API Sidecar检索Moonbeam区块并监控通过Substrate和Ethereum API发送的交易。Substrate API Sidecar是一种REST API服务,用于与使用Substrate框架构建的区块链交互。

以下代码片段使用Axios HTTP客户端查询Sidecar端点/blocks/head(https://paritytech.github.io/substrate-api-sidecar/dist/){target=_blank}以获取最新确认的区块头。然后,在EVM和Substrate API级别解码原生Token转账的fromtovaluetx hash、和transaction status 区块。

import axios from 'axios';

// This script will decode all native token transfers (Substrate & Ethereum) in a given Sidecar block, and extract the tx hash. It can be adapted for any Moonbeam network.

// Endpoint to retrieve the latest block
const endpoint = 'http://127.0.0.1:8080/blocks/head';

async function main() {
  try {
    // Retrieve the block from the Sidecar endpoint
    const response = await axios.get(endpoint);
    // Retrieve the block height of the current block
    console.log('Block Height: ' + response.data.number);

    // Iterate through all extrinsics in the block
    response.data.extrinsics.forEach((extrinsic) => {
      // Retrieve Ethereum Transfers
      if (
        extrinsic.method.pallet === 'ethereum' &&
        extrinsic.method.method === 'transact'
      ) {
        // Get the value for any of the three EIP transaction standards supported
        const value =
          (extrinsic.args.transaction.legacy &&
            extrinsic.args.transaction.legacy.value) ||
          (extrinsic.args.transaction.eip1559 &&
            extrinsic.args.transaction.eip1559.value) ||
          (extrinsic.args.transaction.eip2930 &&
            extrinsic.args.transaction.eip2930.value);

        // Iterate through the events to get transaction details
        extrinsic.events.forEach((event) => {
          if (
            event.method.pallet === 'ethereum' &&
            event.method.method === 'Executed'
          ) {
            console.log('From: ' + event.data[0]);
            console.log('To: ' + event.data[1]);
            console.log('Tx Hash: ' + event.data[2]);
            console.log('Value: ' + value);
            // Check the execution status
            if (event.data[3].succeed) {
              console.log('Status: Success');
            } else {
              console.log('Status: Failed');
            }
          }
        });
      }

      // Retrieve Substrate Transfers
      if (
        extrinsic.method.pallet === 'balances' &&
        (extrinsic.method.method === 'transferKeepAlive' ||
          extrinsic.method.method === 'transferAllowDeath')
      ) {
        // Iterate through the events to get transaction details
        extrinsic.events.forEach((event) => {
          if (
            event.method.pallet === 'balances' &&
            event.method.method === 'Transfer'
          ) {
            console.log('From: ' + event.data[0]);
            console.log('To: ' + event.data[1]);
            console.log('Tx Hash: ' + extrinsic.hash);
            console.log('Value: ' + event.data[2]);
            // Check the execution status
            if (extrinsic.success) {
              console.log('Status: Success');
            } else {
              console.log('Status: Failed');
            }
          }
        });
      }
    });
  } catch (err) {
    console.log(err);
  }
}

main();

关于安装和运行Sidecar服务实例,以及如何解码Moonbeam交易的Sidecar区块等更多信息,请参考Substrate API Sidecar页面

Last update: January 23, 2024
| Created: October 1, 2021