Skip to content

发送和执行XCM消息

概览

XCM消息由一系列的指令组成,由跨共识虚拟机(XCVM)执行。这些指令的组合会执行预定的操作,例如跨链Token转移。您可以通过组合各种XCM指令创建自定义XCM消息。

X-TokensXCM Transactor等Pallet提供带有一组预定义的XCM指令的函数,用于发送XC-20s或通过XCM在其他链上远程执行。然而,要更好地了解组合不同XCM指令的结果,您可以在Moonbeam上本地构建和执行自定义XCM消息。你也可以发送自定义XCM消息至其他链(这将以DescendOrigin指令开始)。但是,要成功执行XCM消息,目标链需要理解这些指令。

要执行或发送自定义XCM消息,你可以直接使用Polkadot XCM Pallet或者尝试通过带有XCM Utilities预编译的以太坊API。在本教程中,您将学习如何使用这两种方式在Moonbase Alpha上本地执行和发送自定义的XCM消息。

本教程假设您已熟悉XCM基本概念,例如基本的XCM专业术语XCM指令。您可以访问XCM概览文档获取更多信息。

Polkadot XCM Pallet接口

Extrinsics

Polkadot XCM Pallet包含以下相关extrinsics(函数):

  • execute(message, maxWeight) - 仅支持Moonbase Alpha - 给定SCALE编码的XCM版本化的XCM消息和要消耗的最大权重,执行自定义XCM消息
  • send(dest, message) - 仅支持Moonbase Alpha - 给定要发送消息的目标链的multilocation和要发送的SCALE编码的XCM版本化的XCM消息,发送自定义消息。要成功执行XCM消息,目标链需要理解消息中的指令

存储函数

Polkadot XCM Pallet包含以下相关只读存储函数:

  • assetTraps(Option) - 给定MultiAssets对的Blake2-256哈希,返回中断资产的现有次数。如果哈希出现omitted的错误,则返回所有中断资产
  • palletVersion() - 提供正在使用的Polkadot XCM Pallet的版本

查看先决条件

开始操作本教程之前,请先准备以下内容:

  • 您的账户必须拥有一些DEV Token 您可以每24小时一次从Moonbase Alpha水龙头上获取DEV代币以在Moonbase Alpha上进行测试

本地执行XCM消息

这一部分涵盖了通过两种不同的方法来构建要在本地(即在Moonbeam中)执行的自定义XCM消息:Polkadot XCM Pallet的execute函数和XCM Utilities预编译xcmExecute函数。此功能为您提供了试验不同的XCM指令并直接查看这些试验结果的平台。这也有助于确定与Moonbeam上给定XCM消息相关联的费用

在以下示例中,您将在Moonbase Alpha上从一个账户转移DEV Token至另一个账户。为此,您需要构建一个XCM消息以包含以下XCM指令,这些指令将在本地执行(在本示例中为Moonbase Alpha):

  • WithdrawAsset - 移除资产并将其放入暂存处
  • DepositAsset - 将资产从暂存处取出并存入等值资产至接收方账户中

注意事项

通常情况下,当您发送XCM消息跨链至目标链时,需要用到BuyExecution指令用于支付远程执行。但是,对于本地执行,此指令非必要,因为您已通过extrinsic调用支付费用。

使用Polkadot.js API执行XCM消息

在本示例中,您将使用Polkadot.js API在Moonbase Alpha上本地执行自定义XCM消息,以直接与Polkadot XCM Pallet交互。

