Skip to content

DRAFT: Add XCM Runtime APIs tutorial #500

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
import { polkadot, polkadotAssetHub } from "@polkadot-api/descriptors";
import { createClient, Binary } from "polkadot-api";
import { getWsProvider } from "polkadot-api/ws-provider/node";
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
import {
PolkadotRuntimeOriginCaller,
XcmVersionedLocation,
XcmVersionedAssets,
XcmV3Junction,
XcmV3Junctions,
XcmV3WeightLimit,
XcmV3MultiassetFungibility,
XcmV3MultiassetAssetId,
DispatchRawOrigin,
} from "@polkadot-api/descriptors";
import {
ss58Decode,
ss58Encode,
DEV_PHRASE,
entropyToMiniSecret,
mnemonicToEntropy,
} from "@polkadot-labs/hdkd-helpers";
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
import { getPolkadotSigner } from "polkadot-api/signer";

async function main() {
// Set up RPC endpoints
const POLKADOT_RPC_ENDPOINT = "ws://localhost:8001";
const POLKADOT_ASSET_HUB_RPC_ENDPOINT = "ws://localhost:8000";

// Connect to the Polkadot relay chain
const polkadotClient = createClient(
withPolkadotSdkCompat(getWsProvider(POLKADOT_RPC_ENDPOINT))
);

// Connect to the Polkadot Asset Hub parachain
const polkadotAssetHubClient = createClient(
withPolkadotSdkCompat(getWsProvider(POLKADOT_ASSET_HUB_RPC_ENDPOINT))
);

// Get the typed APIs for the Polkadot relay chain and the Polkadot Asset Hub parachain
const polkadotApi = polkadotClient.getTypedApi(polkadot);
const polkadotAssetHubApi =
polkadotAssetHubClient.getTypedApi(polkadotAssetHub);

// Create a signer from your mnemonic
const MNEMONIC = DEV_PHRASE; //const MNEMONIC = "INSERT_YOUR_MNEMONIC";
const miniSecret = entropyToMiniSecret(mnemonicToEntropy(MNEMONIC));
const derive = sr25519CreateDerive(miniSecret);
const accountKeyPair = derive("//Alice"); // const accountKeyPair = derive("");
const accountSigner = getPolkadotSigner(
accountKeyPair.publicKey,
"Sr25519",
accountKeyPair.sign
);

try {
// Define the parameters for the transaction
const senderAddress = "15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5"; // Alice's address
const receiverAddress = "16D2eVuK5SWfwvtFD3gVdBC2nc2BafK31BY6PrbZHBAGew7L"; // Bob's address
const amountToTransfer = 120000000000n; // 12 DOT
const polkadotAssetHubParaId = 1000;

// Define the origin caller
let origin = PolkadotRuntimeOriginCaller.system(
DispatchRawOrigin.Signed(senderAddress)
);

// Construct the id of the receiver
const receiverPublicKey = ss58Decode(receiverAddress)[0];
const idBeneficiary = Binary.fromBytes(receiverPublicKey);

// Define a transaction to transfer assets from Polkadot to Polkadot Asset Hub using a Teleport Transfer
const tx = polkadotApi.tx.XcmPallet.limited_teleport_assets({
dest: XcmVersionedLocation.V3({
parents: 0,
interior: XcmV3Junctions.X1(
XcmV3Junction.Parachain(polkadotAssetHubParaId) // Destination is the Polkadot Asset Hub parachain
),
}),
beneficiary: XcmVersionedLocation.V3({
parents: 0,
interior: XcmV3Junctions.X1(
XcmV3Junction.AccountId32({
// Beneficiary address on Polkadot Asset Hub
network: undefined,
id: idBeneficiary,
})
),
}),
assets: XcmVersionedAssets.V3([
{
id: XcmV3MultiassetAssetId.Concrete({
parents: 0,
interior: XcmV3Junctions.Here(), // Asset from the sender's location
}),
fun: XcmV3MultiassetFungibility.Fungible(amountToTransfer), // Asset amount to transfer
},
]),
fee_asset_item: 0, // Asset used to pay transaction fees, 0 means the same asset as the one being transferred
weight_limit: XcmV3WeightLimit.Unlimited(), // No weight limit on transaction
});

// Execute the dry run call to simulate the transaction
const dryRunResult = await polkadotApi.apis.DryRunApi.dry_run_call(
origin,
tx.decodedCall
);

if (!dryRunResult.success) {
const error = new Error("Dry run failed");
error.cause = dryRunResult.value;
throw error;
}

// Extract the results from the dry run call
const { execution_result, emitted_events, local_xcm, forwarded_xcms } =
dryRunResult.value;

// Extract the XCMs generated by this call
const xcmsToAssetHub = forwarded_xcms.find(
([location, _]) =>
location.type === "V4" &&
location.value.parents === 0 &&
location.value.interior.type === "X1" &&
location.value.interior.value.type === "Parachain" &&
location.value.interior.value.value === polkadotAssetHubParaId
);

// Extract the destination and remote XCM from the XCMs generated
const destination = xcmsToAssetHub[0];
const remoteXcm = xcmsToAssetHub[1][0];

// Execute the query weight runtime call for the local XCM
const localWeightResult =
await polkadotApi.apis.XcmPaymentApi.query_xcm_weight(
dryRunResult.value.local_xcm
);

if (!localWeightResult.success) {
const error = new Error("Query weight for local xcm failed");
error.cause = localWeightResult.value;
throw error;
}

// Convert the weight to fees for the local XCM
const localExecutionFeesResult =
await polkadotApi.apis.XcmPaymentApi.query_weight_to_asset_fee(
localWeightResult.value,
{
type: "V4",
value: { parents: 0, interior: { type: "Here", value: undefined } },
}
);

if (!localExecutionFeesResult.success) {
const error = new Error("Query weight to asset fee for local xcm failed");
error.cause = localExecutionFeesResult.value;
throw error;
}

const localExecutionFees = localExecutionFeesResult.value;

// Execute the query delivery fees runtime call
const deliveryFeesResult =
await polkadotApi.apis.XcmPaymentApi.query_delivery_fees(
destination,
remoteXcm
);

if (!deliveryFeesResult.success) {
const error = new Error("Query delivery fees for remote xcm failed");
error.cause = deliveryFeesResult.value;
throw error;
}

const assets = deliveryFeesResult.value;
const deliveryFees =
(assets.type === "V4" &&
assets.value[0].fun.type === "Fungible" &&
assets.value[0].fun.value.valueOf()) ||
0n;

// Execute the query weight runtime call for the remote XCM
const remoteWeightResult =
await polkadotAssetHubApi.apis.XcmPaymentApi.query_xcm_weight(remoteXcm);

if (!remoteWeightResult.success) {
const error = new Error("Query weight for remote xcm failed");
error.cause = remoteWeightResult.value;
throw error;
}

// Convert the weight to fees for the remote XCM
const remoteExecutionFeesResult =
await polkadotAssetHubApi.apis.XcmPaymentApi.query_weight_to_asset_fee(
remoteWeightResult.value,
{
type: "V4",
value: { parents: 1, interior: { type: "Here", value: undefined } },
}
);

if (!remoteExecutionFeesResult.success) {
const error = new Error(
"Query weight to asset fee for remote xcm failed"
);
error.cause = remoteExecutionFeesResult.value;
throw error;
}

const remoteExecutionFees = remoteExecutionFeesResult.value;

// TODO
const balanceBeforeRelay = await polkadotApi.query.System.Account.getValue(
ss58Encode(accountSigner.publicKey)
);
console.log("Alice balance before relay:", balanceBeforeRelay.data.free);
console.log("Local Execution Fees:", localExecutionFees);
console.log("Delivery Fees:", deliveryFees);
console.log("Remote Execution Fees:", remoteExecutionFees);

// Define a transaction to transfer assets from Polkadot to Polkadot Asset Hub using a Teleport Transfer
const newTx = polkadotApi.tx.XcmPallet.limited_teleport_assets({
dest: XcmVersionedLocation.V3({
parents: 0,
interior: XcmV3Junctions.X1(
XcmV3Junction.Parachain(polkadotAssetHubParaId) // Destination is the Polkadot Asset Hub parachain
),
}),
beneficiary: XcmVersionedLocation.V3({
parents: 0,
interior: XcmV3Junctions.X1(
XcmV3Junction.AccountId32({
// Beneficiary address on Polkadot Asset Hub
network: undefined,
id: idBeneficiary,
})
),
}),
assets: XcmVersionedAssets.V3([
{
id: XcmV3MultiassetAssetId.Concrete({
parents: 0,
interior: XcmV3Junctions.Here(), // Asset from the sender's location
}),
fun: XcmV3MultiassetFungibility.Fungible(
amountToTransfer + remoteExecutionFees
), // Asset amount to transfer
},
]),
fee_asset_item: 0, // Asset used to pay transaction fees
weight_limit: XcmV3WeightLimit.Limited(remoteWeightResult.value),
});

//Execute the dry run call to simulate the transaction
const newDryRunResult = await polkadotApi.apis.DryRunApi.dry_run_call(
origin,
newTx.decodedCall
);

if (!newDryRunResult.success) {
const error = new Error("Dry run failed");
error.cause = newDryRunResult.value;
throw error;
}

let txResult = await newTx.signAndSubmit(accountSigner);
console.log("Transaction result:", txResult);

} catch (error) {
console.error("Error occurred:", error.message);
if (error.cause) {
console.dir(error.cause, { depth: null });
}
} finally {
// Ensure clients are always destroyed
polkadotClient.destroy();
polkadotAssetHubClient.destroy();
}
}

main().catch(console.error);
Loading