1

I am trying to create a reserve transfer assets call from a relay chain like Kusama to a Parachain.

To do this, I need to:

  • connect to the relay chain with Polkadot JS API
  • construct the XCM types required by the reserve asset transfer function
  • submit an extrinsic to trigger that xcm, preferably using an account from the Polkadot JS Extension

What is a minimal working example of this process using XCM V2 syntax?

1 Answer 1

1

This can all be done in a simple, pure JavaScript example.

Start with a minimal HTML page with the necessary Polkadot JS imports:

index.html

<!doctype html>
<html lang="en">

<body>
    <h1>Simple XCM Example</h1>
    <button onclick="submitXcm()">Submit an XCM Transaction</button>

    <script src="//unpkg.com/@polkadot/util/bundle-polkadot-util.js"></script>
    <script src="//unpkg.com/@polkadot/util-crypto/bundle-polkadot-util-crypto.js"></script>
    <script src="//unpkg.com/@polkadot/types/bundle-polkadot-types.js"></script>
    <script src="//unpkg.com/@polkadot/api/bundle-polkadot-api.js"></script>
    <script src="//unpkg.com/@polkadot/extension-dapp/bundle-polkadot-extension-dapp.js"></script>
    <!-- Our Custom JavaScript -->
    <script src="./index.js"></script>
</body>

</html>

Then create an index.js file where we will do most of our work.

Connect to Polkadot JS API

To establish a basic connection to Polkadot JS, we can follow the templates found in the docs: https://polkadot.js.org/docs/

index.js

const { WsProvider, ApiPromise } = polkadotApi;
// We will use these later to access the Polkadot JS Extension
const { web3FromAddress, web3Enable } = polkadotExtensionDapp;

async function submitXcm() {
  const provider = new WsProvider('wss://kusama-rpc.polkadot.io');
  const api = await ApiPromise.create({ provider });

  console.log('connected:', api.genesisHash.toHex());

  // We will add the code described below here...
}

Create MultiLocation for Parachain Destination

The first trick to make this work is using api.createType to allow Polkadot JS API to create all the concrete types you need.

Another trick is to make this all work is to use the XcmVersioned... types, not the direct MultiLocation type. In this case, you have to control which version of MultiLocation you want to use, but this is better.

Make sure that all of your constructed types use the same XCM version.

  const parachainId = 3338;
  const destination = api.createType('XcmVersionedMultiLocation', {
    V2: {
      parents: '0',
      interior: {
        X1: { Parachain: parachainId },
      },
    },
  });

From the perspective of the relay chain, you locate a parachain with parent: 0 and a single junction (X1) directly to the Parachain with parachainId.

Create a Beneficiary Location (Receiving Address)

In this scenario, we are reserve transferring KSM from the Kusama relay chain to a Parachain. We want to receive the credit for the reserve backed KSM on the parachain, so we need to figure out OUR MultiLocation.

  let address = 'EGVQCe73TpFyAZx5uKfE1222XfkT3BSKozjgcqzLBnc5eYo';
  let accountId = api.createType('AccountId', address);

  const beneficiary = api.createType('XcmVersionedMultiLocation', {
    V2: {
      parents: '0',
      interior: {
        X1: {
          AccountId32: {
            network: 'Any',
            id: accountId.toHex(),
          },
        },
      },
    },
  });

Some tricks here are:

  • Again, using createType and XcmVersioned... with a specific version number for constructing the types.
  • Passing in the address as a bytes represented by Hex. This is easy to do by creating an AccountId type, and calling toHex().

I believe this object is from the perspective of the parachain, thus the benificiary has 0 parents (since we are already in the perspective of the parachain) and directly references the AccountId32 from a single junction (X1).

Create the KSM MultiAsset

We need to also specify which asset we are transferring, and for this we need to create a MultiAssets type. Note that this is plural, so it expects a vector of MultiAsset, but we are only doing one here anyway.

  // 1 KSM = 10^12, this is .1 KSM
  const amount = 100000000000;
  const assets = api.createType('XcmVersionedMultiAssets', {
    V2: [
      {
        id: {
          Concrete: {
            parents: 0,
            interior: {
              Here: '',
            },
          },
        },
        fun: {
          Fungible: amount,
        },
      },
    ],
  });

We are again in the context of the relay chain, so the KSM asset is the Concrete asset with parents: 0 and Here. KSM is a fungible token, so we can specify how much of it we want to reserve transfer using Fungible and a number. In this case, we are doing 10^11 which is 0.1 KSM due to the fixed point math.

Selecting the Fee Asset Item

We need to pay fees for our XCM and we can choose which asset we want to pay with. Since we only have one asset in this example, the fee_asset_item is indexed to 0. You can imagine if you were transferring multiple assets at a time, you could select a different asset index based on the MultiAssets you constructed.

  const fee_asset_item = '0';

Constructing a Weight Limit

While this is not best practice, the easiest thing to do when making a reserve transfer is to set the weight_limit to Unlimited. You do risk burning a lot of assets to weight fees in the case that the calculated weight is very high, but generally speaking this shouldn't be the case, and really is up to the parachain to ensure that things are correct.

  const weight_limit = 'Unlimited';

Constructing our XCM Pallet Call

These different variables were exactly what we needed to create our limitedReserveTransferAssets call. We just plug it all in:

  const call = api.tx.xcmPallet.limitedReserveTransferAssets(
    destination,
    beneficiary,
    assets,
    fee_asset_item,
    weight_limit
  );

Signing and Submitting our Call with Polkadot JS Extension

Earlier, we selected an address to be the beneficiary of this XCM Reserve Transfer. We are assuming that this same address will also be the person initiating the transfer on Kusama itself.

We also assume that whoever is running this JS has already imported this account into their extension. Of course you can add fancy UI to query the available accounts in the extension and let the user select from them, but for this minimal example, we will just keep things hardcoded.

  await web3Enable('xcm js example');
  const injector = await web3FromAddress(address);

  const unsubscribe = await call.signAndSend(
    address,
    { signer: injector.signer },
    ({ status }) => {
      console.log(`Current status is ${status}`);

      if (status.isInBlock) {
        console.log(`Transaction included at blockHash ${status.asInBlock}`);
      } else if (status.isFinalized) {
        console.log(`Transaction finalized at blockHash ${status.asFinalized}`);
        unsubscribe();
      }
    }
  );

Tricks here:

  • Before we can access the injector for our address, we must first call web3Enable which prompts the user to allow the website to access your Polkadot Extension. The name you choose there will be what the user sees when they are deciding to accept access.
  • Then we obtain an injector for the address we want to sign with. Of course this will only work if the account is imported into the extension.
  • Then we sign and submit our call, using our injector, and keeping track of the status until it is finalized, at which point you should see your reserved back asset on the parachain.

For more documentation about interacting with the extension check out: https://polkadot.js.org/docs/extension/cookbook#get-all-extensions-accounts

Final View

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.