Polkadot XCM Pallet的execute函数接受两个参数:messagemaxWeight。您可以执行以下步骤组装这些参数:

  1. 构建WithdrawAsset指令,其将要求您定义:

    • Moonbase Alpha上DEV token的multilocation
    • 要转移的DEV token数量
    const instr1 = {
      WithdrawAsset: [
        {
          id: { Concrete: { parents: 0, interior: { X1: { PalletInstance: 3 } } } },
          fun: { Fungible: 100000000000000000n }, // 0.1 DEV
        },
      ],
    };
    
  2. 构建DepositAsset指令,其将要求您定义:

    • DEV token的多资产标识符。您可以使用允许通配符匹配的WildMultiAsset format来识别资产
    • Moonbase Alpha上接收账户的multilocation
    const instr2 = {
      DepositAsset: {
        assets: { Wild: { AllCounted: 1 } },
        beneficiary: {
          parents: 0,
          interior: {
            X1: {
              AccountKey20: {
                key: moonbeamAccount,
              },
            },
          },
        },
      },
    };
    
  3. 将XCM指令合并至版本化的XCM消息中:

    const message = { V3: [instr1, instr2] };
    
  4. 指定maxWeight,其中包括您需要定义的refTimeproofSize

    • refTime是可用于执行的计算时间量。在本示例中,您可以设置为400000000n,这是因为 WithdrawAssetDepositAssetrefTime 分别为200000000
    • proofSize是可使用的存储量(以字节为单位)。在本示例中,您可以设置为14484n,这是因为WithdrawAssetDepositAssetproofSize分别为7242
    const maxWeight = { refTime: 400000000n, proofSize: 14484n } ;
    

现在,您已经有了每个参数的值,您可以为交易编写脚本了。为此,您需要执行以下步骤:

  1. 提供调用的输入数据,这包含:

    • 用于创建提供商的Moonbase Alpha端点URL
    • execute函数的每个参数的值
  2. 创建一个用于发送交易的Keyring实例

  3. 创建Polkadot.js API提供商
  4. 使用messagemaxWeight值制作polkadotXcm.execute extrinsic
  5. 使用signAndSend extrinsic和在第二个步骤创建的Keyring实例发送交易

请记住

本教程的操作仅用于演示目的,请勿将您的私钥存储至JavaScript文档中。


注意事项

您可以使用以下编码的调用数据在Polkadot.js Apps上查看上述脚本的示例,该脚本将1个DEV发送给Moonbeam上Bob的账户:0x1c03030800040000010403001300008a5d784563010d010204000103003cd0a705a2dc65e5b1e1205896baa2be8a07c6e002105e5f51e2

交易处理后,0.1 DEV Token和相关联的XCM费用从Alice的账户提取,Bob将在其账户收到0.1 DEV Token。polkadotXcm.Attempted事件将与结果一同发出。

使用XCM Utilities预编译执行XCM交易

在这一部分,您将使用XCM Utilities预编译xcmExecute函数(该函数仅支持Moonbase Alpha)以本地执行XCM消息。XCM Utilities预编译位于以下地址:

0x000000000000000000000000000000000000080C

在底层调用中,XCM Utilities预编译的xcmExecute函数调用Polkadot XCM Pallet的execute函数,即用Rust编码的Substrate pallet。使用XCM Utilities预编译调用xcmExecute的好处是您可以通过以太坊API完成此操作并使用Ethers.js等以太坊库。

xcmExecute函数接受两个参数:要执行SCALE编码的版本化XCM消息和要消耗的最大权重。

首先,您将了解如何生成编码的调用数据,然后您将了解如何使用编码的调用数据与 XCM Utilities预编译交互。

生成XCM消息的编码调用数据

要获取XCM消息的编码调用数据,您可以创建一个类似于在使用Polkadot.js API执行XCM消息部分创建的脚本。您将构建消息来获取编码的调用数据,而不是构建消息并发送交易。为此,您需要执行以下步骤:

  1. 提供调用的输入数据,这包含:

  2. 创建Polkadot.js API提供商

  3. 使用messagemaxWeight值制作polkadotXcm.execute extrinsic
  4. 使用交易获取编码的调用数据

整个脚本如下所示:


执行XCM消息

