Skip to content

通过XCM在波卡中执行Uniswap V2兑换

作者:Alberto Viera

概览

在本教程中,我们将在中继链(即波卡相对于Moonbeam的关系)使用名为XCM的波卡原生互操作通用消息传递协议来演示一个Uniswap V2风格的兑换。为此,我们将使用特定的XCM指令组合,允许您通过XCM消息调用Moonbeam的EVM。因此,任何能够向Moonbeam发送XCM消息的区块链都可以利用Moonbeam的EVM以及构建在其之上的所有dApp。

本教程中的内容仅用于教育用途!

在此范例中,您将在Moonbase Alpha(Moonbeam TestNet)上操作,它拥有自己的中继链(类似于波卡)。中继链Token称为UNIT,而Moonbase Alpha的Token则称为DEV。在测试网中执行此操作不如在真正的生产环境中执行此操作有趣,但是开发人员必须了解发送不正确的XCM消息会导致资金损失。因此,在真正的生产环境中执行前,于测试网中测试XCM功能是必要的。

在此教程中,我们将执行Uniswap V2兑换的账户命名为Alice。此教程包含许多跳转的内容部分,因此让我们先将其整理总结成列表以及流程图:

  1. Alice在中继链上拥有一个账户,她希望在Moonbeam-Swap(Moonbase Alpha上的Uniswap V2克隆演示版本)将DEV Token兑换成MARS Token(Moonbase Alpha上的ERC-20 Token)。Alice此时需要自其中继链账户传送一个XCM消息至Moonbase Alpha
  2. XCM消息将由Moonbase Alpha接收并执行其指令。这些指令表明Alice打算在Moonbase Alpha中购买一些区块执行时间并执行对Moonbase的EVM的调用,具体来说是Uniswap V2(Moonbeam-Swap)路由合约。EVM调用是通过XCM消息经由在Moonbase Alpha上Alice控制的一个特殊账户发送的。此帐户称为多地点衍生账户。即使这是一个无密钥帐户(私钥未知),公共地址可以以确定性的方式计算
  3. XCM执行将会导致兑换经由EVM执行,而Alice将会在其特别账户获得其MARS Token
  4. 经由XCM的远程EVM执行将会得到浏览器获取的一些EVM记录,有任何人皆能够查询验证的EVM交易和收据

Remote EVM Call Through XCM for Uniswap V2 Swap Diagram

如要执行上述列出的步骤,将需要满足一些先决条件,马上查看有哪些先决条件吧!

查看先决条件

所有在概览部分中列出的步骤皆需满足以下的先决条件:

  • 您需要在中继链上拥有UNIT来支付发送XCM时所需的交易费用。如果您有一个具有DEV Token的Moonbase Alpha帐户,您可以在Moonbeam Swap上用一些DEV交换xcUNIT。然后从Moonbase Alpha通过apps.moonbeam.network提现xcUNIT到您在Moonbase中继链上的账户
  • 您的多地点衍生账户必须持有DEV Token来为Uniswap V2兑换提供资金,以及支付XCM的执行费用(尽管这可以用UNIT Token以xcUNIT方式支付)。我们将在下一节中计算多地点衍生账户的地址

您可以每24小时一次从Moonbase Alpha水龙头上获取DEV代币以在Moonbase Alpha上进行测试。

计算您的多地点衍生账户

复制您在Moonbase中继链的现有或新创建的账户。您将需要它来计算相应的多地点衍生账户,这是一种特殊类型的无密钥账户(其私钥未知)。来自多地点衍生账户的交易只能通过来自中继链上相应账户的有效XCM指令启动。换句话说,您是唯一可以在您的多地点衍生账户上发起交易的人——如果您无法访问您的Moonbase中继链账户,您也将无法访问您的多地点衍生账户。

如要产生多地点衍生账户,首先请复制xcm-tools代码库,运行yarn指令以安装所有必要代码包并运行以下指令:

