在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.transfer
或balances.transferKeepAlive
。这将触发一个balances.Transfer
事件 - Substrate功能 — 一些原生Substrate功能可以创建Extrinsic,将Token发送至一个地址。例如,Treasury可以创建一个Extrinsic,如
treasury.proposeSend
,这将触发一个或多个balances.Transfer
事件 - Ethereum转账 — 这将创建一个
ethereum.transact
Extrinsic,为一个空白输入值。这将触发一个balances.Transfer
事件 - 通过智能合约进行以太坊转账 — 这将创建一个
ethereum.transact
Extrinsic,多个数据成为输入值。这将触发一个或多个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
事件对应。如果是,这将提取一个转账的from
、to
以及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转账的from
、to
、value
、tx 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 === 'transfer')
) {
// 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页面。
| Created: October 1, 2021