现在,您已拥有SCALE编码的XCM消息,您可以使用以下代码片段通过您选择的以太坊库以编程方式调用XCM Utilities预编译的xcmExecute函数。以下为基本的执行步骤:

  1. 创建提供商和签署者
  2. 创建用于交互的XCM Utilities Precompile的实例
  3. 定义xcmExecute函数所需的参数,这些参数将是XCM消息的编码调用数据以及用于执行消息的最大权重。您可以将maxWeight设置为400000000n,它对应于refTimeproofSize将自动设置为默认值,即64KB
  4. 执行XCM消息

请记住

以下代码仅为demo,在实际操作中请勿将您的私钥存储至JavaScript或Python文件中。

import { ethers } from 'ethers'; // Import Ethers library
import abi from './xcmUtilsABI.js'; // Import the XCM Utilities Precompile ABI

const privateKey = 'INSERT_YOUR_PRIVATE_KEY';
const xcmUtilsAddress = '0x000000000000000000000000000000000000080C';

/* Create Ethers provider and signer */
const provider = new ethers.JsonRpcProvider(
  'https://rpc.api.moonbase.moonbeam.network'
);
const signer = new ethers.Wallet(privateKey, provider);

/* Create contract instance of the XCM Utilities Precompile */
const xcmUtils = new ethers.Contract(
  xcmUtilsAddress,
  abi,
  signer
);

const executeXcmMessageLocally = async () => {
  /* Define parameters required for the xcmExecute function */
  const encodedCalldata = 'INSERT_ENCODED_CALLDATA';
  const maxWeight = '400000000';

  /* Execute the custom XCM message */
  const tx = await xcmUtils.xcmExecute(encodedCalldata, maxWeight);
  await tx.wait();
  console.log(`Transaction receipt: ${tx.hash}`);
};

executeXcmMessageLocally();
import { Web3 } from 'web3'; // Import Web3 library
import abi from './xcmUtilsABI.js'; // Import the XCM Utilities Precompile ABI

const privateKey = 'INSERT_PRIVATE_KEY';
const accountFrom = web3.eth.accounts.privateKeyToAccount(privateKey).address;
const xcmUtilsAddress = '0x000000000000000000000000000000000000080C';

/* Create Web3 provider */
const web3 = new Web3('https://rpc.api.moonbase.moonbeam.network'); // Change to network of choice

/* Create contract instance of the XCM Utilities Precompile */
const xcmUtils = new web3.eth.Contract(
  abi,
  xcmUtilsAddress,
  { from: accountFrom } // 'from' is necessary for gas estimation
);

const executeXcmMessageLocally = async () => {
  /* Define parameters required for the xcmExecute function */
  const encodedCalldata = 'INSERT_ENCODED_CALLDATA';
  const maxWeight = '400000000';

  /* Send the custom XCM message */
  // Craft the extrinsic
  const tx = await xcmUtils.methods.xcmExecute(encodedCalldata, maxWeight);
  // Sign transaction
  const signedTx = await web3.eth.accounts.signTransaction(
    {
      to: xcmUtilsAddress,
      data: tx.encodeABI(),
      gas: await tx.estimateGas(),
      gasPrice: await web3.eth.getGasPrice(),
      nonce: await web3.eth.getTransactionCount(accountFrom),
    },
    privateKey
  );
  // Send the signed transaction
  const sendTx = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
  console.log(`Transaction receipt: ${sendTx.transactionHash}`);
};

executeXcmMessageLocally();
from web3 import Web3

abi = "INSERT_XCM_UTILS_ABI"  # Paste or import the XCM Utils ABI
# This is for demo purposes, never store your private key in plain text
private_key = "INSERT_PRIVATE_KEY"
# The wallet address that corresponds to your private key
address = "INSERT_ADDRESS"
xcm_utils_address = "0x000000000000000000000000000000000000080C"

## Create Web3 provider ##
web3 = Web3(Web3.HTTPProvider("https://rpc.api.moonbase.moonbeam.network"))

## Create contract instance of the XCM Utilities Precompile ##
xcm_utils = web3.eth.contract(
    # XCM Utilities Precompile address
    address=xcm_utils_address,
    abi=abi,
)