yarn calculate-multilocation-derivative-account \
--ws-provider wss://wss.api.moonbase.moonbeam.network \
--address INSERT_MOONBASE_RELAY_ACCOUNT \
--para-id INSERT_ORIGIN_PARACHAIN_ID_IF_APPLIES \
--parents INSERT_PARENTS_VALUE_IF_APPLIES

接着,让我们检查以上指令中输入的相关参数:

  • --ws-provider-w 标志对应我们用于获得此信息的端点
  • --address-a标志对应您的Moonbase中继链账户地址
  • --para-id-p标志对应原链(如有)的平行链ID。如果您从中继链传送XCM则无需提供此参数
  • -parents标签与目标链在源链上的父值相关。如果您正从中继链源头在平行链目标链生成multi-location衍生账户,此数值将会是1。如果不是,父值预设为0

以我们的例子来说,我们将会通过Alice账户经由XCM传送远程EVM调用,也就是5GKh9gMK5dn9SJp6qfMNcJiMMnY7LReYmgug2Fr5fKE64imn,因此指令和获得的结果将会如同下方图示。

Calculating the multilocation-derivative account

所有数值被整理成以下表格:

名称 数值
原链编码地址 5GKh9gMK5dn9SJp6qfMNcJiMMnY7LReYmgug2Fr5fKE64imn
原链解码地址 0xbc5f3c61709f218d983fc773a600958a07fb18047418df7eeb0501d0679e397a
多地点衍生账户(32字节) 0x61cd3e07fe7d7f6d4680e3e322986b7877f108ddb18ec02c2f17e82fe15f9016
多地点衍生账户(20字节) 0x61cd3e07fe7d7f6d4680e3e322986b7877f108dd

该脚本将返回32字节和20字节的地址。我们关心的是以太坊形式的账户——20字节的账户,即0x61cd3e07fe7d7f6d4680e3e322986b7877f108dd。请随时在Moonscan上查找您的多地点衍生帐户。接下来,您可以使用DEV Token为该帐户提供资金。

您可以每24小时一次从Moonbase Alpha水龙头上获取DEV代币以在Moonbase Alpha上进行测试。

获得Uniswap V2兑换调用数据

以下部分将会包含获得Uniswap V2兑换调用数据的步骤,因为我们将会需要填入此调用数据至我们通过XCM构建的远程EVM调用

此处的目标函数为来自Uniswap V2路由的函数其中之一,也就是swapExactETHForTokens。此函数将会把指定数量的协议原生Token(此范例中为DEV)兑换为其他ERC-20 Token。它需要以下参数输入:

  • 您希望从兑换获得的最小数量(考虑滑点)
  • 交易路径(如果没有直接相关的池子,该兑换将会通过多个交易对池子执行)
  • Token兑换的接收人地址
  • 此交易失效的期限(以Unix Time为单位)

获得调用数据的最简单方法为通过Moonbeam Uniswap V2 Demo页面。当您进入该页面,请跟随以下步骤:

  1. 设置兑换的from数值及Token以及兑换的to Token。以此例子来说,我们希望能够以0.01 DEV兑换MARS
  2. 点击Swap按钮。MetaMask将会弹出,请不要签署交易
  3. 在MetaMask中,点击hex标签,您将能看到编码的调用数据
  4. 点击Copy raw transaction data按钮,这将会复制编码的调用数据至剪贴板

Calldata for Uniswap V2 swap

注意事项

其他钱包也提供在签署交易前查看编码调用数据的类似功能。

