Last active
September 9, 2025 00:19
-
-
Save franciscoaguirre/a6dea0c55e81faba65bedf700033a1a2 to your computer and use it in GitHub Desktop.
Interacting with the XCM precompile with Hardhat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| passetHub, | |
| XcmV3MultiassetFungibility, | |
| XcmV3WeightLimit, | |
| XcmV5AssetFilter, | |
| XcmV5Instruction, | |
| XcmV5Junction, | |
| XcmV5Junctions, | |
| XcmV5WildAsset, | |
| XcmVersionedXcm, | |
| } from "@polkadot-api/descriptors"; | |
| import { Binary, FixedSizeBinary, getTypedCodecs } from "polkadot-api"; | |
| const PAS_UNITS = 10_000_000_000n; | |
| const PAS_CENTS = 100_000_000n; | |
| const ACCOUNT = "<polkadot-ss58-account>"; | |
| export async function getSimpleTransfer(): Promise<string> { | |
| const xcm = XcmVersionedXcm.V5([ | |
| XcmV5Instruction.WithdrawAsset([ | |
| { | |
| id: { parents: 1, interior: XcmV5Junctions.Here() }, | |
| fun: XcmV3MultiassetFungibility.Fungible(1n * PAS_UNITS), | |
| }, | |
| ]), | |
| XcmV5Instruction.PayFees({ | |
| asset: { | |
| id: { parents: 1, interior: XcmV5Junctions.Here() }, | |
| fun: XcmV3MultiassetFungibility.Fungible(10n * PAS_CENTS), | |
| }, | |
| }), | |
| XcmV5Instruction.DepositAsset({ | |
| assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1)), | |
| beneficiary: { | |
| parents: 0, | |
| interior: XcmV5Junctions.X1( | |
| XcmV5Junction.AccountId32({ | |
| network: undefined, | |
| id: FixedSizeBinary.fromAccountId32(ACCOUNT), | |
| }) | |
| ), | |
| }, | |
| }), | |
| ]); | |
| const codecs = await getTypedCodecs(passetHub); | |
| const xcmEncoded = codecs.apis.XcmPaymentApi.query_xcm_weight.args.enc([xcm]); | |
| const xcmHex = Binary.fromBytes(xcmEncoded).asHex(); | |
| return xcmHex; | |
| } | |
| export async function getTeleport(paraId: number): Promise<string> { | |
| const xcm = XcmVersionedXcm.V5([ | |
| XcmV5Instruction.WithdrawAsset([ | |
| { | |
| id: { parents: 1, interior: XcmV5Junctions.Here() }, | |
| fun: XcmV3MultiassetFungibility.Fungible(1n * PAS_UNITS), | |
| }, | |
| ]), | |
| XcmV5Instruction.PayFees({ | |
| asset: { | |
| id: { parents: 1, interior: XcmV5Junctions.Here() }, | |
| fun: XcmV3MultiassetFungibility.Fungible(10n * PAS_CENTS), | |
| }, | |
| }), | |
| XcmV5Instruction.InitiateTransfer({ | |
| destination: { | |
| parents: 1, | |
| interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(paraId)), | |
| }, | |
| remote_fees: Enum( | |
| "Teleport", | |
| XcmV5AssetFilter.Definite([ | |
| { | |
| id: { parents: 1, interior: XcmV5Junctions.Here() }, | |
| fun: XcmV3MultiassetFungibility.Fungible(10n * PAS_CENTS), | |
| }, | |
| ]) | |
| ), | |
| preserve_origin: false, | |
| remote_xcm: [ | |
| XcmV5Instruction.DepositAsset({ | |
| assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1)), | |
| beneficiary: { | |
| parents: 0, | |
| interior: XcmV5Junctions.X1( | |
| XcmV5Junction.AccountId32({ | |
| network: undefined, | |
| id: FixedSizeBinary.fromAccountId32(ACCOUNT), | |
| }) | |
| ), | |
| }, | |
| }), | |
| ], | |
| assets: [ | |
| Enum("Teleport", XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1))), // We send everything. | |
| ], | |
| }), | |
| ]); | |
| const codecs = await getTypedCodecs(passetHub); | |
| const xcmEncoded = codecs.apis.XcmPaymentApi.query_xcm_weight.args.enc([xcm]); | |
| const xcmHex = Binary.fromBytes(xcmEncoded).asHex(); | |
| return xcmHex; | |
| } | |
| async function main() { | |
| const hex = await getTeleport(1000); | |
| console.log("Message:", hex); | |
| } | |
| const ASSET_HUB_PARA_ID = 1000; | |
| // Sends PAS to a non-system parachain. | |
| // | |
| // They are routed via AssetHub. | |
| export async function getTransferToPara(paraId: number) { | |
| const xcm = XcmVersionedXcm.V5([ | |
| XcmV5Instruction.WithdrawAsset([ | |
| { | |
| id: { parents: 1, interior: XcmV5Junctions.Here() }, | |
| fun: XcmV3MultiassetFungibility.Fungible(10n * PAS_UNITS), | |
| }, | |
| ]), | |
| XcmV5Instruction.PayFees({ | |
| asset: { | |
| id: { parents: 1, interior: XcmV5Junctions.Here() }, | |
| fun: XcmV3MultiassetFungibility.Fungible(1n * PAS_UNITS), | |
| }, | |
| }), | |
| XcmV5Instruction.InitiateTransfer({ | |
| destination: { | |
| parents: 1, | |
| interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(ASSET_HUB_PARA_ID)), | |
| }, | |
| remote_fees: Enum( | |
| "Teleport", | |
| XcmV5AssetFilter.Definite([ | |
| { | |
| id: { parents: 1, interior: XcmV5Junctions.Here() }, | |
| fun: XcmV3MultiassetFungibility.Fungible(1n * PAS_UNITS), | |
| }, | |
| ]) | |
| ), | |
| preserve_origin: false, | |
| remote_xcm: [ | |
| XcmV5Instruction.DepositReserveAsset({ | |
| assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1)), | |
| dest: { parents: 1, interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(paraId)) }, | |
| xcm: [ | |
| XcmV5Instruction.BuyExecution({ | |
| fees: { | |
| id: { parents: 1, interior: XcmV5Junctions.Here() }, | |
| fun: XcmV3MultiassetFungibility.Fungible(10n * PAS_CENTS), | |
| }, | |
| weight_limit: XcmV3WeightLimit.Unlimited(), | |
| }), | |
| XcmV5Instruction.DepositAsset({ | |
| assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1)), | |
| beneficiary: { | |
| parents: 0, | |
| interior: XcmV5Junctions.X1( | |
| XcmV5Junction.AccountId32({ | |
| network: undefined, | |
| id: FixedSizeBinary.fromAccountId32(ACCOUNT), | |
| }) | |
| ), | |
| }, | |
| }), | |
| ], | |
| }), | |
| ], | |
| assets: [ | |
| Enum("Teleport", XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1))), // We send everything. | |
| ], | |
| }), | |
| ]); | |
| const codecs = await getTypedCodecs(passetHub); | |
| const xcmEncoded = codecs.apis.XcmPaymentApi.query_xcm_weight.args.enc([xcm]); | |
| const xcmHex = Binary.fromBytes(xcmEncoded).asHex(); | |
| return xcmHex; | |
| } | |
| main(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const hre = require("hardhat"); | |
| const { getSimpleTransfer, getTeleport, getTransferToPara } = require("./generate-xcm.js"); | |
| async function main() { | |
| const precompileAddress = "0x00000000000000000000000000000000000A0000"; | |
| const xcm = await hre.ethers.getContractAt("IXcm", precompileAddress); | |
| const message = await getTransferToPara(2034); | |
| const weight = await xcm.weighMessage(message); | |
| console.dir({ weight }); | |
| const tx = await xcm.execute(message, [weight[0], weight[1]]); | |
| const receipt = await tx.wait(); | |
| console.dir({ receipt }); | |
| } | |
| main() | |
| .then(() => process.exit()) | |
| .catch((error) => { | |
| console.error(error); | |
| process.exit(1); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
hi @franciscoaguirre, Thanks for your example on how to use xcm-precompile. Although the transaction was successful, the XCM execution isn't working as expected
I'm getting an error:
AssetsTrappedExample:
getSimpleTransferWith my understanding,
getSimpleTransferis teleportnative tokenfrom thePasset Hubto theRelay ChainI have a few questions:
Since we're using an EVM account to sign the execute transaction, which account—the EVM one or a Substrate one—should the WithdrawAsset instruction be based on?
Are the EVM account and the Substrate account derived from the same private key? If not, what's the formula for mapping between the two?
Hopefully, you can response me soon