def execute_xcm_message_locally():
    ## Define parameters required for the xcmExecute function ##
    encoded_calldata = "INSERT_ENCODED_CALLDATA"
    max_weight = 400000000

    ## Execute the custom XCM message ##
    # Craft the extrinsic
    tx = xcm_utils.functions.xcmExecute(encoded_calldata, max_weight).build_transaction(
        {
            "from": address,
            "nonce": web3.eth.get_transaction_count(address),
        }
    )
    # Sign transaction
    signedTx = web3.eth.account.sign_transaction(tx, private_key)
    # Send tx
    hash = web3.eth.send_raw_transaction(signedTx.rawTransaction)
    receipt = web3.eth.wait_for_transaction_receipt(hash)
    print(f"Transaction receipt: { receipt.transactionHash.hex() }")


execute_xcm_message_locally()

以上就是所需要执行的全部代码。您已在Moonbase Alpha上成功使用Polkadot XCM Pallet与XCM Utilities Precompile执行了一条自定义XCM消息!

跨链发送XCM消息

这一部分涵盖了通过两种不同的方法来跨链发送自定义XCM消息(即从Moonbeam到目标链,如中继链):Polkadot XCM Pallet的send函数和XCM Utilities预编译xcmSend函数。

要成功执行XCM消息,目标链需要理解消息中的指令。相反,您将在目标链上看到Barrier过滤器。为保证安全,XCM消息前会加上DecendOrigin指令以防止XCM代表源链的主权账户执行操作。如上所述,此部分的示例仅用于演示目的

在以下示例中,您将构建一个包含以下XCM指令的XCM消息,这些指令将在Alphanet中继链中执行:

  • WithdrawAsset - 移除资产并将其放入暂存处
  • BuyExecution - 从暂存处获取资产以支付执行费用。支付的费用由目标链决定
  • DepositAsset - 将资产从暂存处取出并存入等值资产至接收方账户中

这些指令的目的是将中继链的原生资产(即Alphanet中继链的UNIT)从Moonbase Alpha转移到中继链上的一个账户。此示例仅用于演示目的,以演示如何跨链发送自定义XCM消息。 请记住,目标链需要理解消息中的指令才可执行。

使用Polkadot.js API发送XCM消息

在本示例中,您将使用Polkadot.js API在Moonbase Alpha上本地执行自定义XCM消息,以直接与Polkadot XCM Pallet交互。

Polkadot XCM Pallet的send函数接受两个参数:destmessage。您可以执行以下步骤开始组装这些参数:

  1. dest构建中继链Token UNIT的multilocation:

    const dest = { V3: { parents: 1, interior: null } };
    
  2. 构建WithdrawAsset指令,这将要求您定义:

    • 中继链上UNIT token的multilocation
    • 要提现的UNIT token数量
    const instr1 = {
      WithdrawAsset: [
        {
          id: { Concrete: { parents: 1, interior: null } },
          fun: { Fungible: 1000000000000n }, // 1 UNIT
        },
      ],
    };
    
  3. 构建BuyExecution指令,这将要求您定义:

    • 中继链上UNIT token的multilocation
    • 用于执行的UNIT token数量
    • 权重限制
    const instr2 = {
      BuyExecution: [
        {
          id: { Concrete: { parents: 1, interior: null } },
          fun: { Fungible: 1000000000000n }, // 1 UNIT
        },
        { Unlimited: null }
      ],
    };
    
  4. 构建DepositAsset指令,这将要求您定义:

    • UNIT token的多资产标识符。您可以使用允许通配符匹配的WildMultiAsset format来识别资产
    • 中继链上接收账户的multilocation
    const instr3 = {
      DepositAsset: {
        assets: { Wild: 'All' },
        beneficiary: {
          parents: 1,
          interior: {
            X1: {
              AccountId32: {
                id: relayAccount,
              },
            },
          },
        },
      },
    };
    
  5. 将XCM指令合并至版本化的XCM消息中:

    const message = { V3: [instr1, instr2, instr3] };
    