当您获得编码调用数据,请在您的钱包中取消交易。我们获得的兑换调用数据编码将如以下所示(除函数选择器外的所有内容均以32字节或64个十六进制字符表示):

  1. 函数选择器,4个字节长(8个十六进制字符)代表您调用的函数
  2. 我们希望从兑换中获得的最小数量(考虑滑点),以此范例来说,10b3e6f66568aaee1.2035MARS Token
  3. 路径参数(动态类型)的数据部分的位置(指针)。十六进制的80是十进制的128,代表有关路径的信息显示在从头开始的128个字节之后(不包括函数选择器)。因此,关于路径的下一位信息出现在第6个元素中
  4. 兑换后接收Token的地址,在此范例中为调用中的msg.sender
  5. 兑换的期限限制
  6. 代表路径的地址数组的长度
  7. 首个参与兑换的Token,也就是进行了包装的DEV(Wrapped DEV)
  8. 第二个参与兑换的Token,MARS,所以其为最后一个参与Token
1. 0x7ff36ab5
2. 00000000000000000000000000000000000000000000000010b3e6f66568aaee -> Min Amount Out
3. 0000000000000000000000000000000000000000000000000000000000000080
4. 000000000000000000000000d720165d294224a7d16f22ffc6320eb31f3006e1 -> Receiving Address
5. 0000000000000000000000000000000000000000000000000000000063dbcda5 -> Deadline
6. 0000000000000000000000000000000000000000000000000000000000000002
7. 000000000000000000000000d909178cc99d318e4d46e7e66a972955859670e1
8. 0000000000000000000000001fc56b105c4f0a1a8038c2b429932b122f6b631f

在调用数据中,我们需要更改三处以确保我们的兑换会成功:

  • 最小出金额度(考虑滑点)。因为当你尝试这个时,池子中可能有不同的DEV/MARS余额
  • 我们多地点衍生账户的收取地址
  • 为我们的兑换提供更灵活的截止日期,这样您就不必立即提交

目前我们仅在测试,请勿在真实的生产环境中使用此代码!我们编码的调用数据应如下所示(为了可见性而保留换行符):

0x7ff36ab5
0000000000000000000000000000000000000000000000000de0b6b3a7640000 -> New Min Amount
0000000000000000000000000000000000000000000000000000000000000080
00000000000000000000000061cd3e07fe7d7f6d4680e3e322986b7877f108dd -> New Address
00000000000000000000000000000000000000000000000000000000A036B1B9 -> New Deadline
0000000000000000000000000000000000000000000000000000000000000002
000000000000000000000000d909178cc99d318e4d46e7e66a972955859670e1
0000000000000000000000001fc56b105c4f0a1a8038c2b429932b122f6b631f

以一行的方式表现如下:

0x7ff36ab50000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000061cd3e07fe7d7f6d4680e3e322986b7877f108dd00000000000000000000000000000000000000000000000000000000A036B1B90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000d909178cc99d318e4d46e7e66a972955859670e10000000000000000000000001fc56b105c4f0a1a8038c2b429932b122f6b631f

您同样可以使用Uniswap V2 SDK以编程的方式获得调用数据。

生成Moonbeam编码调用数据

现在我们有了Uniswap V2兑换编码调用数据,我们需要生成来自XCM消息的TransactXCM指令将执行的字节。请注意,这些字节表示将在远程链中执行的操作。在此例子中,我们希望XCM消息执行自我们获得编码的调用数据进入EVM并进行兑换。

要为交易参数获得SCALE(编码类型)编码数据,我们可以利用以下Polkadot.js API脚本(请注意此处需要@polkadot/apiethers)。

import { ApiPromise, WsProvider } from '@polkadot/api'; // Version 9.13.6
import { ethers } from 'ethers'; // Version 6.0.2

// 1. Input Data
const providerWsURL = 'wss://wss.api.moonbase.moonbeam.network';
const uniswapV2Router = '0x8a1932D6E26433F3037bd6c3A40C816222a6Ccd4';
const contractCall =
  '0x7ff36ab50000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000061cd3e07fe7d7f6d4680e3e322986b7877f108dd00000000000000000000000000000000000000000000000000000000A036B1B90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000d909178cc99d318e4d46e7e66a972955859670e10000000000000000000000001fc56b105c4f0a1a8038c2b429932b122f6b631f';

