Remote Staking via XCM¶
December 14, 2022 | by Kevin Neilson
Introduction¶
In this tutorial, we’ll stake DEV tokens remotely by sending XCM instructions from an account on the Moonbase relay chain (equivalent to the Polkadot relay chain). This tutorial assumes a basic familiarity with XCM and Remote Execution via XCM. You don’t have to be an expert on these topics but you may find it helpful to have some XCM knowledge as background.
There are actually two possible approaches for staking on Moonbeam remotely via XCM. We could send a remote EVM call that calls the staking precompile, or we could use XCM to call the parachain staking pallet directly without interacting with the EVM. For this tutorial, we’ll be taking the latter approach and interacting with the parachain staking pallet directly.
Note that there are still limitations in what you can remotely execute through XCM messages. In addition, developers must understand that sending incorrect XCM messages can result in the loss of funds. Consequently, it is essential to test XCM features on a TestNet before moving to a production environment.
Checking Prerequisites¶
For development purposes this tutorial is written for Moonbase Alpha and Moonbase relay using TestNet funds. For prerequisites:
- A Moonbase Alpha relay chain account funded with some UNIT, the native token of the Moonbase relay chain. If you have a Moonbase Alpha account funded with DEV tokens, you can swap some DEV for xcUNIT here on Moonbeam Swap. Then withdraw the xcUNIT from Moonbase Alpha to your account on the Moonbase relay chain using apps.moonbeam.network
- You'll need to calculate the multilocation derivative account of your Moonbase Alpha relay chain account and fund it with DEV tokens. You can get DEV tokens for testing on Moonbase Alpha once every 24 hours from the Moonbase Alpha Faucet
Calculating your Multilocation Derivative Account¶
Copy the account of your existing or newly created account on the Moonbase relay chain. You're going to need it to calculate the corresponding multilocation derivative account, which is a special type of account that’s keyless (the private key is unknown). Transactions from a multilocation derivative account can be initiated only via valid XCM instructions from the corresponding account on the relay chain. In other words, you are the only one who can initiate transactions on your multilocation derivative account - and if you lose access to your Moonbase relay account, you’ll also lose access to your multilocation derivative account.
To generate the multilocation derivative account, first clone Alberto’s xcmTools repo. Run yarn
to install the necessary packages and then run:
ts-node calculateMultilocationDerivative.ts \
--w wss://wss.api.moonbase.moonbeam.network \
--a YOUR-MOONBASE-RELAY-ACCOUNT-HERE \
--n 0x57657374656e64
Let's review the parameters passed along with this command:
- The
-w
flag corresponds to the endpoint we’re using to fetch this information - The
-a
flag corresponds to your Moonbase relay chain address - The
-n
flag corresponds to the encoded form of “westend”, the name of the relay chain that Moonbase relay is based on
The script will return 32-byte and 20-byte addresses. We’re interested in the ethereum-style account - the 20-byte one. Feel free to look up your multilocation derivative account on Moonscan. You’ll note that this account is empty. You’ll now need to fund this account with at least 1.1 DEV. As this is the amount that the faucet dispenses, you'll need to make a minimum of two faucet requests or you can always reach out to us on Discord for additional DEV tokens.
Preparing to Stake on Moonbase Alpha¶
First and foremost, you’ll need the address of the collator you want to delegate to. To locate it, head to the Moonbase Alpha Staking dApp in a second window. Ensure you’re on the correct network, then press Select a Collator. Next to your desired collator, press the Copy icon. You’ll also need to make a note of the number of delegations your collator has. The PS-31 collator shown below has 60
delegations at the time of writing.
Remote Staking via XCM with Polkadot.js Apps¶
If you prefer to perform these steps programmatically via the Polkadot API, you can instead skip to the following section.
First, generate the encoded call data of the staking action by heading to Moonbase Alpha Polkadot.js Apps. In order to see the Extrinsics menu here, you’ll need to have at least one account accessible in Polkadot.js Apps. If you don’t, create one now. Then, head to the Developer tab and press Extrinsics.
In the following steps you will be preparing a transaction, but you’ll need to refrain from submitting the transaction here in order to complete this tutorial in its entirety. We’ll take the resulting encoded call data from preparing this staking operation, and send it via XCM from the relay chain in a later step. From the Extrinsics page, take the following steps:
- Select the parachainStaking Pallet
- Select the delegate function
- Paste in your selected collator’s address. You can retrieve a list of collator candidates via the Polkadot.js API with these instructions
- Paste your desired stake amount in Wei. In the below example 1 DEV or
1000000000000000000
Wei is specified. You can find a unit converter here on Moonscan - Enter the collator’s number of existing delegations (this can be found next to the collator’s name / address on the Moonbase Alpha Staking dApp or fetched from the Polkadot.js API). Alternatively, you can enter the upper bound of
300
because this estimation is only used to determine the weight of the call - Enter your number of existing delegations from your multilocation derivative account. This is most likely
0
but because this estimation is only used to determine the weight of the call, you can specify an upper bound here of100
. Or, if you'd prefer, you can use the Polkadot.js API to fetch your exact number of existing delegations according to these instructions - Finally, copy the encoded call data to a text file or another easily accessible place because you will need it later. Do not copy the encoded call hash, and do not submit the transaction
Note
Astute readers may notice the selected account below is named “Academy.” It does not matter which account you have selected in Moonbase Alpha Polkadot.js Apps. This is because you're not submitting the prepared transaction, only copying the encoded call data, which does not contain a reference to the sending account.
Sending the XCM Instructions from Polkadot.js Apps¶
If you'd prefer to submit the XCM instructions programmatically via the Polkadot API, you can skip to the following section. Otherwise, in another tab, head to Moonbase relay Polkadot.Js Apps. Click on the Developer tab and press Extrinsics.
Building the Destination Multilocation¶
Let’s get started crafting our XCM message that will transport our remote execution instructions to the Moonbase Alpha parachain to ultimately stake our desired amount of DEV tokens to a chosen collator. To get started, take the following steps:
- Unlike the prior steps where the selected account wasn’t relevant, the account selected here must be the account associated with your multilocation derivative account
- Choose the xcmPallet pallet
- Choose the send method
- Set the destination version to V1
- To target Moonbase Alpha, set the destination to:
{
"parents":0,
"interior":
{
"x1":
{
"Parachain": 1000
}
}
}
In the next section, we’ll start assembling the XCM instructions.
Preparing the Structure of the XCM Message¶
- Select V2 for XcmVersionedXcm
- Our XCM Message is going to have 3 distinct XCM instructions, so press the first Add Item button 3 times
- Below the first XCM Instruction of WithdrawAsset, we need to add the asset we’re going to withdraw here, so press the Add Item button below WithdrawAsset once
Assembling the Contents of the XCM Message¶
Now we’re ready for the fun part! You'll need to press Add Item beneath the BuyExecution and Transact XCM instructions respectively. Construct the XCM message that will remotely stake funds on the Moonbase Alpha parachain as follows:
{
"WithdrawAsset":
[
{
"id":
{
"Concrete":
{
"parents": 0,
"interior": {
"X1": {
"PalletInstance": 3
}
}
}
"Fungible": 100000000000000000
}
],
"BuyExecution":
{
"fees": {
"id":
{
"Concrete":
{
"parents": 0,
"interior": {
"X1": {
"PalletInstance": 3
}
}
}
"Fungible": 100000000000000000
}
},
"weightLimit": "Unlimited"
},
"Transact":
{
"originType": "SovereignAccount",
"requiredWeightAtMost": "40000000000",
"call": {
"encoded": "0x0c113a7d3048f3cb0391bb44b518e5729f07bcc7a45d000064a7b3b6e00d00000000000000002c01000025000000"
}
}
}
Note
Providing the above encoded call data will automatically stake to the PS-31 collator on Moonbase Alpha. You are welcome to delegate to any collator on Moonbase Alpha provided you have copied the appropriate encoded call data from Moonbase Alpha Polkadot.js Apps.
Verify that the structure of your XCM message resembles the below image, then press Submit Transaction. Note that your encoded call data will vary based on your chosen collator.
Note
The encoded call data for the call configured above is 0x630001010100a10f020c00040000010403001300008a5d78456301130000010403001300008a5d784563010006010700902f5009b80c113a7d3048f3cb0391bb44b518e5729f07bcc7a45d000064a7b3b6e00d00000000000000002c01000025000000
.
And that’s it! To verify that your delegation was successful, you can visit Subscan to check your staking balance. Be advised that it may take a few minutes before your staking balance is visible on Subscan. Additionally, be aware that you will not be able to see this staking operation on Moonscan, because we initiated the delegation action directly via the parachain staking pallet (on the substrate side) rather than through the staking precompile (on the EVM).
Remote Staking via XCM with the Polkadot API¶
Here, we'll be taking the same series of steps as above, only this time, we'll be relying on the Polkadot API instead of using Polkadot.js Apps.
Start by generating the encoded call data via the Polkadot API as shown below. Here, we are not submitting a transaction but simplying preparing one to get the encoded call data. Remember to update delegatorAccount
with your account. Feel free to run the below code snippet locally.
import { ApiPromise, WsProvider } from "@polkadot/api";
const provider = new WsProvider("wss://wss.api.moonbase.moonbeam.network");
const candidate = "0x3A7D3048F3CB0391bb44B518e5729f07bCc7A45D";
const delegatorAccount = "YOUR-ACCOUNT-HERE";
const amount = "1000000000000000000";
const main = async () => {
const api = await ApiPromise.create({ provider: provider });
// Fetch the your existing number of delegations and the collators existing delegations
let delegatorInfo = await api.query.parachainStaking.delegatorState(
delegatorAccount
);
if (delegatorInfo.toHuman()) {
delegatorDelegationCount = delegatorInfo.toHuman()["delegations"].length;
} else {
delegatorDelegationCount = 0;
}
const collatorInfo = await api.query.parachainStaking.candidateInfo(
candidate
);
const candidateDelegationCount = collatorInfo.toHuman()["delegationCount"];
let tx = api.tx.parachainStaking.delegate(
candidate,
amount,
candidateDelegationCount,
delegatorDelegationCount
);
// Get SCALE Encoded Call Data
let encodedCall = tx.method.toHex();
console.log(`Encoded Call Data: ${encodedCall}`);
};
main();
Note
If running this as a TypeScript project, be sure to set the strict
flag under compilerOptions
to false
in your tsconfig.json
.
If you'd prefer not to set up a local environment you can run the below snippet in the JavaScript console of Polkadot.js Apps.
const candidate = '0x3A7D3048F3CB0391bb44B518e5729f07bCc7A45D';
const delegatorAccount = 'YOUR-ACCOUNT-HERE';
const amount = '1000000000000000000';
// Fetch the your existing number of delegations and the collators existing delegations
let delegatorInfo = await api.query.parachainStaking.delegatorState(delegatorAccount);
if (delegatorInfo.toHuman()) {
delegatorDelegationCount = delegatorInfo.toHuman()['delegations'].length;
} else {
delegatorDelegationCount = 0;
}
const collatorInfo = await api.query.parachainStaking.candidateInfo(candidate);
const candidateDelegationCount = collatorInfo.toHuman()["delegationCount"];
let tx = api.tx.parachainStaking.delegate(candidate, amount, candidateDelegationCount, delegatorDelegationCount);
// Get SCALE Encoded Call Data
let encodedCall = tx.method.toHex();
console.log(`Encoded Call Data: ${encodedCall}`);
Sending the XCM Instructions via the Polkadot API¶
In this section we'll be constructing and sending the XCM instructions via the Polkadot API. We'll be crafting an XCM message that will transport our remote execution instructions to the Moonbase Alpha parachain to ultimately stake our desired amount of DEV tokens to a chosen collator. After adding the seed phrase of your development account on Moonbase relay, you can construct and send the transaction via the Polkadot API as follows:
// Import
import { ApiPromise, WsProvider } from "@polkadot/api";
// Construct API provider
const wsProvider = new WsProvider(
"wss://frag-moonbase-relay-rpc-ws.g.moonbase.moonbeam.network"
);
const api = await ApiPromise.create({ provider: wsProvider });
// Import the keyring as required
import { Keyring } from "@polkadot/api";
// Initialize wallet key pairs
const keyring = new Keyring({ type: "sr25519" });
// For demo purposes only. Never store your private key or mnemonic in a JavaScript file
const otherPair = await keyring.addFromUri("YOUR-DEV-SEED-PHRASE-HERE");
console.log(`Derived Address from Private Key: ${otherPair.address}`);
// Create the destination multilocation (define where the message will be sent)
const dest = { V2: { parents: 0, interior: { X1: { Parachain: 1000 } } } };
// Create the full XCM message which defines the action to take on the destination chain
const message = {
V2: [
{
WithdrawAsset: [
{
id: {
concrete: { parents: 0, interior: { X1: { PalletInstance: 3 } } },
},
fun: { Fungible: 100000000000000000n },
},
],
},
{
BuyExecution: [
{
id: {
Concrete: { parents: 0, interior: { X1: { PalletInstance: 3 } } },
},
fun: { Fungible: 100000000000000000n },
},
{ unlimited: null },
],
},
{
Transact: {
originType: "SovereignAccount",
requireWeightAtMost: 40000000000n,
call: {
encoded:
"0x0c113a7d3048f3cb0391bb44b518e5729f07bcc7a45d000064a7b3b6e00d00000000000000002c01000025000000",
},
},
},
],
};
// Define the transaction using the send method of the xcm pallet
let tx = api.tx.xcmPallet.send(dest, message);
// Retrieve the encoded calldata of the transaction
const encodedCallData = tx.toHex();
console.log("Encoded call data is" + encodedCallData);
// Sign and send the transaction
const txHash = await tx.signAndSend(otherPair);
// Show the transaction hash
console.log(`Submitted with hash ${txHash}`);
Note
Remember that your multilocation derivative account must be funded with at least 1.1 DEV or more to ensure you have enough to cover the stake amount and transaction fees.
In the above snippet, besides submitting the remote staking via XCM transaction, we also print out the encoded call data and the transaction hash to assist with any debugging.
And that’s it! To verify that your delegation was successful, you can visit Subscan to check your staking balance. Be advised that it may take a few minutes before your staking balance is visible on Subscan. Additionally, be aware that you will not be able to see this staking operation on Moonscan, because we initiated the delegation action directly via the parachain staking pallet (on the Substrate side) rather than through the staking precompile (on the EVM).
| Created: December 10, 2022