现在,您已经有了每个参数的值,您可以为交易编写脚本了。为此,您需要执行以下步骤:

  1. 提供调用的输入数据,这包含:

    • 用于创建提供商的Moonbase Alpha端点URL
    • send函数的每个参数的值
  2. 创建用于发送交易的Keyring实例

  3. 创建Polkadot.js API提供商
  4. 使用destmessage值制作polkadotXcm.execute extrinsic
  5. 使用signAndSend extrinsic和在第二个步骤创建的Keyring实例发送交易

请记住

本教程的操作仅用于演示目的,请勿将您的私钥存储至JavaScript文档中。


注意事项

您可以使用以下编码的调用数据在Polkadot.js Apps上查看上述脚本的示例,该脚本将1个UNIT发送给中继链上Bob的账户:0x1c00030100030c000400010000070010a5d4e81300010000070010a5d4e8000d0100010101000c36e9ba26fa63c60ec728fe75fe57b86a450d94e7fee7f9f9eddd0d3f400d67

交易处理后,polkadotXcm.sent事件将与发送的XCM消息详情一同发出。

使用XCM Utilities预编译发送XCM交易

在这一部分,您将使用XCM Utilities预编译xcmSend函数(该函数仅支持Moonbase Alpha)以跨链发送XCM消息。XCM Utilities预编译位于以下地址:

0x000000000000000000000000000000000000080C

在底层调用中,XCM Utilities预编译的xcmSend函数调用Polkadot XCM Pallet的send函数,即用Rust编码的Substrate pallet。使用XCM Utilities预编译调用send的好处是您可以通过以太坊API完成此操作并使用Ethers.js等以太坊库。要成功执行XCM消息,目标链需要了解消息中的指令。

xcmSend函数接受两个参数:目标链的multilocation和要发送的SCALE编码的版本化XCM消息。

首先,您将了解如何生成用于XCM消息的编码调用数据,然后您将了解如何使用编码的调用数据与 XCM Utilities预编译交互。

生成XCM消息的编码调用数据

要获取XCM消息的编码调用数据,您可以创建一个类似于在使用Polkadot.js API执行XCM消息部分创建的脚本。您将构建消息来获取编码的调用数据,而不是构建消息并发送交易。为此,您需要执行以下步骤:

  1. 提供调用的输入数据,这包含:

  2. 创建Polkadot.js API提供商

  3. 使用messagemaxWeight值制作polkadotXcm.execute extrinsic
  4. 使用交易获取编码的调用数据

完整脚本如下所示:


发送XCM消息

在发送XCM消息前,您需要构建目标的multilocation。在本示例中,您将以Moonbase Alpha作为源链的中继链:

const dest = [
  1, // Parents: 1 
  [] // Interior: Here
];

现在,你已拥有SCALE编码的XCM消息和目标multilocation,您可以使用以下代码片段选择以太坊库以编程方式调用XCM Utilities预编译的xcmSend函数。通常,您需要执行以下步骤:

  1. 创建提供商和签署者
  2. 创建用于交互的XCM Utilities Precompile的实例
  3. 定义xcmSend函数所需的参数,该参数将是XCM消息的目标链和编码的调用数据
  4. 发送XCM消息

请记住

以下代码片段仅用于演示目的,请勿将您的私钥存储至JavaScript或Python文件中。

import { ethers } from 'ethers'; // Import Ethers library
import abi from './xcmUtilsABI.js'; // Import the XCM Utilities Precompile ABI

const privateKey = 'INSERT_PRIVATE_KEY';
const xcmUtilsAddress = '0x000000000000000000000000000000000000080C';

/* Create Ethers provider and signer */
const provider = new ethers.JsonRpcProvider(
  'https://rpc.api.moonbase.moonbeam.network'
);
const signer = new ethers.Wallet(privateKey, provider);