const generateCallData = async () => {
  // 2. Create Substrate API Provider
  const substrateProvider = new WsProvider(providerWsURL);
  const ethProvider = new ethers.WebSocketProvider(providerWsURL);
  const api = await ApiPromise.create({ provider: substrateProvider });

  // 3. Estimate Gas for EVM Call
  const gasLimit = await ethProvider.estimateGas({
    to: uniswapV2Router,
    data: contractCall,
    value: ethers.parseEther('0.01'),
  });
  console.log(`Gas required for call is ${gasLimit.toString()}`);

  // 4. Call Parameters
  const callParams = {
    V2: {
      gasLimit: gasLimit + 10000n, // Estimated plus some extra gas
      action: { Call: uniswapV2Router }, // Uniswap V2 router address
      value: ethers.parseEther('0.01'), // 0.01 DEV
      input: contractCall, // Swap encoded calldata
    },
  };

  // 5. Create the Extrinsic
  const tx = api.tx.ethereumXcm.transact(callParams);

  // 6. Get SCALE Encoded Calldata
  const encodedCall = tx.method.toHex();
  console.log(`Encoded Calldata: ${encodedCall}`);

  api.disconnect();
};

generateCallData();

注意事项

您也可以手动在Polkadot.js Apps构建函数以获得SCALE编码数据。

让我们检视上方代码段中的主要组成部分:

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

    • Moonbase Alpha终端URL以创建providers(提供者)
    • Uniswap V2路由地址,即调用交互的对象
    • 我们先前计算的Uniswap V2兑换编码数据
  2. 创建必要的providers。一个为Polkadot.js API provider,我们可以通过它直接调用Moonbeam pallets。另外一个为以太坊API provider,可以通过Ethers.js创建

  3. 这一步主要是一个最佳做法。在这里,我们估算将通过XCM执行的EVM调用的gas,因为稍后需要。您也可以硬编码gas limit值,但并不推荐
  4. 构建远程EVM调用。我们将Gas增加了10000个单位,以便在情况发生变化时提供处理空间。输入与用于gas估算的输入相同
  5. transact函数创建以太坊XCM pallet调用,提供我们先前构建的调用参数
  6. 获取特定交易参数的SCALE调用数据,我们稍后需要将其提供给Transact XCM指令。请注意,在这种特定情况下,因为我们只需要交易参数的调用数据,所以我们必须使用 tx.method.toHex()

当您设定好代码,您可以通过node执行,您将会获得Moonbase Alpha远程EVM调用数据:

Getting the Moonbase Alpha remote EVM XCM calldata for Uniswap V2 swap

此处范例的编码调用数据如下:

0x260001eeed020000000000000000000000000000000000000000000000000000000000008a1932d6e26433f3037bd6c3a40c816222a6ccd40000c16ff286230000000000000000000000000000000000000000000000000091037ff36ab50000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000061cd3e07fe7d7f6d4680e3e322986b7877f108dd00000000000000000000000000000000000000000000000000000000a036b1b90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000d909178cc99d318e4d46e7e66a972955859670e10000000000000000000000001fc56b105c4f0a1a8038c2b429932b122f6b631f00

就这样!您已经了解需要创建XCM消息本身的所有细节!这是一段漫长的旅程,但我们快结束了。

从中继链构建XCM消息

我们即将进入本教程的最后一部分!在本部分,我们将使用Polkadot.js API制作XCM消息。我们还将剖析消息的每条指令,以了解每一步发生的情况。

我们将构建的XCM消息包含以下指令:

  • WithdrawAsset — 将资金从在目标链调用XCM的账户中转移至holding,此处资金将能够在之后的操作中使用
  • BuyExecution — 购买指定数量的区块执行时间
  • Transact — 使用部分前一条指令购买的区块执行时间执行一些任意字节
  • DepositAsset — 将资产从holding中取出并存入至指定账户

要构建一个通过XCM启用远程EVM调用的XCM消息,以及获得其SCALE编码调用数据,您可以使用以下代码段:

