Using the Moonbeam XCM SDK: v1¶
Introduction¶
The Moonbeam XCM SDK enables developers to easily transfer assets between chains, either between parachains or between a parachain and the relay chain, within the Polkadot/Kusama ecosystem. With the SDK, you don't need to worry about determining the multilocation of the origin or destination assets or which extrinsics are used on which networks to send XCM transfers.
The XCM SDK offers helper functions, that provide a very simple interface to execute XCM transfers between chains in the Polkadot/Kusama ecosystem. In addition, the XCM config package allows any parachain project to add their information in a standard way, so they can be immediately supported by the XCM SDK.
For an overview of the available methods and interfaces in the Moonbeam XCM SDK, please refer to the Reference page.
The examples in this guide are shown on Moonbeam, but can be adapted to be used on Moonriver or Moonbase Alpha.
Install the XCM SDK¶
To get started with the Moonbeam XCM SDK, you'll need to first install the SDK:
npm install @moonbeam-network/xcm-sdk
You'll also need to install a few additional dependencies that you'll use to interact with the SDK in this guide. You'll need the Polkadot.js API to create a Polkadot signer:
npm install @polkadot/api @polkadot/util-crypto
You'll also need an Ethereum signer if you're interacting with an Ethereum-compatible chain like Moonbeam. This guide will cover using Ethers.js and viem. You'll need to install whichever library you want to use:
npm install ethers@^5.7.2
npm install viem
Create Signers¶
When transferring assets between chains, you'll need signers in place to sign the transactions. If you're interacting with an Ethereum-compatible chain that uses standard Ethereum-style H160 addresses, such as Moonbeam, you'll need to have an Ethereum signer, which can be an Ethers.js signer or a viem Wallet Client. To interact with the relay chain or other parachains, you'll need a Polkadot signer.
You can pass, for example, a browser extension wallet as a signer into Ethers or viem, such as MetaMask. Similarly, with Polkadot, you can pass a compatible wallet to the signer using the @polkadot/extension-dapp
library.
To create an EVM signer and a Polkadot signer, you can refer to the following sections.
Remember
Never store your private key or mnemonic in a JavaScript or TypeScript file.
Create a EVM Signer¶
To create an Ethers signer, you can use the following code snippet:
import { ethers } from 'ethers';
const privateKey = 'INSERT_PRIVATE_KEY';
const provider = new ethers.providers.WebSocketProvider('INSERT_WS_ENDPOINT', {
chainId: INSERT_CHAIN_ID,
name: 'INSERT_CHAIN_NAME',
});
const evmSigner = new ethers.Wallet(privateKey, provider);
For Moonbeam specifically, you can use the following configurations:
import { ethers } from 'ethers';
const privateKey = 'INSERT_PRIVATE_KEY';
const provider = new ethers.providers.WebSocketProvider(
'INSERT_WSS_API_ENDPOINT',
{
chainId: 1284,
name: 'moonbeam',
}
);
const evmSigner = new ethers.Wallet(privateKey, provider);
import { ethers } from 'ethers';
const privateKey = 'INSERT_PRIVATE_KEY';
const provider = new ethers.providers.WebSocketProvider(
'INSERT_WSS_API_ENDPOINT',
{
chainId: 1285,
name: 'moonriver',
}
);
const evmSigner = new ethers.Wallet(privateKey, provider);
import { ethers } from 'ethers';
const privateKey = 'INSERT_PRIVATE_KEY';
const provider = new ethers.providers.WebSocketProvider(
'wss://wss.api.moonbase.moonbeam.network',
{
chainId: 1287,
name: 'moonbase',
}
);
const evmSigner = new ethers.Wallet(privateKey, provider);
Alternatively, you can create a viem Wallet Client to pass as EVM signer:
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts'
import { moonbeam } from 'viem/chains';
const privateKey = 'INSERT_PRIVATE_KEY';
const account = privateKeyToAccount(privateKey);
const evmSigner = createWalletClient({
account,
chain: moonbeam,
transport: http(),
});
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts'
import { moonriver } from 'viem/chains';
const privateKey = 'INSERT_PRIVATE_KEY';
const account = privateKeyToAccount(privateKey);
const evmSigner = createWalletClient({
account,
chain: moonriver,
transport: http(),
});
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts'
import { moonbaseAlpha } from 'viem/chains';
const privateKey = 'INSERT_PRIVATE_KEY';
const account = privateKeyToAccount(privateKey);
const evmSigner = createWalletClient({
account,
chain: moonbaseAlpha,
transport: http(),
});
If you want to pass in a browser extension wallet to viem, you can use the following code:
import { createWalletClient, custom } from 'viem';
import { moonbeam } from 'viem/chains';
const evmSigner = createWalletClient({
chain: moonbeam,
transport: custom(window.ethereum),
});
import { createWalletClient, custom } from 'viem';
import { moonriver } from 'viem/chains';
const evmSigner = createWalletClient({
chain: moonriver,
transport: custom(window.ethereum),
});
import { createWalletClient, custom } from 'viem';
import { moonbaseAlpha } from 'viem/chains';
const evmSigner = createWalletClient({
chain: moonbaseAlpha,
transport: custom(window.ethereum),
});
Note
To configure your project for Moonbeam or Moonriver, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
Create a Polkadot Signer¶
In this example, you can use a Polkadot.js Keyring to sign transactions. Please note that this approach is not recommended for production applications.
import { Keyring } from '@polkadot/api';
import { cryptoWaitReady } from '@polkadot/util-crypto';
const privateKey = 'INSERT_PRIVATE_KEY';
await cryptoWaitReady();
const keyring = new Keyring({
ss58Format: 'INSERT_SS58_FORMAT',
type: 'sr25519',
});
const pair = keyring.createFromUri(privateKey);
Note
In the above INSERT_PRIVATE_KEY
field, you can specify a seed phrase instead of a private key.
Get Asset and Chain Data¶
You can use any of the following code examples to retrieve information on the supported assets and the chains that support these assets.
Get List of Supported Assets¶
To get a list of all of the assets supported by the XCM SDK, you can instantiate the XCM SDK and call the assets
function.
import { Sdk } from '@moonbeam-network/xcm-sdk';
const sdkInstance = new Sdk();
const assets = sdkInstance.assets();
console.log('The supported assets are as follows:');
assets.assets.forEach((asset) => {
console.log(`- ${asset.originSymbol}`);
});
Get List of Supported Assets by Ecosystem¶
To get a list of the supported assets for a particular ecosystem, you can pass in the ecosystem name: polkadot
, kusama
, or alphanet-relay
. For example, the following snippet will get all of the Polkadot assets supported:
import { Sdk } from '@moonbeam-network/xcm-sdk';
const sdkInstance = new Sdk();
const assets = sdkInstance.assets('polkadot');
console.log('The supported assets within the Polkadot ecosystem are as follows:');
assets.assets.forEach((asset) => {
console.log(`- ${asset.originSymbol}`);
});
Get List of Supported Chains by Asset¶
To get a list of the supported source and destination chains for a given asset, you can use the following code snippet, which logs the supported chains by asset for all of the supported assets in the Polkadot ecosystem:
import { Sdk } from '@moonbeam-network/xcm-sdk';
const sdkInstance = new Sdk();
const assets = sdkInstance.assets('polkadot');
assets.assets.forEach((asset) => {
const { sourceChains, source } = assets.asset(asset);
console.log(`You can send ${asset.originSymbol}...`);
if (sourceChains.length > 1) {
sourceChains.forEach((sourceChain) => {
const { destinationChains } = source(sourceChain);
if (destinationChains.length > 0) {
destinationChains.forEach((destination) => {
console.log(`- From ${source.name} to ${destination.name}`);
});
}
});
}
});
Build XCM Transfer Data¶
In order to transfer an asset from one chain to another, you'll need to first build the transfer data, which defines the asset to be transferred, the source chain and address, the destination chain and address, and the associated signer for the transaction. Building the transfer data is the first step; in the next section, you'll learn how to use the transfer data to actually transfer the asset.
To get started, you'll use the Sdk
function, which will expose two methods for building the XCM transfer data: assets
and getTransferData
.
import { Sdk } from '@moonbeam-network/xcm-sdk';
const sdkInstance = new Sdk();
You can choose either method, as both will return the data necessary to initiate an asset transfer between the source chain and the destination chain. Using assets
will provide additional data along the way, including the list of supported assets and, once an asset is selected, the supported source and destination chains that are able to send and receive the asset.
The process for using assets
to build the transfer data is as follows:
-
Call the
assets
function and optionally pass in the ecosystem that you want to retrieve a list of assets for or that the asset you want to transfer belongs to. The available ecosystems are:polkadot
,kusama
, andalphanet-relay
. For example:const { assets, asset } = sdkInstance.assets('polkadot');
This will return a list of the supported assets and the
asset
function that can be used to define the asset to be transferred -
Call the
asset
function and pass in the key or asset object (which includes the key and the origin symbol) to define the asset to be transferred. For example:// Using the key const { sourceChains, source } = asset('dot');
This will return a list of the supported source chains and the
source
function, which is used to define the source chain to transfer the asset from -
Call the
source
function and pass in the key or the chain object (which includes the key, name, and chain type). For example:// Using the key const { destinationChains, destination } = source('polkadot');
This will return a list of the supported destination chains where there is an open XCM channel from the source chain for the given asset and the
destination
function, which is used to define the destination chain to transfer the asset to -
Call the
destination
function and pass in the key or the chain object (which includes the key, name, and chain type). For example:// Using the key const { accounts } = destination('moonbeam');
This will return the
accounts
function, which is used to define the source and destination addresses and the associated signers for each address
The asset and chain objects are managed within the @moonbeam-network/xcm-config
package. You do not need to directly interact with this package as the SDK exposes this data, but there you can find the list of assets and chain data.
An example of the steps described above to build the transfer data to transfer DOT from the Polkadot relay chain to Moonbeam is as follows:
import { Sdk } from '@moonbeam-network/xcm-sdk';
const sdkInstance = new Sdk();
const fromPolkadot = async () => {
const { assets, asset } = sdkInstance.assets();
console.log(
`The supported assets are: ${assets.map((asset) => asset.originSymbol)}`
);
const { sourceChains, source } = asset('dot');
console.log(
`The supported source chains are: ${sourceChains.map(
(chain) => chain.name
)}`
);
const { destinationChains, destination } = source('polkadot');
console.log(
`The supported destination chains are: ${destinationChains.map(
(chain) => chain.name
)}`
);
const { accounts } = destination('moonbeam');
const data = await accounts(
pair.address,
evmSigner.address, // If using viem, use evmSigner.account.address
{
evmSigner,
polkadotSigner: pair,
}
);
};
fromPolkadot();
Note
For more information on each of the Sdk().assets()
builder functions, including the parameters and returned data, please refer to the XCM SDK Reference.
If you don't need any of the asset or chain information, you can use the getTransferData
function:
import { Sdk } from '@moonbeam-network/xcm-sdk';
const sdkInstance = new Sdk();
const fromPolkadot = async () => {
const data = await sdkInstance.getTransferData({
destinationAddress: evmSigner.address, // If using viem, use evmSigner.account.address
destinationKeyOrChain: 'moonbeam',
keyOrAsset: 'dot',
polkadotSigner: pair,
sourceAddress: pair.address,
sourceKeyOrChain: 'polkadot',
evmSigner,
});
};
fromPolkadot();
Note
For more information on the Sdk().getTransferData()
function, including the parameters and returned data, please refer to the XCM SDK Reference.
As previously mentioned, regardless of which method you use to build the transfer data, you'll generate the same output.
Example response
// Send DOT from Polkadot to Moonbeam
// data
{
destination: {
balance: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
chain: l {
ecosystem: 'polkadot',
isTestChain: false,
key: 'moonbeam',
name: 'Moonbeam',
type: 'evm-parachain',
assetsData: [Map],
genesisHash: '0xfe58ea77779b7abda7da4ec526d14db9b1e9cd40a217c34892af80a9b332b76d',
parachainId: 2004,
ss58Format: 1284,
usesChainDecimals: false,
weight: 1000000000,
ws: 'wss://wss.api.moonbeam.network',
id: 1284,
rpc: 'https://rpc.api.moonbeam.network'
},
existentialDeposit: e {
key: 'glmr',
originSymbol: 'GLMR',
amount: 0n,
decimals: 18,
symbol: 'GLMR'
},
fee: e {
key: 'dot',
originSymbol: 'DOT',
amount: 33068783n,
decimals: 10,
symbol: 'DOT'
},
min: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
}
},
getEstimate: [Function: getEstimate],
isSwapPossible: true,
max: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
min: e {
key: 'dot',
originSymbol: 'DOT',
amount: 33068783n,
decimals: 10,
symbol: 'DOT'
},
source: {
balance: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
chain: m {
ecosystem: 'polkadot',
isTestChain: false,
key: 'polkadot',
name: 'Polkadot',
type: 'parachain',
assetsData: Map(0) {},
genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
parachainId: 0,
ss58Format: 0,
usesChainDecimals: false,
weight: 1000000000,
ws: 'wss://rpc.polkadot.io'
},
destinationFeeBalance: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
existentialDeposit: e {
key: 'dot',
originSymbol: 'DOT',
amount: 10000000000n,
decimals: 10,
symbol: 'DOT'
},
fee: e {
key: 'dot',
originSymbol: 'DOT',
amount: 169328990n,
decimals: 10,
symbol: 'DOT'
},
feeBalance: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
max: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
min: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
}
},
swap: [AsyncFunction: swap],
transfer: [AsyncFunction: transfer]
}
As you may have noticed in the example response, the transfer data contains information on the asset to be transferred, the source chain, and the destination chain. In addition, a few functions have been exposed:
swap()
- returns the transfer data necessary to swap the asset from the destination chain back to the source chaintransfer()
- transfers a given amount of the asset from the source chain to the destination chaingetEstimate()
- returns an estimated amount of the asset that will be received on the destination chain, less any destination fees
Transfer an Asset¶
Now that you've built the transfer data, you can go ahead and transfer the asset from the source chain to the destination chain. To do so, you can use the transfer
function, but first you'll need to specify an amount to send. You can specify the amount in integer or decimal format. For example, if you wanted to send 0.1 DOT, you could use 1000000000n
or '0.1'
. You can use asset conversion methods, like toDecimal
to convert the asset to decimal format.
For this example, you can transfer twice the minimum amount required of DOT:
...
const amount = data.min.toDecimal() * 2;
console.log(`Sending from ${data.source.chain.name} amount: ${amount}`);
const hash = await data.transfer(amount);
console.log(`${data.source.chain.name} tx hash: ${hash}`);
As seen in the above snippet, the transfer
function returns a transaction hash on the source chain.
Note
For more information on the parameters and returned data for transfer
, please refer to the XCM SDK Reference.
Swap an Asset¶
To swap an asset, you can use the same transfer data and call data.swap()
to switch the source and destination chain information. From there, you can simply call the transfer
function to execute the swap.
...
const swapData = await data.swap();
const amount = swapData.min.toDecimal() * 2;
console.log(`Sending from ${swapData.source.chain.name} amount: ${amount}`);
const hash = await swapData.transfer(amount);
console.log(`${swapData.source.chain.name} tx hash: ${hash}`);
The swap
function returns the transfer data with the original source chain and destination chain swapped. Using the previous example of sending DOT from Polkadot to Moonbeam, the swap transfer data would send DOT from Moonbeam to Polkadot.
Example response
// swapData
{
destination: {
balance: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
chain: m {
ecosystem: 'polkadot',
isTestChain: false,
key: 'polkadot',
name: 'Polkadot',
type: 'parachain',
assetsData: Map(0) {},
genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
parachainId: 0,
ss58Format: 0,
usesChainDecimals: false,
weight: 1000000000,
ws: 'wss://rpc.polkadot.io'
},
existentialDeposit: e {
key: 'dot',
originSymbol: 'DOT',
amount: 10000000000n,
decimals: 10,
symbol: 'DOT'
},
fee: e {
key: 'dot',
originSymbol: 'DOT',
amount: 169328990n,
decimals: 10,
symbol: 'DOT'
},
feeBalance: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
max: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
min: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
}
},
getEstimate: [Function: getEstimate],
isSwapPossible: true,
max: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
min: e {
key: 'dot',
originSymbol: 'DOT',
amount: 33068783n,
decimals: 10,
symbol: 'DOT'
},
source: {
balance: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
chain: l {
ecosystem: 'polkadot',
isTestChain: false,
key: 'moonbeam',
name: 'Moonbeam',
type: 'evm-parachain',
assetsData: [Map],
genesisHash: '0xfe58ea77779b7abda7da4ec526d14db9b1e9cd40a217c34892af80a9b332b76d',
parachainId: 2004,
ss58Format: 1284,
usesChainDecimals: false,
weight: 1000000000,
ws: 'wss://wss.api.moonbeam.network',
id: 1284,
rpc: 'https://rpc.api.moonbeam.network'
},
destinationFeeBalance: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
},
existentialDeposit: e {
key: 'glmr',
originSymbol: 'GLMR',
amount: 0n,
decimals: 18,
symbol: 'GLMR'
},
fee: e {
key: 'dot',
originSymbol: 'DOT',
amount: 33068783n,
decimals: 10,
symbol: 'DOT'
},
min: e {
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
}
},
swap: [AsyncFunction: swap],
transfer: [AsyncFunction: transfer]
}
Note
For more information on the parameters and returned data for swap
, please refer to the XCM SDK Reference.
Get an Estimate of the Asset to be Received on the Destination Chain¶
When you send an XCM message, you typically pay fees on the destination chain to execute the XCM instructions. Before you transfer the asset, you can use the getEstimate
function to calculate an estimated amount of the asset that will be received on the destination chain, minus any fees.
The getEstimate
function is tied to a specific transfer request as it is based on the asset being transferred and the destination chain fees, so you'll need to create the transfer data first.
You'll need to provide the amount to be transferred to the getEstimate
function. In the following example, you'll get the estimated amount of DOT that will be received on Moonbeam when 0.1 DOT is transferred. You can specify the amount in integer (1000000000n
) or decimal ('0.1'
) format.
...
const amount = '0.1';
const estimatedAmount = data.getEstimate(amount);
console.log(
`The estimated amount of ${
data.source.balance.originSymbol
} to be received on ${
data.destination.chain.name
} is: ${estimatedAmount.toDecimal()} ${data.destination.balance.symbol}`
);
The getEstimate
function returns the estimated amount along with information on the asset being transferred.
Example response
// estimatedAmount
{
key: 'dot',
originSymbol: 'DOT',
amount: 966931217n,
decimals: 10,
symbol: 'DOT'
}
Note
For more information on the parameters and returned data for getEstimate
, please refer to the XCM SDK Reference.
Get Transfer Minimum and Maximum Amounts¶
You can use transfer data to retrieve the minimum and maximum amount of an asset that can be transferred. To do so, you'll access the min
and max
properties of the asset being transferred:
...
const amount = data.min.toDecimal();
const symbol = data.min.originSymbol;
console.log(`You can send min: ${amount} ${symbol}`);
...
const amount = data.max.toDecimal();
const symbol = data.max.originSymbol;
console.log(`You can send max: ${amount} ${symbol}`);
The min
and max
properties return the minimum and maximum amount of the asset that can be transferred, along with information on the asset. If the source account does not hold a balance of the chosen asset, the data.max
amount will be 0n
.
Example response
// data.min
{
key: 'dot',
originSymbol: 'DOT',
amount: 33068783n,
decimals: 10,
symbol: 'DOT'
}
// data.max
{
key: 'dot',
originSymbol: 'DOT',
amount: 0n,
decimals: 10,
symbol: 'DOT'
}
Note
For more information on assets and asset amounts, please refer to the XCM SDK Reference.
Get Transfer Fees¶
The transfer data provides information on transfer fees for the source and destination chains. You can retrieve the fees using the following snippet:
...
const sourceChain = data.source.chain.name;
const sourceFee = data.source.fee;
const destinationChain = data.destination.chain.name;
const destinationFee = data.destination.fee;
console.log(
`You will pay ${sourceFee.toDecimal()} ${
sourceFee.symbol
} fee on ${
sourceChain
} and ${destinationFee.toDecimal()} ${
destinationFee.symbol
} fee on ${destinationChain}.`
);
The fee
property returns the amount of fees to be paid along with information on the asset.
Example response
// sourceFee
{
key: 'dot',
originSymbol: 'DOT',
amount: 169328990n,
decimals: 10,
symbol: 'DOT'
}
// destinationFee
{
key: 'dot',
originSymbol: 'DOT',
amount: 33068783n,
decimals: 10,
symbol: 'DOT'
}
Note
For more information on assets and asset amounts, including fees, please refer to the XCM SDK Reference.
| Created: June 23, 2023