/* Create contract instance of the XCM Utilities Precompile */
const xcmUtils = new ethers.Contract(
  xcmUtilsAddress,
  abi,
  signer
);

const sendXcm = async () => {
  /* Define parameters required for the xcmSend function */
  const encodedCalldata = 'INSERT_ENCODED_CALLDATA';
  const dest = [
    1, // Parents: 1 
    [] // Interior: Here
  ];

  /* Send the custom XCM message */
  const tx = await xcmUtils.xcmSend(dest, encodedCalldata);
  await tx.wait();
  console.log(`Transaction receipt: ${tx.hash}`);
};

sendXcm();
import { Web3 } from 'web3'; // Import Web3 library
import abi from './xcmUtilsABI.js'; // Import the XCM Utilities Precompile ABI

const privateKey = 'INSERT_PRIVATE_KEY';
const accountFrom = web3.eth.accounts.privateKeyToAccount(privateKey).address;
const xcmUtilsAddress = '0x000000000000000000000000000000000000080C';

/* Create Web3 provider */
const web3 = new Web3('https://rpc.api.moonbase.moonbeam.network'); // Change to network of choice

/* Create contract instance of the XCM Utilities Precompile */
const xcmUtils = new web3.eth.Contract(
  abi,
  xcmUtilsAddress,
  { from: accountFrom } // 'from' is necessary for gas estimation
);

const sendXcm = async () => {
  /* Define parameters required for the xcmSend function */
  const encodedCalldata = 'INSERT_ENCODED_CALLDATA';
  const dest = [
    1, // Parents: 1
    [], // Interior: Here
  ];

  /* Send the custom XCM message */
  // Craft the extrinsic
  const tx = await xcmUtils.methods.xcmSend(dest, encodedCalldata);
  // Sign transaction
  const signedTx = await web3.eth.accounts.signTransaction(
    {
      to: xcmUtilsAddress,
      data: tx.encodeABI(),
      gas: await tx.estimateGas(),
      gasPrice: await web3.eth.getGasPrice(),
      nonce: await web3.eth.getTransactionCount(accountFrom),
    },
    privateKey
  );
  // Send the signed transaction
  const sendTx = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
  console.log(`Transaction receipt: ${sendTx.transactionHash}`);
};

sendXcm();
from web3 import Web3

abi = "INSERT_XCM_UTILS_ABI"  # Paste or import the XCM Utils ABI
# This is for demo purposes, never store your private key in plain text
private_key = "INSERT_PRIVATE_KEY"
# The wallet address that corresponds to your private key
address = "INSERT_ADDRESS"
xcm_utils_address = "0x000000000000000000000000000000000000080C"

## Create Web3 provider ##
web3 = Web3(Web3.HTTPProvider("https://rpc.api.moonbase.moonbeam.network"))

## Create contract instance of the XCM Utilities Precompile ##
xcm_utils = web3.eth.contract(
    # XCM Utilities Precompile address
    address=xcm_utils_address,
    abi=abi,
)


def send_xcm():
    ## Define parameters required for the xcmSend function ##
    encoded_calldata = "INSERT_ENCODED_CALLDATA"
    xcm_dest = [1, []]  # Parents: 1  # Interior: Here

    ## Send the custom XCM message ##
    # Craft the extrinsic
    tx = xcm_utils.functions.xcmSend(xcm_dest, encoded_calldata).build_transaction(
        {
            "from": address,
            "nonce": web3.eth.get_transaction_count(address),
        }
    )
    # Sign transaction
    signed_tx = web3.eth.account.sign_transaction(tx, private_key)
    # Send tx
    hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
    receipt = web3.eth.wait_for_transaction_receipt(hash)
    print(f"Transaction receipt: { receipt.transactionHash.hex() }")


send_xcm()

这样就可以了!您已成功使用Polkadot XCM Pallet和XCM Utilities预编译从Moonbase Alpha上发送消息至另一条链。

Last update: January 25, 2024
| Created: April 14, 2023