import { ApiPromise, WsProvider } from '@polkadot/api'; // Version 9.13.6

// 1. Input Data
const providerWsURL = 'wss://frag-moonbase-relay-rpc-ws.g.moonbase.moonbeam.network';
const amountToWithdraw = BigInt(1 * 10 ** 16); // 0.01 DEV
const devMultiLocation = { parents: 0, interior: { X1: { PalletInstance: 3 } } };
const weightTransact = 40000000000n; // 25000 * Gas limit of EVM call
const multiLocAccount = '0x61cd3e07fe7d7f6d4680e3e322986b7877f108dd';
const transactBytes =
  '0x260001eeed020000000000000000000000000000000000000000000000000000000000008a1932d6e26433f3037bd6c3a40c816222a6ccd40000c16ff286230000000000000000000000000000000000000000000000000091037ff36ab50000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000061cd3e07fe7d7f6d4680e3e322986b7877f108dd00000000000000000000000000000000000000000000000000000000a036b1b90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000d909178cc99d318e4d46e7e66a972955859670e10000000000000000000000001fc56b105c4f0a1a8038c2b429932b122f6b631f00';

// 2. XCM Destination (Moonbase Alpha Parachain ID 1000)
const dest = { V3: { parents: 0, interior: { X1: { Parachain: 1000 } } } };

// 3. XCM Instruction 1
const instr1 = {
  WithdrawAsset: [
    {
      id: { Concrete: devMultiLocation },
      fun: { Fungible: amountToWithdraw },
    },
  ],
};

// 4. XCM Instruction 2
const instr2 = {
  BuyExecution: {
    fees: {
      id: { Concrete: devMultiLocation },
      fun: { Fungible: amountToWithdraw },
    },
    weightLimit: { 'Unlimited': null },
  },
};

// 5. XCM Instruction 3
const instr3 = {
  Transact: {
    originKind: 'SovereignAccount',
    requireWeightAtMost: { refTime: weightTransact, proofSize: 700000n },
    call: {
      encoded: transactBytes,
    },
  },
};

// 6. XCM Instruction 4
const instr4 = {
  DepositAsset: {
    assets: { Wild: 'All' },
    beneficiary: {
      parents: 0,
      interior: { X1: { AccountKey20: { key: multiLocAccount } } },
    },
  },
};

// 7. Build XCM Message
const message = { V3: [instr1, instr2, instr3, instr4] };

const generateCallData = async () => {
  // 8. Create Substrate API Provider
  const substrateProvider = new WsProvider(providerWsURL);
  const api = await ApiPromise.create({ provider: substrateProvider });

  // 9. Create the Extrinsic
  const tx = api.tx.xcmPallet.send(dest, message);

  // 10. Get SCALE Encoded Calldata
  const encodedCall = tx.toHex();
  console.log(`Encoded Calldata: ${encodedCall}`);

  api.disconnect();
};

generateCallData();

注意事项

您也可以在Polkadot.js Apps中手动构建函数以获得SCALE编码调用数据。

让我们检视上方代码段的主要组成部分:

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

    • Moonbase Alpha中继链终端URL以创建provider
    • 从多地点衍生账户中提取的Token数量(以Wei为单位)。以此例子来说,0.01个Token绰绰有余。要了解如何获取此值,请参考XCM费用页面
    • Moonbase Alpha上看到的DEV Token的多地点
    • transact XCM指令的权重。这可以通过将25000和之前获得的gas limit相乘得到。建议增加大约10%的估计值。您可以在通过XCM进行远程EVM调用页面中阅读有关此数值的更多信息
    • 多地点衍生账户,因其将会在其后的XCM指令中用到
    • 我们在先前教程中计算的transact XCM指令字节
  2. 为XCM消息定义目标多地点。在此例中为Moonbase Alpha平行链

  3. 首个XCM指令,WithdrawAsset。您需要提供资产多地点以及您希望提取的数量,两个变量皆已在先前提及
  4. 第二个XCM指令,BuyExecution。在此,我们通过提供DEV Token的多地点和我们用之前的指令取出的数量来支付Moonbase Alpha区块执行时间。接下来,我们用0.001 DEV Token购买所有可能的执行(Unlimited权重),这应该是大约200亿个权重单位,对于我们的范例来说足够了
  5. 第三个XCM指令,Transact。此指令将会使用购买的部分权重(被定义为requireWeightAtMost)并执行提供的任意字节(transactBytes
  6. 第四个XCM指令,DepositAsset。在之前执行的操作之后剩下的在holding里的任何东西(在这种情况下,它应该为DEV Token)被存入多地点衍生账户,设置为 beneficiary
  7. 通过在V2数组中连接指令构建XCM消息
  8. 创建Polkadot.js API provider
  9. 使用目标链和XCM消息制作xcmPallet.send函数。此函数会将DescendOrigin XCM指令附加到我们的XCM消息中,该指令将提供必要的信息计算多地点衍生账户
  10. 获取SCALE编码调用数据。请注意,在这种特定情况下,因为我们需要完整的SCALE编码调用数据,所以我们必须使用tx.toHex()。这是因为我们将使用调用数据提交此交易

挑战

您可以尝试一个更直接的范例,并执行从多地点衍生账户到您喜欢的任何其他账户的余额转移。您必须为balance.Transfer extrinsic构建SCALE编码调用数据,或者创建以太坊调用作为余额转账交易。

当您设定好代码,您可以通过node执行,您将会获得中继链XCM调用数据:

Getting the Relay Chain XCM calldata for Uniswap V2 swap

此范例的编码调用数据如下:

0x4d0604630003000100a10f031000040000010403000f0000c16ff28623130000010403000f0000c16ff286230006010700902f500982b92a00fd04260001eeed020000000000000000000000000000000000000000000000000000000000008a1932d6e26433f3037bd6c3a40c816222a6ccd40000c16ff286230000000000000000000000000000000000000000000000000091037ff36ab50000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000061cd3e07fe7d7f6d4680e3e322986b7877f108dd00000000000000000000000000000000000000000000000000000000a036b1b90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000d909178cc99d318e4d46e7e66a972955859670e10000000000000000000000001fc56b105c4f0a1a8038c2b429932b122f6b631f000d01000001030061cd3e07fe7d7f6d4680e3e322986b7877f108dd

现在我们已经拥有了SCALE编码调用数据,最后一个步骤是提交交易,交易将会传送我们XCM消息至Moonbase Alpha并执行远程EVM调用。

从中继链发送XCM消息

此部分教程将会综合所有上述内容,让我们复习我们先前做了什么:

  • 我们创建了一个拥有UNIT Token(中继链原生Token)的中继链账户
  • 我们决定了其在Moonbase Alpha上的多地点衍生账户并使此新地址拥有足够的DEV Token(Moonbase Alpha原生Token)
  • 我们获得了Uniswap V2兑换编码调用数据,其中我们将使用0.01 DEV Token兑换成MARS(Moonbase Alpha中的ERC-20格式Token)。我们同样还更改了部分内容以使其符合此特例
  • 我们在Moonbase Alpha中构建了SCALE编码调用数据以通过XCM访问EVM
  • 我们设计了我们的交易以向Moonbase Alpha发送一条XCM消息,我们将在其中要求它执行之前构建的SCALE编码调用数据。接着,这将执行一个EVM调用,该调用将为MARS Token执行Uniswap V2兑换!

如要发送我们先前教程中构建的XCM消息,您可以使用以下代码段:

import { ApiPromise, WsProvider } from '@polkadot/api'; // Version 9.13.6
import Keyring from '@polkadot/keyring'; // Version 10.3.1

// 1. Input Data
const providerWsURL = 'wss://frag-moonbase-relay-rpc-ws.g.moonbase.moonbeam.network';
const MNEMONIC = 'INSERT_MNEMONIC'; // Not safe, only for testing
const txCall =
  '0x4d0604630003000100a10f031000040000010403000f0000c16ff28623130000010403000f0000c16ff286230006010700902f500982b92a00fd04260001eeed020000000000000000000000000000000000000000000000000000000000008a1932d6e26433f3037bd6c3a40c816222a6ccd40000c16ff286230000000000000000000000000000000000000000000000000091037ff36ab50000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000061cd3e07fe7d7f6d4680e3e322986b7877f108dd00000000000000000000000000000000000000000000000000000000a036b1b90000000000000000000000000000000000000000000000000000000000000002000000000000000000000000d909178cc99d318e4d46e7e66a972955859670e10000000000000000000000001fc56b105c4f0a1a8038c2b429932b122f6b631f000d01000001030061cd3e07fe7d7f6d4680e3e322986b7877f108dd';

// 2. Create Keyring Instance
const keyring = new Keyring({ type: 'sr25519' });

const sendXCM = async () => {
  // 3. Create Substrate API Provider
  const substrateProvider = new WsProvider(providerWsURL);
  const api = await ApiPromise.create({ provider: substrateProvider });

  // 4. Create Account from Mnemonic
  const alice = keyring.addFromUri(MNEMONIC);

  // 5. Create the Extrinsic
  const tx = await api.tx(txCall).signAndSend(alice, (result) => {
    // 6. Check Transaction Status
    if (result.status.isInBlock) {
      console.log(`Transaction included in blockHash ${result.status.asInBlock}`);
    }
  });

  api.disconnect();
};

sendXCM();

当您设定好代码,您可以通过node执行,该XCM消息将会被发送以执行您在Moonbase Alpha上的Uniswap V2兑换:

Sending the XCM message from the Relay Chain to Moonbase Alpha for the Uniswap V2 swap

就是这样!您发送了一条XCM消息,该消息通过XCM执行了远程EVM调用,并在Moonbase Alpha中产生了Uniswap V2格式的交换。但是,让我们来更详细地了解究竟发生什么事情。

此操作将触发不同的事件。第一个是在中继链中唯一相关的,它被命名为xcmPallet.Sent,其来自xcmPallet.send extrinsic。在Moonbase Alpha中,来自parachainSystem.setValidationData extrinsic(处理所有输入XCM消息)的以下事件值得关注:

  • parachainSystem.DownwardMessagesReceived — 表示收到一个XCM消息
  • evm.Log — 不同合约调用发出的内部事件。结构皆相同:合约地址、主题和相关数据
  • ethereum.Executed — 包含有关from地址、to地址和已完成EVM调用的交易哈希的信息
  • polkadotXcm.AssetsTrapped — 标记了部分资产仍在holding中未存入给定地址。如果Transact XCM指令没有用尽分配给它的Token,它将在XCM被执行之后执行RefundSurplus。该指令将从购买的执行中取出任何剩余的Token并将其转移至holding。我们可以通过调整提供给Transact指令的费用,或者在Transact之后立即添加指令来防止这种情况
  • dmpQueue.ExecutedDownward — 说明执行从中继链接收到的消息(DMP消息)的结果。在这种情况下,outcome被标记为Complete

我们的XCM已成功执行!如果您访问Moonbase Alpha Moonscan并搜索交易哈希,你将能发现通过XCM消息执行的Uniswap V2兑换。

挑战

为您想要的任何其他Token执行MARS的Uniswap V2兑换。请注意,在这种情况下,您必须首先通过XCM远程执行一个ERC-20 approve,以允许Uniswap V2路由代表您使用Token。在成功批准后,您将可以发送兑换本身的XCM消息。

本教程仅用于教育目的。 因此,不应在生产环境中使用本教程中创建的任何合约或代码。

Last update: January 25, 2024
| Created: March 29, 2023