From 9ed7bbc532f561a04476aa63ffb351b7ec539e1a Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 16 Sep 2025 00:59:08 +0800 Subject: [PATCH 1/6] Create XCM Observability Guide (#873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulling in external contributions for formatting review. * Copy XCM Observability * Apply the new format * Update Induction * Update Prerequisites * Update Where to Go Next * Add Understanding the Basics * Add scripts * Add result * Update Failure Event Handling * Add forwardIdFor * Update Workaround for Older Runtimes * Update Where to Go Next * Update Prerequisites * Remove Where to Go Next * Reorder the content * Change to Hydration * Update script * Update script * Check if PolkadotXcm.Sent exists * Add Define a Scenario: Multi-Hop XCM with Manual `SetTopic` * Add multi-hop-with-set-topic.ts * Add results * Update the order * Check grammar * Update Prerequisites * Update scripts * Add Set Up Your Project * Update script name * Update header * Update limited-reserve-transfer-assets.ts * Update scripts * Update deposit-reserve-asset-with-set-topic.ts * Update initiate-reserve-withdraw-with-set-topic.ts * Add forwarded-xcm-remote-topic.html * Update XCM Transfer with Manual `SetTopic` * Fix typos * Add Multi-hop XCM Transfer with Manual `SetTopic` * Update fully * Update partially * Update flowchart * Update flowchart * Update flowchart * Break into two lines * Break into two lines * Use TD * Update flowchart * Update limited_reserve_transfer_assets * Update DepositReserveAsset * Update InitiateReserveWithdraw * Update scripts * Update MAX_RETRIES * Fix fmt * Fix fmt * Update Prerequisites * Update to Polkadot Hub * Update to Polkadot Hub * Add Observability Features * Update wordings * Update Observability Features * Update Automatic vs Manual `SetTopic` * Update Summary * Fix output files * Change the exchange rate * Fix conflicts * Fix conflicts * Revert changes * Revert changes * Revert changes * Revert changes * Revert changes * Revert changes * Revert changes * Change Setting Up Your Workspace * Add links to Rust docs * Update limited-reserve-transfer-assets.ts * Update deposit-reserve-asset-with-set-topic.ts * Update initiate-reserve-withdraw-with-set-topic.ts * Fix imports * Fix typos * Add Troubleshooting on Running Scripts * Revert unrelated changes * Revert unrelated changes * Use 1.6.0 or later * Fix merge conflicts * Update tutorials/interoperability/xcm-observability.md Co-authored-by: NicolΓ‘s Hussein <80422357+nhussein11@users.noreply.github.com> * Add XCM Observability in Action * Update XCM Observability in Action * Update XCM Observability * Fix execution-with-error.html * Move Workaround for Older Runtimes * Use blake2b instead of blake2AsU8a * Use blake2b instead of blake2AsU8a * Add WithUniqueTopic * Add on system chains * Add on system chains --------- Co-authored-by: NicolΓ‘s Hussein <80422357+nhussein11@users.noreply.github.com> --- .../execution-with-error.html | 19 + .../xcm-observability/forward-id-for.ts | 32 + ...t-reserve-asset-with-set-topic-result.html | 10 + .../deposit-reserve-asset-with-set-topic.ts | 236 +++ .../forwarded-xcm-custom-topic.html | 25 + .../forwarded-xcm-remote-topic.html | 46 + .../forwarded-xcm.html | 25 + ...eserve-withdraw-with-set-topic-result.html | 13 + ...nitiate-reserve-withdraw-with-set-topic.ts | 291 ++++ ...imited-reserve-transfer-assets-result.html | 9 + .../limited-reserve-transfer-assets.ts | 186 +++ develop/interoperability/xcm-observability.md | 149 ++ llms-full.txt | 1285 +++++++++++++++++ llms.txt | 4 +- tutorials/interoperability/.nav.yml | 1 + .../xcm-observability-in-action.md | 245 ++++ 16 files changed, 2575 insertions(+), 1 deletion(-) create mode 100644 .snippets/code/develop/interoperability/xcm-observability/execution-with-error.html create mode 100644 .snippets/code/develop/interoperability/xcm-observability/forward-id-for.ts create mode 100644 .snippets/code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic-result.html create mode 100644 .snippets/code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic.ts create mode 100644 .snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-custom-topic.html create mode 100644 .snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-remote-topic.html create mode 100644 .snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm.html create mode 100644 .snippets/code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic-result.html create mode 100644 .snippets/code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic.ts create mode 100644 .snippets/code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets-result.html create mode 100644 .snippets/code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets.ts create mode 100644 develop/interoperability/xcm-observability.md create mode 100644 tutorials/interoperability/xcm-observability-in-action.md diff --git a/.snippets/code/develop/interoperability/xcm-observability/execution-with-error.html b/.snippets/code/develop/interoperability/xcm-observability/execution-with-error.html new file mode 100644 index 000000000..e00aef25b --- /dev/null +++ b/.snippets/code/develop/interoperability/xcm-observability/execution-with-error.html @@ -0,0 +1,19 @@ +
+
+"error": {
+  "type": "Module",
+  "value": {
+    "type": "PolkadotXcm",
+    "value": {
+      "type": "LocalExecutionIncompleteWithError",
+      "value": {
+        "index": 0,
+        "error": {
+          "type": "FailedToTransactAsset"
+        }
+      }
+    }
+  }
+}
+  
+
diff --git a/.snippets/code/develop/interoperability/xcm-observability/forward-id-for.ts b/.snippets/code/develop/interoperability/xcm-observability/forward-id-for.ts new file mode 100644 index 000000000..7784e3e15 --- /dev/null +++ b/.snippets/code/develop/interoperability/xcm-observability/forward-id-for.ts @@ -0,0 +1,32 @@ +import {blake2b} from "@noble/hashes/blake2"; +import {fromHex, mergeUint8, toHex} from "@polkadot-api/utils"; +import {Binary} from "polkadot-api"; + +function forwardIdFor(originalMessageId: string): string { + // Decode the hex original_id into bytes + const messageIdBytes = fromHex(originalMessageId); + + // Create prefixed input: b"forward_id_for" + original_id + const prefix = Binary.fromText("forward_id_for").asBytes(); + const input = mergeUint8([prefix, messageIdBytes]); + + // Hash it using blake2b with 32-byte output + const forwardedIdBytes = blake2b(input, {dkLen: 32}); + // Convert to hex + return toHex(forwardedIdBytes); +} + +// Example: Forwarded ID from an original_id +const originalMessageId = "0x5c082b4750ee8c34986eb22ce6e345bad2360f3682cda3e99de94b0d9970cb3e"; + +// Create the forwarded ID +const forwardedIdHex = forwardIdFor(originalMessageId); + +console.log("πŸ”„ Forwarded ID:", forwardedIdHex); + +const expectedForwardedId = "0xb3ae32fd2e2f798e8215865a8950d19df8330843608d4ee44e9f86849029724a"; +if (forwardedIdHex === expectedForwardedId) { + console.log("βœ… Forwarded ID matches expected value."); +} else { + console.error("❌ Forwarded ID does not match expected value."); +} \ No newline at end of file diff --git a/.snippets/code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic-result.html b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic-result.html new file mode 100644 index 000000000..99199a40e --- /dev/null +++ b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic-result.html @@ -0,0 +1,10 @@ +
+ npx tsx limited-reserve-transfer-assets.ts + βœ… Local dry run successful. + πŸ“¦ Finalised on Polkadot Hub in block #9471830: 0x98bd858739b3b5dd558def60cbd85d5e7fb2f4e33b0c00e1895e316541d727d9 + πŸ“£ Last message sent on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Sent Message ID on Polkadot Hub matched. + πŸ“¦ Finalised on Hydration in block #8749233: 0xe1413c5126698d7189d6f55a38e62d07ea4915078c2b1f3914d70f670e79e162 + πŸ“£ Last message processed on Hydration: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Processed Message ID on Hydration matched. +
diff --git a/.snippets/code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic.ts b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic.ts new file mode 100644 index 000000000..e16b5d9b6 --- /dev/null +++ b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic.ts @@ -0,0 +1,236 @@ +import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; +import {withPolkadotSdkCompat} from "polkadot-api/polkadot-sdk-compat"; +import {getPolkadotSigner} from "polkadot-api/signer"; +import {getWsProvider} from "polkadot-api/ws-provider/web"; +import { + assetHub, + hydration, + XcmV3MultiassetFungibility, + XcmV3WeightLimit, + XcmV5AssetFilter, + XcmV5Instruction, + XcmV5Junction, + XcmV5Junctions, + XcmV5WildAsset, + XcmVersionedXcm, +} from "@polkadot-api/descriptors"; +import {sr25519CreateDerive} from "@polkadot-labs/hdkd"; +import { + DEV_PHRASE, + entropyToMiniSecret, + mnemonicToEntropy, + ss58Address, +} from "@polkadot-labs/hdkd-helpers"; + +const XCM_VERSION = 5; +const MAX_RETRIES = 8; // Number of attempts to wait for block finalisation + +const toHuman = (_key: any, value: any) => { + if (typeof value === "bigint") { + return Number(value); + } + + if (value && typeof value === "object" && typeof value.asHex === "function") { + return value.asHex(); + } + + return value; +}; + +async function assertProcessedMessageId( + client: PolkadotClient, + api: any, + name: String, + blockBefore: BlockInfo, + expectedMessageId: String, +) { + let processedMessageId = undefined; + for (let i = 0; i < MAX_RETRIES; i++) { + const blockAfter = await client.getFinalizedBlock(); + if (blockAfter.number == blockBefore.number) { + const waiting = 1_000 * (2 ** i); + console.log(`⏳ Waiting ${waiting / 1_000}s for ${name} block to be finalised (${i + 1}/${MAX_RETRIES})...`); + await new Promise((resolve) => setTimeout(resolve, waiting)); + continue; + } + + console.log(`πŸ“¦ Finalised on ${name} in block #${blockAfter.number}: ${blockAfter.hash}`); + const processedEvents = await api.event.MessageQueue.Processed.pull(); + const processingFailedEvents = await api.event.MessageQueue.ProcessingFailed.pull(); + if (processedEvents.length > 0) { + processedMessageId = processedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message processed on ${name}: ${processedMessageId}`); + break; + } else if (processingFailedEvents.length > 0) { + processedMessageId = processingFailedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message ProcessingFailed on ${name}: ${processedMessageId}`); + break; + } else { + console.log(`πŸ“£ No Processed events on ${name} found.`); + blockBefore = blockAfter; // Update the block before to the latest one + } + } + + if (processedMessageId === expectedMessageId) { + console.log(`βœ… Processed Message ID on ${name} matched.`); + } else if (processedMessageId === undefined) { + console.error(`❌ Processed Message ID on ${name} is undefined. Try increasing MAX_RETRIES to wait for block finalisation.`); + } else { + console.error(`❌ Processed Message ID [${processedMessageId}] on ${name} doesn't match expected Message ID [${expectedMessageId}].`); + } +} + +async function main() { + const para1Name = "Polkadot Hub"; + const para1Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8000")), + ); + const para1Api = para1Client.getTypedApi(assetHub); + + const para2Name = "Hydration"; + const para2Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8001")), + ); + const para2Api = para2Client.getTypedApi(hydration); + + const entropy = mnemonicToEntropy(DEV_PHRASE); + const miniSecret = entropyToMiniSecret(entropy); + const derive = sr25519CreateDerive(miniSecret); + const alice = derive("//Alice"); + const alicePublicKey = alice.publicKey; + const aliceSigner = getPolkadotSigner(alicePublicKey, "Sr25519", alice.sign); + const aliceAddress = ss58Address(alicePublicKey); + + const origin = Enum("system", Enum("Signed", aliceAddress)); + const beneficiary = { + parents: 0, + interior: XcmV5Junctions.X1(XcmV5Junction.AccountId32({ + id: Binary.fromHex("0x9818ff3c27d256631065ecabf0c50e02551e5c5342b8669486c1e566fcbf847f") + })), + } + const expectedMessageId = "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2"; + + const message = XcmVersionedXcm.V5([ + XcmV5Instruction.WithdrawAsset([ + { + id: { + parents: 1, + interior: XcmV5Junctions.Here(), + }, + fun: XcmV3MultiassetFungibility.Fungible(1_000_000_000n), + }, + ]), + + XcmV5Instruction.ClearOrigin(), + + XcmV5Instruction.BuyExecution({ + fees: { + id: { + parents: 1, + interior: XcmV5Junctions.Here(), + }, + fun: XcmV3MultiassetFungibility.Fungible(500_000_000n), + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + + XcmV5Instruction.DepositReserveAsset({ + assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.All()), + dest: { + parents: 1, + interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(2034)), + }, + xcm: [ + XcmV5Instruction.BuyExecution({ + fees: { + id: { + parents: 1, + interior: XcmV5Junctions.Here(), + }, + fun: XcmV3MultiassetFungibility.Fungible(500_000_000n), + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + + XcmV5Instruction.DepositAsset({ + assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.All()), + beneficiary, + }), + ], + }), + + XcmV5Instruction.SetTopic(Binary.fromHex(expectedMessageId)), + ]); + + const weight: any = await para1Api.apis.XcmPaymentApi.query_xcm_weight(message); + if (weight.success !== true) { + console.error("❌ Failed to query XCM weight:", weight.error); + para1Client.destroy(); + return; + } + + const tx: any = para1Api.tx.PolkadotXcm.execute({ + message, + max_weight: weight.value, + }); + const decodedCall: any = tx.decodedCall; + console.log("πŸ‘€ Executing XCM:", JSON.stringify(decodedCall, toHuman, 2)); + + try { + const dryRunResult: any = await para1Api.apis.DryRunApi.dry_run_call( + origin, + decodedCall, + XCM_VERSION, + ); + console.log("πŸ“¦ Dry run result:", JSON.stringify(dryRunResult.value, toHuman, 2)); + + const executionResult = dryRunResult.value.execution_result; + if (!dryRunResult.success || !executionResult.success) { + console.error("❌ Local dry run failed!"); + } else { + console.log("βœ… Local dry run successful."); + + const emittedEvents: [any] = dryRunResult.value.emitted_events; + const polkadotXcmSentEvent = emittedEvents.find(event => + event.type === "PolkadotXcm" && event.value.type === "Sent" + ); + if (polkadotXcmSentEvent === undefined) { + console.log(`⚠️ PolkadotXcm.Sent is only available in runtimes built from stable2503-5 or later.`); + } else { + let para2BlockBefore = await para2Client.getFinalizedBlock(); + const extrinsic = await tx.signAndSubmit(aliceSigner); + const para1Block = extrinsic.block; + console.log(`πŸ“¦ Finalised on ${para1Name} in block #${para1Block.number}: ${para1Block.hash}`); + + if (!extrinsic.ok) { + const dispatchError = extrinsic.dispatchError; + if (dispatchError.type === "Module") { + const modErr: any = dispatchError.value; + console.error(`❌ Dispatch error in module: ${modErr.type} β†’ ${modErr.value?.type}`); + } else { + console.error("❌ Dispatch error:", JSON.stringify(dispatchError, toHuman, 2)); + } + } + + const sentEvents: any = await para1Api.event.PolkadotXcm.Sent.pull(); + if (sentEvents.length > 0) { + const sentMessageId = sentEvents[0].payload.message_id.asHex(); + console.log(`πŸ“£ Last message sent on ${para1Name}: ${sentMessageId}`); + if (sentMessageId === expectedMessageId) { + console.log(`βœ… Sent Message ID on ${para1Name} matched.`); + } else { + console.error(`❌ Sent Message ID [${sentMessageId}] on ${para1Name} doesn't match expected Message ID [${expectedMessageId}].`); + } + await assertProcessedMessageId(para2Client, para2Api, para2Name, para2BlockBefore, expectedMessageId); + } else { + console.log(`πŸ“£ No Sent events on ${para1Name} found.`); + } + } + } + } finally { + para1Client.destroy(); + para2Client.destroy(); + } +} + +main().catch(console.error); \ No newline at end of file diff --git a/.snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-custom-topic.html b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-custom-topic.html new file mode 100644 index 000000000..63dd34b02 --- /dev/null +++ b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-custom-topic.html @@ -0,0 +1,25 @@ +
+
+[
+  {
+    "type": "ReserveAssetDeposited",
+    "value": [...]
+  },
+  {
+    "type": "ClearOrigin"
+  },
+  {
+    "type": "BuyExecution",
+    "value": {...}
+  },
+  {
+    "type": "DepositAsset",
+    "value": {...}
+  },
+  {
+    "type": "SetTopic",
+    "value": "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2"
+  }
+]
+  
+
diff --git a/.snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-remote-topic.html b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-remote-topic.html new file mode 100644 index 000000000..74afcc8ad --- /dev/null +++ b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-remote-topic.html @@ -0,0 +1,46 @@ +
+
+[
+  {
+    "type": "ReserveAssetDeposited",
+    "value": [...]
+  },
+  {
+    "type": "ClearOrigin"
+  },
+  {
+    "type": "BuyExecution",
+    "value": {...}
+  },
+  {
+    "type": "ExchangeAsset",
+    "value": {...}
+  },
+  {
+    "type": "InitiateReserveWithdraw",
+    "value": {
+      "assets": {...},
+      "reserve": {...},
+      "xcm": [
+        {
+          "type": "BuyExecution",
+          "value": {...}
+        },
+        {
+          "type": "DepositAsset",
+          "value": {...}
+        },
+        {
+          "type": "SetTopic",
+          "value": "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2"
+        }
+      ]
+    }
+  },
+  {
+    "type": "SetTopic",
+    "value": "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2"
+  }
+]
+  
+
diff --git a/.snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm.html b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm.html new file mode 100644 index 000000000..8155efd30 --- /dev/null +++ b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm.html @@ -0,0 +1,25 @@ +
+
+[
+  {
+    "type": "ReserveAssetDeposited",
+    "value": [...]
+  },
+  {
+    "type": "ClearOrigin"
+  },
+  {
+    "type": "BuyExecution",
+    "value": {...}
+  },
+  {
+    "type": "DepositAsset",
+    "value": {...}
+  },
+  {
+    "type": "SetTopic",
+    "value": "0x3b5650b78230aebb8f2573d2c5e8356494ab01e39e716087c177bf871dce70b9"
+  }
+]
+  
+
diff --git a/.snippets/code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic-result.html b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic-result.html new file mode 100644 index 000000000..00ec47faf --- /dev/null +++ b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic-result.html @@ -0,0 +1,13 @@ +
+ npx tsx initiate-reserve-withdraw-with-set-topic.ts + βœ… Local dry run successful. + πŸ“¦ Finalised on Polkadot Hub in block #9471831: 0x2620f7e29765fc953263b7835711011616702c9d82ef5306fe3ef4196cb75cab + πŸ“£ Last message sent on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Sent Message ID on Polkadot Hub matched. + πŸ“¦ Finalised on Hydration in block #8749235: 0xafe7f6149b1773a8d3d229040cda414aafd64baaeffa37fb4a5b2a542308b2d6 + πŸ“£ Last message processed on Hydration: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Processed Message ID on Hydration matched. + πŸ“¦ Finalised on Polkadot Hub in block #9471832: 0x7c150b69e3562694f0573e4fee73dfb86f3ab71b808679a1777586ff24643e9a + πŸ“£ Last message processed on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Processed Message ID on Polkadot Hub matched. +
diff --git a/.snippets/code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic.ts b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic.ts new file mode 100644 index 000000000..06dd02a95 --- /dev/null +++ b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic.ts @@ -0,0 +1,291 @@ +import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; +import {withPolkadotSdkCompat} from "polkadot-api/polkadot-sdk-compat"; +import {getPolkadotSigner} from "polkadot-api/signer"; +import {getWsProvider} from "polkadot-api/ws-provider/web"; +import { + assetHub, + hydration, + XcmV2MultiassetWildFungibility, + XcmV3MultiassetFungibility, + XcmV3WeightLimit, + XcmV5AssetFilter, + XcmV5Instruction, + XcmV5Junction, + XcmV5Junctions, + XcmV5WildAsset, + XcmVersionedXcm, +} from "@polkadot-api/descriptors"; +import {sr25519CreateDerive} from "@polkadot-labs/hdkd"; +import { + DEV_PHRASE, + entropyToMiniSecret, + mnemonicToEntropy, + ss58Address, +} from "@polkadot-labs/hdkd-helpers"; + +const XCM_VERSION = 5; +const MAX_RETRIES = 8; // Number of attempts to wait for block finalisation + +const toHuman = (_key: any, value: any) => { + if (typeof value === "bigint") { + return Number(value); + } + + if (value && typeof value === "object" && typeof value.asHex === "function") { + return value.asHex(); + } + + return value; +}; + +async function assertProcessedMessageId( + client: PolkadotClient, + api: any, + name: String, + blockBefore: BlockInfo, + expectedMessageId: String, +) { + let processedMessageId = undefined; + for (let i = 0; i < MAX_RETRIES; i++) { + const blockAfter = await client.getFinalizedBlock(); + if (blockAfter.number == blockBefore.number) { + const waiting = 1_000 * (2 ** i); + console.log(`⏳ Waiting ${waiting / 1_000}s for ${name} block to be finalised (${i + 1}/${MAX_RETRIES})...`); + await new Promise((resolve) => setTimeout(resolve, waiting)); + continue; + } + + console.log(`πŸ“¦ Finalised on ${name} in block #${blockAfter.number}: ${blockAfter.hash}`); + const processedEvents = await api.event.MessageQueue.Processed.pull(); + const processingFailedEvents = await api.event.MessageQueue.ProcessingFailed.pull(); + if (processedEvents.length > 0) { + processedMessageId = processedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message processed on ${name}: ${processedMessageId}`); + break; + } else if (processingFailedEvents.length > 0) { + processedMessageId = processingFailedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message ProcessingFailed on ${name}: ${processedMessageId}`); + break; + } else { + console.log(`πŸ“£ No Processed events on ${name} found.`); + blockBefore = blockAfter; // Update the block before to the latest one + } + } + + if (processedMessageId === expectedMessageId) { + console.log(`βœ… Processed Message ID on ${name} matched.`); + } else if (processedMessageId === undefined) { + console.error(`❌ Processed Message ID on ${name} is undefined. Try increasing MAX_RETRIES to wait for block finalisation.`); + } else { + console.error(`❌ Processed Message ID [${processedMessageId}] on ${name} doesn't match expected Message ID [${expectedMessageId}].`); + } +} + +async function main() { + const para1Name = "Polkadot Hub"; + const para1Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8000")), + ); + const para1Api = para1Client.getTypedApi(assetHub); + + const para2Name = "Hydration"; + const para2Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8001")), + ); + const para2Api = para2Client.getTypedApi(hydration); + + const entropy = mnemonicToEntropy(DEV_PHRASE); + const miniSecret = entropyToMiniSecret(entropy); + const derive = sr25519CreateDerive(miniSecret); + const alice = derive("//Alice"); + const alicePublicKey = alice.publicKey; + const aliceSigner = getPolkadotSigner(alicePublicKey, "Sr25519", alice.sign); + const aliceAddress = ss58Address(alicePublicKey); + + const origin = Enum("system", Enum("Signed", aliceAddress)); + const beneficiary = { + parents: 0, + interior: XcmV5Junctions.X1(XcmV5Junction.AccountId32({ + id: Binary.fromHex("0x9818ff3c27d256631065ecabf0c50e02551e5c5342b8669486c1e566fcbf847f") + })), + } + const tokenId = XcmV5Junction.GeneralIndex(1337n); // Change to another token if FailedToTransactAsset("Funds are unavailable") + const assetId = { + parents: 0, + interior: XcmV5Junctions.X2([ + XcmV5Junction.PalletInstance(50), + tokenId, + ]), + }; + const giveId = { + parents: 1, + interior: XcmV5Junctions.X3([ + XcmV5Junction.Parachain(1000), + XcmV5Junction.PalletInstance(50), + tokenId, + ]), + }; + const giveFun = XcmV3MultiassetFungibility.Fungible(1_000_000n); + const dest = { + parents: 1, + interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(2034)), + }; + const wantId = { + parents: 1, + interior: XcmV5Junctions.Here(), + }; + const wantFun = XcmV3MultiassetFungibility.Fungible(2_000_000_000n); // Adjust the exchange rate if xcm_error is NoDeal + const expectedMessageId = "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2"; + + const message = XcmVersionedXcm.V5([ + XcmV5Instruction.WithdrawAsset([{ + id: assetId, + fun: giveFun, + }]), + + XcmV5Instruction.SetFeesMode({jit_withdraw: true}), + + XcmV5Instruction.DepositReserveAsset({ + assets: XcmV5AssetFilter.Wild( + XcmV5WildAsset.AllOf({ + id: assetId, + fun: XcmV2MultiassetWildFungibility.Fungible(), + })), + dest, + xcm: [ + XcmV5Instruction.BuyExecution({ + fees: { + id: giveId, + fun: giveFun, + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + + XcmV5Instruction.ExchangeAsset({ + give: XcmV5AssetFilter.Wild( + XcmV5WildAsset.AllOf({ + id: giveId, + fun: XcmV2MultiassetWildFungibility.Fungible(), + }), + ), + want: [{ + id: wantId, + fun: wantFun, + }], + maximal: false, + }), + + XcmV5Instruction.InitiateReserveWithdraw({ + assets: XcmV5AssetFilter.Wild( + XcmV5WildAsset.AllOf({ + id: wantId, + fun: XcmV2MultiassetWildFungibility.Fungible(), + }), + ), + reserve: { + parents: 1, + interior: XcmV5Junctions.X1( + XcmV5Junction.Parachain(1000), + ), + }, + xcm: [ + XcmV5Instruction.BuyExecution({ + fees: { + id: wantId, + fun: wantFun, + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + + XcmV5Instruction.DepositAsset({ + assets: XcmV5AssetFilter.Wild( + XcmV5WildAsset.AllOf({ + id: wantId, + fun: XcmV2MultiassetWildFungibility.Fungible(), + }), + ), + beneficiary, + }), + + XcmV5Instruction.SetTopic(Binary.fromHex(expectedMessageId)), // Ensure the same topic is also set on remote XCM calls + ], + }), + ], + }), + + XcmV5Instruction.SetTopic(Binary.fromHex(expectedMessageId)), + ]); + + const weight: any = await para1Api.apis.XcmPaymentApi.query_xcm_weight(message); + if (weight.success !== true) { + console.error("❌ Failed to query XCM weight:", weight.error); + para1Client.destroy(); + return; + } + + const tx: any = para1Api.tx.PolkadotXcm.execute({ + message, + max_weight: weight.value, + }); + const decodedCall: any = tx.decodedCall; + console.log("πŸ‘€ Executing XCM:", JSON.stringify(decodedCall, toHuman, 2)); + + try { + const dryRunResult: any = await para1Api.apis.DryRunApi.dry_run_call( + origin, + decodedCall, + XCM_VERSION, + ); + console.log("πŸ“¦ Dry run result:", JSON.stringify(dryRunResult.value, toHuman, 2)); + + const executionResult = dryRunResult.value.execution_result; + if (!dryRunResult.success || !executionResult.success) { + console.error("❌ Local dry run failed!"); + } else { + console.log("βœ… Local dry run successful."); + + const emittedEvents: [any] = dryRunResult.value.emitted_events; + const polkadotXcmSentEvent = emittedEvents.find(event => + event.type === "PolkadotXcm" && event.value.type === "Sent" + ); + if (polkadotXcmSentEvent === undefined) { + console.log(`⚠️ PolkadotXcm.Sent is only available in runtimes built from stable2503-5 or later.`); + } else { + let para2BlockBefore = await para2Client.getFinalizedBlock(); + const extrinsic = await tx.signAndSubmit(aliceSigner); + const para1Block = extrinsic.block; + console.log(`πŸ“¦ Finalised on ${para1Name} in block #${para1Block.number}: ${para1Block.hash}`); + + if (!extrinsic.ok) { + const dispatchError = extrinsic.dispatchError; + if (dispatchError.type === "Module") { + const modErr: any = dispatchError.value; + console.error(`❌ Dispatch error in module: ${modErr.type} β†’ ${modErr.value?.type}`); + } else { + console.error("❌ Dispatch error:", JSON.stringify(dispatchError, toHuman, 2)); + } + } + + const sentEvents: any = await para1Api.event.PolkadotXcm.Sent.pull(); + if (sentEvents.length > 0) { + const sentMessageId = sentEvents[0].payload.message_id.asHex(); + console.log(`πŸ“£ Last message sent on ${para1Name}: ${sentMessageId}`); + if (sentMessageId === expectedMessageId) { + console.log(`βœ… Sent Message ID on ${para1Name} matched.`); + } else { + console.error(`❌ Sent Message ID [${sentMessageId}] on ${para1Name} doesn't match expected Message ID [${expectedMessageId}].`); + } + await assertProcessedMessageId(para2Client, para2Api, para2Name, para2BlockBefore, expectedMessageId); + await assertProcessedMessageId(para1Client, para1Api, para1Name, para1Block, expectedMessageId); + } else { + console.log(`πŸ“£ No Sent events on ${para1Name} found.`); + } + } + } + } finally { + para1Client.destroy(); + para2Client.destroy(); + } +} + +main().catch(console.error); \ No newline at end of file diff --git a/.snippets/code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets-result.html b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets-result.html new file mode 100644 index 000000000..ccd27729f --- /dev/null +++ b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets-result.html @@ -0,0 +1,9 @@ +
+ npx tsx limited-reserve-transfer-assets.ts + βœ… Local dry run successful. + πŸ“¦ Finalised on Polkadot Hub in block #9477291: 0xf54cecc017762c714bbdf3e82d72ed90886257ca17d32ec6dc8ea20e28110af8 + πŸ“£ Last message sent on Polkadot Hub: 0x20432393771dc049cea4900565a936d169b8ebdd64efa351890766df918615a4 + πŸ“¦ Finalised on Hydration in block #8761211: 0xa4c493ba9328f38174aa7a9ade0779654839e9d3c83b2bafc60d4e5b7de6a00f + πŸ“£ Last message processed on Hydration: 0x20432393771dc049cea4900565a936d169b8ebdd64efa351890766df918615a4 + βœ… Processed Message ID on Hydration matched. +
diff --git a/.snippets/code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets.ts b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets.ts new file mode 100644 index 000000000..c3952a977 --- /dev/null +++ b/.snippets/code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets.ts @@ -0,0 +1,186 @@ +import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; +import {withPolkadotSdkCompat} from "polkadot-api/polkadot-sdk-compat"; +import {getPolkadotSigner} from "polkadot-api/signer"; +import {getWsProvider} from "polkadot-api/ws-provider/web"; +import { + assetHub, + hydration, + XcmV3MultiassetFungibility, + XcmV3WeightLimit, + XcmV5Junction, + XcmV5Junctions, + XcmVersionedAssets, + XcmVersionedLocation, +} from "@polkadot-api/descriptors"; +import {sr25519CreateDerive} from "@polkadot-labs/hdkd"; +import { + DEV_PHRASE, + entropyToMiniSecret, + mnemonicToEntropy, + ss58Address, +} from "@polkadot-labs/hdkd-helpers"; + +const XCM_VERSION = 5; +const MAX_RETRIES = 8; // Number of attempts to wait for block finalisation + +const toHuman = (_key: any, value: any) => { + if (typeof value === "bigint") { + return Number(value); + } + + if (value && typeof value === "object" && typeof value.asHex === "function") { + return value.asHex(); + } + + return value; +}; + +async function assertProcessedMessageId( + client: PolkadotClient, + api: any, + name: String, + blockBefore: BlockInfo, + expectedMessageId: String, +) { + let processedMessageId = undefined; + for (let i = 0; i < MAX_RETRIES; i++) { + const blockAfter = await client.getFinalizedBlock(); + if (blockAfter.number == blockBefore.number) { + const waiting = 1_000 * (2 ** i); + console.log(`⏳ Waiting ${waiting / 1_000}s for ${name} block to be finalised (${i + 1}/${MAX_RETRIES})...`); + await new Promise((resolve) => setTimeout(resolve, waiting)); + continue; + } + + console.log(`πŸ“¦ Finalised on ${name} in block #${blockAfter.number}: ${blockAfter.hash}`); + const processedEvents = await api.event.MessageQueue.Processed.pull(); + const processingFailedEvents = await api.event.MessageQueue.ProcessingFailed.pull(); + if (processedEvents.length > 0) { + processedMessageId = processedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message processed on ${name}: ${processedMessageId}`); + break; + } else if (processingFailedEvents.length > 0) { + processedMessageId = processingFailedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message ProcessingFailed on ${name}: ${processedMessageId}`); + break; + } else { + console.log(`πŸ“£ No Processed events on ${name} found.`); + blockBefore = blockAfter; // Update the block before to the latest one + } + } + + if (processedMessageId === expectedMessageId) { + console.log(`βœ… Processed Message ID on ${name} matched.`); + } else if (processedMessageId === undefined) { + console.error(`❌ Processed Message ID on ${name} is undefined. Try increasing MAX_RETRIES to wait for block finalisation.`); + } else { + console.error(`❌ Processed Message ID [${processedMessageId}] on ${name} doesn't match expected Message ID [${expectedMessageId}].`); + } +} + +async function main() { + const para1Name = "Polkadot Hub"; + const para1Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8000")), + ); + const para1Api = para1Client.getTypedApi(assetHub); + + const para2Name = "Hydration"; + const para2Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8001")), + ); + const para2Api = para2Client.getTypedApi(hydration); + + const entropy = mnemonicToEntropy(DEV_PHRASE); + const miniSecret = entropyToMiniSecret(entropy); + const derive = sr25519CreateDerive(miniSecret); + const alice = derive("//Alice"); + const alicePublicKey = alice.publicKey; + const aliceSigner = getPolkadotSigner(alicePublicKey, "Sr25519", alice.sign); + const aliceAddress = ss58Address(alicePublicKey); + + const origin = Enum("system", Enum("Signed", aliceAddress)); + const beneficiary = { + parents: 0, + interior: XcmV5Junctions.X1(XcmV5Junction.AccountId32({ + id: Binary.fromHex("0x9818ff3c27d256631065ecabf0c50e02551e5c5342b8669486c1e566fcbf847f") + })), + } + + const tx: any = para1Api.tx.PolkadotXcm.limited_reserve_transfer_assets({ + dest: XcmVersionedLocation.V5({ + parents: 1, + interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(2034)), + }), + beneficiary: XcmVersionedLocation.V5(beneficiary), + assets: XcmVersionedAssets.V5([ + { + id: { + parents: 0, + interior: XcmV5Junctions.X2([ + XcmV5Junction.PalletInstance(50), + XcmV5Junction.GeneralIndex(1984n), + ]), + }, + fun: XcmV3MultiassetFungibility.Fungible(500_000_000n), + }, + ]), + fee_asset_item: 0, + weight_limit: XcmV3WeightLimit.Unlimited(), + }); + const decodedCall: any = tx.decodedCall; + console.log("πŸ‘€ Executing XCM:", JSON.stringify(decodedCall, toHuman, 2)); + + try { + const dryRunResult: any = await para1Api.apis.DryRunApi.dry_run_call( + origin, + decodedCall, + XCM_VERSION, + ); + console.log("πŸ“¦ Dry run result:", JSON.stringify(dryRunResult.value, toHuman, 2)); + + const executionResult = dryRunResult.value.execution_result; + if (!dryRunResult.success || !executionResult.success) { + console.error("❌ Local dry run failed!"); + } else { + console.log("βœ… Local dry run successful."); + + const emittedEvents: [any] = dryRunResult.value.emitted_events; + const polkadotXcmSentEvent = emittedEvents.find(event => + event.type === "PolkadotXcm" && event.value.type === "Sent" + ); + if (polkadotXcmSentEvent === undefined) { + console.log(`⚠️ PolkadotXcm.Sent is only available in runtimes built from stable2503-5 or later.`); + } else { + let para2BlockBefore = await para2Client.getFinalizedBlock(); + const extrinsic = await tx.signAndSubmit(aliceSigner); + const para1Block = extrinsic.block; + console.log(`πŸ“¦ Finalised on ${para1Name} in block #${para1Block.number}: ${para1Block.hash}`); + + if (!extrinsic.ok) { + const dispatchError = extrinsic.dispatchError; + if (dispatchError.type === "Module") { + const modErr: any = dispatchError.value; + console.error(`❌ Dispatch error in module: ${modErr.type} β†’ ${modErr.value?.type}`); + } else { + console.error("❌ Dispatch error:", JSON.stringify(dispatchError, toHuman, 2)); + } + } + + const sentEvents: any = await para1Api.event.PolkadotXcm.Sent.pull(); + if (sentEvents.length > 0) { + const sentMessageId = sentEvents[0].payload.message_id.asHex(); + console.log(`πŸ“£ Last message sent on ${para1Name}: ${sentMessageId}`); + await assertProcessedMessageId(para2Client, para2Api, para2Name, para2BlockBefore, sentMessageId); + } else { + console.log(`πŸ“£ No Sent events on ${para1Name} found.`); + } + } + } + } finally { + para1Client.destroy(); + para2Client.destroy(); + } +} + +main().catch(console.error); \ No newline at end of file diff --git a/develop/interoperability/xcm-observability.md b/develop/interoperability/xcm-observability.md new file mode 100644 index 000000000..ea5340d3a --- /dev/null +++ b/develop/interoperability/xcm-observability.md @@ -0,0 +1,149 @@ +--- +title: XCM Observability +description: A conceptual overview of XCM observability in Polkadot, covering message correlation, tracing, and debugging features in modern runtimes. +--- + +# XCM Observability + +## Introduction + +Cross-Consensus Messaging (XCM) powers interoperability across the Polkadot ecosystem, but tracing and debugging these flows across multiple chains is challenging. + +This guide introduces the observability features in modern Polkadot runtimes and the Polkadot SDK that make XCMs easier to trace and debug. + +You'll learn how to: + +- Use the [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} instruction and `message_id` to track XCMs across chains +- Correlate messages through [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events +- Apply derived message ID workarounds for older runtimes +- Diagnose failed or incomplete XCMs with indexers and Chopsticks replay + +For a hands-on walkthrough, see the companion tutorial: [XCM Observability in Action](/tutorials/interoperability/xcm-observability-in-action){target=\_blank}. + +## Core Concepts + +### What is `SetTopic`? + +When executing XCMs (via [`limited_reserve_transfer_assets`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.limited_reserve_transfer_assets){target=\_blank}, other extrinsics, or raw calls from the [`PolkadotXcm` pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html){target=\_blank}) on system chains, the runtime automatically appends a [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} instruction if one is missing. + +- **[`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank}** is an XCM instruction that sets a 32-byte topic register inside the message. +- The topic acts as a **logical identifier** (`message_id`) for the XCM, allowing you to group and trace related messages across chains. + +⚠️ **Note:** When using [`WithUniqueTopic`](https://paritytech.github.io/polkadot-sdk/master/staging_xcm_builder/struct.WithUniqueTopic.html){target=\_blank}, the topic ID is guaranteed to be unique if `WithUniqueTopic` automatically appends the `SetTopic` instruction. However, if the message already includes a `SetTopic` instruction, the uniqueness of the ID depends on the message creator and is not guaranteed. + +### Observability Features + +Runtimes built from **`stable2503-5` or later** provide key observability features for tracing and correlating XCMs across chains: + +- **[`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank}** emitted on the origin chain when an XCM is sent. +- **[`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank}** emitted on the destination chain when the XCM is processed. +- The **`message_id` in the `Sent` event matches the `id` in the `Processed` event**, enabling reliable cross-chain correlation. This `message_id` is derived from the `SetTopic` instruction. + +| Chain Role | Event | Field | Purpose | +|-------------------|--------------------------|--------------|------------------------------------| +| Origin chain | `PolkadotXcm.Sent` | `message_id` | Identifies the sent XCM | +| Destination chain | `MessageQueue.Processed` | `id` | Confirms processing of the message | + +Matching these IDs lets you **correlate** an origin message with its destination processing. + +### Why Not `XcmpQueue.XcmpMessageSent`? + +- The event [`XcmpQueue.XcmpMessageSent`](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcmp_queue/pallet/enum.Event.html#variant.XcmpMessageSent){target=\_blank} contains a `message_hash` unrelated to `SetTopic`. +- It is **not reliable for cross-chain tracing** and should be avoided for observability purposes. + +### Automatic vs Manual `SetTopic` + +- The runtime **automatically appends a `SetTopic` instruction** if one is missing at the end of an XCM. +- When using high-level extrinsics such as `limited_reserve_transfer_assets`, **you do not need to set a topic manually**; the runtime handles it for you. +- If you **manually craft an XCM** (e.g., via [`execute`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.execute){target=\_blank} or [`send`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.send){target=\_blank}), you **can supply your own `SetTopic`**, but it **must be the final instruction** to be respected. +- In **multi-hop XCM flows**, manually setting the topic ensures **consistent tracing across all hops**. Any **remote XCM calls** embedded inside the XCM must also include a `SetTopic` at the end to preserve the same `message_id` throughout the cross-chain flow. + +## Message Lifecycle + +Here is a high-level overview of an XCM lifecycle with observability events: + +```mermaid +flowchart TD + subgraph Origin["Origin Chain"] + direction LR + A["User submits extrinsic"] --> B["Appends SetTopic if missing"] + B --> C["XCM forwarded to destination"] + C --> D["Sends XCM"] + D --> E["PolkadotXcm.Sent emitted with message_id"] + end + + subgraph Destination["Destination Chain"] + direction LR + F["Process message payload"] --> G["MessageQueue.Processed emitted with matching id"] + G --> H["Success/failure logged; further hops if any"] + end + + Origin --> Destination +``` + +* The `SetTopic` ensures a consistent `message_id` is passed and visible in these events. +* For multi-hop flows, the same `message_id` travels through all chains. + +## Workaround for Older Runtimes + +* Runtimes prior to **`stable2503-5`** emit a **derived `forwarded_id`** instead of the original topic in downstream `Processed` events. +* The forwarded ID is calculated as: + +```rust +fn forward_id_for(original_id: &XcmHash) -> XcmHash { + (b"forward_id_for", original_id).using_encoded(sp_io::hashing::blake2_256) +} +``` + +### Example + +| Original `message_id` | Forwarded `message_id` | +|-----------------------|---------------------------------------------------------------| +| `0x5c082b47...` | `0xb3ae32fd...` == blake2_256("forward_id_for" + original_id) | + +Tools and indexers tracing messages across mixed runtime versions should check **both** the original and forwarded IDs. + +```ts +--8<-- 'code/develop/interoperability/xcm-observability/forward-id-for.ts' +``` + +## Failure Event Handling + +When an XCM fails, the transaction **rolls back** and no explicit failure event is emitted on-chain. + +### How to Detect Failures + +1. **View Nested Dispatch Errors via Indexers** + + Most indexers display nested errors indicating why an XCM failed, e.g.: + + --8<-- 'code/develop/interoperability/xcm-observability/execution-with-error.html' + + Common errors include missing assets, exceeded execution limits, or invalid asset locations. This nested error reporting, introduced in runtimes from **`stable2506` onward**, usually suffices to diagnose typical issues. + +2. **Replay Using Chopsticks for Full Logs** + + For detailed troubleshooting: + + - Replay the failing extrinsic with logging enabled + - See exactly which instruction failed and why + - View complete error chains like `FailedToTransactAsset` or `AssetNotFound` + + This approach is invaluable for multi-hop flows and complex custom XCMs. + +### Recommended Debugging Workflow + +1. Start with indexer or API error output +2. If unclear, use Chopsticks to replay the exact transaction +3. Inspect logs for the failing XCM instruction and reason +4. Adjust weight limits, asset locations, or message construction as needed + +β†’ See [Replay and Dry Run XCMs Using Chopsticks](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank} for replay instructions. + +## Best Practices + +- When manually setting `SetTopic`, **always place it as the final instruction** in your XCM to ensure it is respected by the runtime. +- Prefer **automatic topic insertion** via high-level extrinsics for simplicity. +- If your use case involves multi-hop or custom XCMs, **manually set `SetTopic`** (including remote XCM calls) to guarantee consistent tracing. +- Ensure your `message_id` values are unique **if** you require deduplication or strict correlation. +- When supporting legacy runtimes, be aware of the `forward_id_for` pattern. diff --git a/llms-full.txt b/llms-full.txt index 628f914f5..dc27dd58d 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -27,6 +27,7 @@ Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/re Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/from-apps/transact.md Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/from-apps/transfers.md Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/index.md +Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-observability.md Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-runtime-apis.md Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/networks.md Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/parachains/customize-parachain/add-existing-pallets.md @@ -183,6 +184,7 @@ Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/re Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-channels/para-to-para.md Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-channels/para-to-system.md Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-fee-estimation.md +Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-observability-in-action.md Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-transfers/from-relaychain-to-parachain.md Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-transfers/index.md Doc-Page: https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/onchain-governance/fast-track-gov-proposal.md @@ -3157,6 +3159,208 @@ Whether you're building applications that need to interact with multiple chains, --- END CONTENT --- +Doc-Content: https://docs.polkadot.com/develop/interoperability/xcm-observability/ +--- BEGIN CONTENT --- +--- +title: XCM Observability +description: A conceptual overview of XCM observability in Polkadot, covering message correlation, tracing, and debugging features in modern runtimes. +--- + +# XCM Observability + +## Introduction + +Cross-Consensus Messaging (XCM) powers interoperability across the Polkadot ecosystem, but tracing and debugging these flows across multiple chains is challenging. + +This guide introduces the observability features in modern Polkadot runtimes and the Polkadot SDK that make XCMs easier to trace and debug. + +You'll learn how to: + +- Use the [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} instruction and `message_id` to track XCMs across chains +- Correlate messages through [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events +- Apply derived message ID workarounds for older runtimes +- Diagnose failed or incomplete XCMs with indexers and Chopsticks replay + +For a hands-on walkthrough, see the companion tutorial: [XCM Observability in Action](/tutorials/interoperability/xcm-observability-in-action){target=\_blank}. + +## Core Concepts + +### What is `SetTopic`? + +When executing XCMs (via [`limited_reserve_transfer_assets`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.limited_reserve_transfer_assets){target=\_blank}, other extrinsics, or raw calls from the [`PolkadotXcm` pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html){target=\_blank}) on system chains, the runtime automatically appends a [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} instruction if one is missing. + +- **[`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank}** is an XCM instruction that sets a 32-byte topic register inside the message. +- The topic acts as a **logical identifier** (`message_id`) for the XCM, allowing you to group and trace related messages across chains. + +⚠️ **Note:** When using [`WithUniqueTopic`](https://paritytech.github.io/polkadot-sdk/master/staging_xcm_builder/struct.WithUniqueTopic.html){target=\_blank}, the topic ID is guaranteed to be unique if `WithUniqueTopic` automatically appends the `SetTopic` instruction. However, if the message already includes a `SetTopic` instruction, the uniqueness of the ID depends on the message creator and is not guaranteed. + +### Observability Features + +Runtimes built from **`stable2503-5` or later** provide key observability features for tracing and correlating XCMs across chains: + +- **[`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank}** emitted on the origin chain when an XCM is sent. +- **[`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank}** emitted on the destination chain when the XCM is processed. +- The **`message_id` in the `Sent` event matches the `id` in the `Processed` event**, enabling reliable cross-chain correlation. This `message_id` is derived from the `SetTopic` instruction. + +| Chain Role | Event | Field | Purpose | +|-------------------|--------------------------|--------------|------------------------------------| +| Origin chain | `PolkadotXcm.Sent` | `message_id` | Identifies the sent XCM | +| Destination chain | `MessageQueue.Processed` | `id` | Confirms processing of the message | + +Matching these IDs lets you **correlate** an origin message with its destination processing. + +### Why Not `XcmpQueue.XcmpMessageSent`? + +- The event [`XcmpQueue.XcmpMessageSent`](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcmp_queue/pallet/enum.Event.html#variant.XcmpMessageSent){target=\_blank} contains a `message_hash` unrelated to `SetTopic`. +- It is **not reliable for cross-chain tracing** and should be avoided for observability purposes. + +### Automatic vs Manual `SetTopic` + +- The runtime **automatically appends a `SetTopic` instruction** if one is missing at the end of an XCM. +- When using high-level extrinsics such as `limited_reserve_transfer_assets`, **you do not need to set a topic manually**; the runtime handles it for you. +- If you **manually craft an XCM** (e.g., via [`execute`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.execute){target=\_blank} or [`send`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.send){target=\_blank}), you **can supply your own `SetTopic`**, but it **must be the final instruction** to be respected. +- In **multi-hop XCM flows**, manually setting the topic ensures **consistent tracing across all hops**. Any **remote XCM calls** embedded inside the XCM must also include a `SetTopic` at the end to preserve the same `message_id` throughout the cross-chain flow. + +## Message Lifecycle + +Here is a high-level overview of an XCM lifecycle with observability events: + +```mermaid +flowchart TD + subgraph Origin["Origin Chain"] + direction LR + A["User submits extrinsic"] --> B["Appends SetTopic if missing"] + B --> C["XCM forwarded to destination"] + C --> D["Sends XCM"] + D --> E["PolkadotXcm.Sent emitted with message_id"] + end + + subgraph Destination["Destination Chain"] + direction LR + F["Process message payload"] --> G["MessageQueue.Processed emitted with matching id"] + G --> H["Success/failure logged; further hops if any"] + end + + Origin --> Destination +``` + +* The `SetTopic` ensures a consistent `message_id` is passed and visible in these events. +* For multi-hop flows, the same `message_id` travels through all chains. + +## Workaround for Older Runtimes + +* Runtimes prior to **`stable2503-5`** emit a **derived `forwarded_id`** instead of the original topic in downstream `Processed` events. +* The forwarded ID is calculated as: + +```rust +fn forward_id_for(original_id: &XcmHash) -> XcmHash { + (b"forward_id_for", original_id).using_encoded(sp_io::hashing::blake2_256) +} +``` + +### Example + +| Original `message_id` | Forwarded `message_id` | +|-----------------------|---------------------------------------------------------------| +| `0x5c082b47...` | `0xb3ae32fd...` == blake2_256("forward_id_for" + original_id) | + +Tools and indexers tracing messages across mixed runtime versions should check **both** the original and forwarded IDs. + +```ts +import {blake2b} from "@noble/hashes/blake2"; +import {fromHex, mergeUint8, toHex} from "@polkadot-api/utils"; +import {Binary} from "polkadot-api"; + +function forwardIdFor(originalMessageId: string): string { + // Decode the hex original_id into bytes + const messageIdBytes = fromHex(originalMessageId); + + // Create prefixed input: b"forward_id_for" + original_id + const prefix = Binary.fromText("forward_id_for").asBytes(); + const input = mergeUint8([prefix, messageIdBytes]); + + // Hash it using blake2b with 32-byte output + const forwardedIdBytes = blake2b(input, {dkLen: 32}); + // Convert to hex + return toHex(forwardedIdBytes); +} + +// Example: Forwarded ID from an original_id +const originalMessageId = "0x5c082b4750ee8c34986eb22ce6e345bad2360f3682cda3e99de94b0d9970cb3e"; + +// Create the forwarded ID +const forwardedIdHex = forwardIdFor(originalMessageId); + +console.log("πŸ”„ Forwarded ID:", forwardedIdHex); + +const expectedForwardedId = "0xb3ae32fd2e2f798e8215865a8950d19df8330843608d4ee44e9f86849029724a"; +if (forwardedIdHex === expectedForwardedId) { + console.log("βœ… Forwarded ID matches expected value."); +} else { + console.error("❌ Forwarded ID does not match expected value."); +} +``` + +## Failure Event Handling + +When an XCM fails, the transaction **rolls back** and no explicit failure event is emitted on-chain. + +### How to Detect Failures + +1. **View Nested Dispatch Errors via Indexers** + + Most indexers display nested errors indicating why an XCM failed, e.g.: + +
+
+"error": {
+  "type": "Module",
+  "value": {
+    "type": "PolkadotXcm",
+    "value": {
+      "type": "LocalExecutionIncompleteWithError",
+      "value": {
+        "index": 0,
+        "error": {
+          "type": "FailedToTransactAsset"
+        }
+      }
+    }
+  }
+}
+  
+
+ + Common errors include missing assets, exceeded execution limits, or invalid asset locations. This nested error reporting, introduced in runtimes from **`stable2506` onward**, usually suffices to diagnose typical issues. + +2. **Replay Using Chopsticks for Full Logs** + + For detailed troubleshooting: + + - Replay the failing extrinsic with logging enabled + - See exactly which instruction failed and why + - View complete error chains like `FailedToTransactAsset` or `AssetNotFound` + + This approach is invaluable for multi-hop flows and complex custom XCMs. + +### Recommended Debugging Workflow + +1. Start with indexer or API error output +2. If unclear, use Chopsticks to replay the exact transaction +3. Inspect logs for the failing XCM instruction and reason +4. Adjust weight limits, asset locations, or message construction as needed + +β†’ See [Replay and Dry Run XCMs Using Chopsticks](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank} for replay instructions. + +## Best Practices + +- When manually setting `SetTopic`, **always place it as the final instruction** in your XCM to ensure it is respected by the runtime. +- Prefer **automatic topic insertion** via high-level extrinsics for simplicity. +- If your use case involves multi-hop or custom XCMs, **manually set `SetTopic`** (including remote XCM calls) to guarantee consistent tracing. +- Ensure your `message_id` values are unique **if** you require deduplication or strict correlation. +- When supporting legacy runtimes, be aware of the `forward_id_for` pattern. +--- END CONTENT --- + Doc-Content: https://docs.polkadot.com/develop/interoperability/xcm-runtime-apis/ --- BEGIN CONTENT --- --- @@ -30866,6 +31070,1087 @@ This approach provides accurate fee estimation for XCM teleports from Asset Hub The key insight is understanding how asset references change based on the perspective of each chain in the XCM ecosystem, which is crucial for proper fee estimation and XCM construction. --- END CONTENT --- +Doc-Content: https://docs.polkadot.com/tutorials/interoperability/xcm-observability-in-action/ +--- BEGIN CONTENT --- +--- +title: XCM Observability in Action +description: A hands-on guide to tracing, correlating, and debugging cross-chain XCMs using observability tools in the Polkadot SDK. +--- + +# XCM Observability in Action + +## Introduction + +Cross-Consensus Messaging (XCM) powers interoperability in the Polkadot ecosystem, but tracing flows across multiple chains is challenging in practice. + +This tutorial walks through a **hands-on scenario**: sending assets between parachains and tracing the resulting XCM across origin and destination chains. Along the way, you will: + +- Capture `message_id` and [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} for tracking +- Correlate [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events across chains +- Apply manual topic tagging for custom multi-hop flows + +For background concepts and best practices, see the companion page: [XCM Observability](/develop/interoperability/xcm-observability){target=\_blank}. + +## Prerequisites + +Before you begin, make sure you've: + +- [Chopsticks](/develop/toolkit/parachains/fork-chains/chopsticks/get-started/){target=\_blank} installed +- Access to local or remote parachain endpoints +- An origin chain running runtime **`stable2503-5`** or later +- A TypeScript development environment with essential tools +- Familiarity with replaying or dry-running XCMs + +If you're new to replay or dry-run XCMs, see [Replay and Dry Run XCMs Using Chopsticks](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank}. + +## Setting Up Your Workspace + +1. Create a project directory: + + ```bash + mkdir -p xcm-obs-demo && cd xcm-obs-demo + ``` + +2. Install Chopsticks globally: + + ```bash + npm install -g @acala-network/chopsticks@latest + ``` + +3. Download 1.6.0 runtime, which is built from **`stable2503-5` or later**: + + ```bash + mkdir -p wasms + wget https://github.com/polkadot-fellows/runtimes/releases/download/v1.6.0/asset-hub-polkadot_runtime-v1006000.compact.compressed.wasm -O wasms/asset-hub-polkadot_v1.6.0.wasm + ``` + +4. Download config of Polkadot Hub: + + ```bash + mkdir -p configs + wget https://raw.githubusercontent.com/AcalaNetwork/chopsticks/master/configs/polkadot-asset-hub.yml -O configs/polkadot-hub-override.yaml + ``` + +5. Edit `configs/polkadot-hub-override.yaml` to include: + + ```yaml title="configs/polkadot-hub-override.yaml" + ... + db: ./db.sqlite + wasm-override: wasms/asset-hub-polkadot_v1.6.0.wasm + + import-storage: + ... + ``` + +6. Fork the relevant chains locally using Chopsticks: + + ```bash + npx @acala-network/chopsticks xcm -r polkadot -p configs/polkadot-hub-override.yaml -p hydradx + ``` + + β†’ See the [Fork a Chain with Chopsticks guide](/tutorials/polkadot-sdk/testing/fork-live-chains/){target=\_blank} for detailed instructions. + +7. Open a **new terminal** in the same folder and initialise a Node.js project: + + ```bash + npm init -y && npm pkg set type="module" + ``` + +8. Install TypeScript and Polkadot dependencies: + + ```bash + npm install --save-dev typescript @types/node tsx + npm install polkadot-api @polkadot-labs/hdkd @polkadot-labs/hdkd-helpers + npm install @noble/hashes + ``` + +9. Initialise TypeScript: + + ```bash + npx tsc --init + ``` + +10. Add descriptors: + + ```bash + npx papi add assetHub -w ws://localhost:8000 + npx papi add hydration -w ws://localhost:8001 + ``` + +## Scenario 1: XCM Flow with Implicit `SetTopic` + +### Overview + +- **Origin:** Polkadot Hub +- **Destination:** Hydration +- **Extrinsic:** `limited_reserve_transfer_assets` (high-level) +- **Topic:** Set automatically by the runtime + +### Run the Script + +Create and run `limited-reserve-transfer-assets.ts`: + +```ts +import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; +import {withPolkadotSdkCompat} from "polkadot-api/polkadot-sdk-compat"; +import {getPolkadotSigner} from "polkadot-api/signer"; +import {getWsProvider} from "polkadot-api/ws-provider/web"; +import { + assetHub, + hydration, + XcmV3MultiassetFungibility, + XcmV3WeightLimit, + XcmV5Junction, + XcmV5Junctions, + XcmVersionedAssets, + XcmVersionedLocation, +} from "@polkadot-api/descriptors"; +import {sr25519CreateDerive} from "@polkadot-labs/hdkd"; +import { + DEV_PHRASE, + entropyToMiniSecret, + mnemonicToEntropy, + ss58Address, +} from "@polkadot-labs/hdkd-helpers"; + +const XCM_VERSION = 5; +const MAX_RETRIES = 8; // Number of attempts to wait for block finalisation + +const toHuman = (_key: any, value: any) => { + if (typeof value === "bigint") { + return Number(value); + } + + if (value && typeof value === "object" && typeof value.asHex === "function") { + return value.asHex(); + } + + return value; +}; + +async function assertProcessedMessageId( + client: PolkadotClient, + api: any, + name: String, + blockBefore: BlockInfo, + expectedMessageId: String, +) { + let processedMessageId = undefined; + for (let i = 0; i < MAX_RETRIES; i++) { + const blockAfter = await client.getFinalizedBlock(); + if (blockAfter.number == blockBefore.number) { + const waiting = 1_000 * (2 ** i); + console.log(`⏳ Waiting ${waiting / 1_000}s for ${name} block to be finalised (${i + 1}/${MAX_RETRIES})...`); + await new Promise((resolve) => setTimeout(resolve, waiting)); + continue; + } + + console.log(`πŸ“¦ Finalised on ${name} in block #${blockAfter.number}: ${blockAfter.hash}`); + const processedEvents = await api.event.MessageQueue.Processed.pull(); + const processingFailedEvents = await api.event.MessageQueue.ProcessingFailed.pull(); + if (processedEvents.length > 0) { + processedMessageId = processedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message processed on ${name}: ${processedMessageId}`); + break; + } else if (processingFailedEvents.length > 0) { + processedMessageId = processingFailedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message ProcessingFailed on ${name}: ${processedMessageId}`); + break; + } else { + console.log(`πŸ“£ No Processed events on ${name} found.`); + blockBefore = blockAfter; // Update the block before to the latest one + } + } + + if (processedMessageId === expectedMessageId) { + console.log(`βœ… Processed Message ID on ${name} matched.`); + } else if (processedMessageId === undefined) { + console.error(`❌ Processed Message ID on ${name} is undefined. Try increasing MAX_RETRIES to wait for block finalisation.`); + } else { + console.error(`❌ Processed Message ID [${processedMessageId}] on ${name} doesn't match expected Message ID [${expectedMessageId}].`); + } +} + +async function main() { + const para1Name = "Polkadot Hub"; + const para1Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8000")), + ); + const para1Api = para1Client.getTypedApi(assetHub); + + const para2Name = "Hydration"; + const para2Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8001")), + ); + const para2Api = para2Client.getTypedApi(hydration); + + const entropy = mnemonicToEntropy(DEV_PHRASE); + const miniSecret = entropyToMiniSecret(entropy); + const derive = sr25519CreateDerive(miniSecret); + const alice = derive("//Alice"); + const alicePublicKey = alice.publicKey; + const aliceSigner = getPolkadotSigner(alicePublicKey, "Sr25519", alice.sign); + const aliceAddress = ss58Address(alicePublicKey); + + const origin = Enum("system", Enum("Signed", aliceAddress)); + const beneficiary = { + parents: 0, + interior: XcmV5Junctions.X1(XcmV5Junction.AccountId32({ + id: Binary.fromHex("0x9818ff3c27d256631065ecabf0c50e02551e5c5342b8669486c1e566fcbf847f") + })), + } + + const tx: any = para1Api.tx.PolkadotXcm.limited_reserve_transfer_assets({ + dest: XcmVersionedLocation.V5({ + parents: 1, + interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(2034)), + }), + beneficiary: XcmVersionedLocation.V5(beneficiary), + assets: XcmVersionedAssets.V5([ + { + id: { + parents: 0, + interior: XcmV5Junctions.X2([ + XcmV5Junction.PalletInstance(50), + XcmV5Junction.GeneralIndex(1984n), + ]), + }, + fun: XcmV3MultiassetFungibility.Fungible(500_000_000n), + }, + ]), + fee_asset_item: 0, + weight_limit: XcmV3WeightLimit.Unlimited(), + }); + const decodedCall: any = tx.decodedCall; + console.log("πŸ‘€ Executing XCM:", JSON.stringify(decodedCall, toHuman, 2)); + + try { + const dryRunResult: any = await para1Api.apis.DryRunApi.dry_run_call( + origin, + decodedCall, + XCM_VERSION, + ); + console.log("πŸ“¦ Dry run result:", JSON.stringify(dryRunResult.value, toHuman, 2)); + + const executionResult = dryRunResult.value.execution_result; + if (!dryRunResult.success || !executionResult.success) { + console.error("❌ Local dry run failed!"); + } else { + console.log("βœ… Local dry run successful."); + + const emittedEvents: [any] = dryRunResult.value.emitted_events; + const polkadotXcmSentEvent = emittedEvents.find(event => + event.type === "PolkadotXcm" && event.value.type === "Sent" + ); + if (polkadotXcmSentEvent === undefined) { + console.log(`⚠️ PolkadotXcm.Sent is only available in runtimes built from stable2503-5 or later.`); + } else { + let para2BlockBefore = await para2Client.getFinalizedBlock(); + const extrinsic = await tx.signAndSubmit(aliceSigner); + const para1Block = extrinsic.block; + console.log(`πŸ“¦ Finalised on ${para1Name} in block #${para1Block.number}: ${para1Block.hash}`); + + if (!extrinsic.ok) { + const dispatchError = extrinsic.dispatchError; + if (dispatchError.type === "Module") { + const modErr: any = dispatchError.value; + console.error(`❌ Dispatch error in module: ${modErr.type} β†’ ${modErr.value?.type}`); + } else { + console.error("❌ Dispatch error:", JSON.stringify(dispatchError, toHuman, 2)); + } + } + + const sentEvents: any = await para1Api.event.PolkadotXcm.Sent.pull(); + if (sentEvents.length > 0) { + const sentMessageId = sentEvents[0].payload.message_id.asHex(); + console.log(`πŸ“£ Last message sent on ${para1Name}: ${sentMessageId}`); + await assertProcessedMessageId(para2Client, para2Api, para2Name, para2BlockBefore, sentMessageId); + } else { + console.log(`πŸ“£ No Sent events on ${para1Name} found.`); + } + } + } + } finally { + para1Client.destroy(); + para2Client.destroy(); + } +} + +main().catch(console.error); +``` + +```bash +npx tsx limited-reserve-transfer-assets.ts +``` + +### Forwarded XCM Example + +The runtime adds a `SetTopic` to the forwarded XCM automatically: + +
+
+[
+  {
+    "type": "ReserveAssetDeposited",
+    "value": [...]
+  },
+  {
+    "type": "ClearOrigin"
+  },
+  {
+    "type": "BuyExecution",
+    "value": {...}
+  },
+  {
+    "type": "DepositAsset",
+    "value": {...}
+  },
+  {
+    "type": "SetTopic",
+    "value": "0x3b5650b78230aebb8f2573d2c5e8356494ab01e39e716087c177bf871dce70b9"
+  }
+]
+  
+
+ +### Trace Events + +| Chain | Event | Field | Notes | +|--------------|--------------------------|--------------|----------------------------------------| +| Polkadot Hub | `PolkadotXcm.Sent` | `message_id` | Matches the topic in the forwarded XCM | +| Hydration | `MessageQueue.Processed` | `id` | Matches origin's `message_id` | + +> ⚠️ Dry run generated topics may differ from actual execution. + +### Message Trace Output + +
+ npx tsx limited-reserve-transfer-assets.ts + βœ… Local dry run successful. + πŸ“¦ Finalised on Polkadot Hub in block #9477291: 0xf54cecc017762c714bbdf3e82d72ed90886257ca17d32ec6dc8ea20e28110af8 + πŸ“£ Last message sent on Polkadot Hub: 0x20432393771dc049cea4900565a936d169b8ebdd64efa351890766df918615a4 + πŸ“¦ Finalised on Hydration in block #8761211: 0xa4c493ba9328f38174aa7a9ade0779654839e9d3c83b2bafc60d4e5b7de6a00f + πŸ“£ Last message processed on Hydration: 0x20432393771dc049cea4900565a936d169b8ebdd64efa351890766df918615a4 + βœ… Processed Message ID on Hydration matched. +
+ +## Scenario 2: XCM Transfer with Manual `SetTopic` + +### Overview + +- **Origin:** Polkadot Hub +- **Destination:** Hydration +- **Topic:** Manually assigned +- **Goal:** Ensure traceability in custom multi-hop flows + +### Run the Script + +Create and run `deposit-reserve-asset-with-set-topic.ts`: + +```ts +import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; +import {withPolkadotSdkCompat} from "polkadot-api/polkadot-sdk-compat"; +import {getPolkadotSigner} from "polkadot-api/signer"; +import {getWsProvider} from "polkadot-api/ws-provider/web"; +import { + assetHub, + hydration, + XcmV3MultiassetFungibility, + XcmV3WeightLimit, + XcmV5AssetFilter, + XcmV5Instruction, + XcmV5Junction, + XcmV5Junctions, + XcmV5WildAsset, + XcmVersionedXcm, +} from "@polkadot-api/descriptors"; +import {sr25519CreateDerive} from "@polkadot-labs/hdkd"; +import { + DEV_PHRASE, + entropyToMiniSecret, + mnemonicToEntropy, + ss58Address, +} from "@polkadot-labs/hdkd-helpers"; + +const XCM_VERSION = 5; +const MAX_RETRIES = 8; // Number of attempts to wait for block finalisation + +const toHuman = (_key: any, value: any) => { + if (typeof value === "bigint") { + return Number(value); + } + + if (value && typeof value === "object" && typeof value.asHex === "function") { + return value.asHex(); + } + + return value; +}; + +async function assertProcessedMessageId( + client: PolkadotClient, + api: any, + name: String, + blockBefore: BlockInfo, + expectedMessageId: String, +) { + let processedMessageId = undefined; + for (let i = 0; i < MAX_RETRIES; i++) { + const blockAfter = await client.getFinalizedBlock(); + if (blockAfter.number == blockBefore.number) { + const waiting = 1_000 * (2 ** i); + console.log(`⏳ Waiting ${waiting / 1_000}s for ${name} block to be finalised (${i + 1}/${MAX_RETRIES})...`); + await new Promise((resolve) => setTimeout(resolve, waiting)); + continue; + } + + console.log(`πŸ“¦ Finalised on ${name} in block #${blockAfter.number}: ${blockAfter.hash}`); + const processedEvents = await api.event.MessageQueue.Processed.pull(); + const processingFailedEvents = await api.event.MessageQueue.ProcessingFailed.pull(); + if (processedEvents.length > 0) { + processedMessageId = processedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message processed on ${name}: ${processedMessageId}`); + break; + } else if (processingFailedEvents.length > 0) { + processedMessageId = processingFailedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message ProcessingFailed on ${name}: ${processedMessageId}`); + break; + } else { + console.log(`πŸ“£ No Processed events on ${name} found.`); + blockBefore = blockAfter; // Update the block before to the latest one + } + } + + if (processedMessageId === expectedMessageId) { + console.log(`βœ… Processed Message ID on ${name} matched.`); + } else if (processedMessageId === undefined) { + console.error(`❌ Processed Message ID on ${name} is undefined. Try increasing MAX_RETRIES to wait for block finalisation.`); + } else { + console.error(`❌ Processed Message ID [${processedMessageId}] on ${name} doesn't match expected Message ID [${expectedMessageId}].`); + } +} + +async function main() { + const para1Name = "Polkadot Hub"; + const para1Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8000")), + ); + const para1Api = para1Client.getTypedApi(assetHub); + + const para2Name = "Hydration"; + const para2Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8001")), + ); + const para2Api = para2Client.getTypedApi(hydration); + + const entropy = mnemonicToEntropy(DEV_PHRASE); + const miniSecret = entropyToMiniSecret(entropy); + const derive = sr25519CreateDerive(miniSecret); + const alice = derive("//Alice"); + const alicePublicKey = alice.publicKey; + const aliceSigner = getPolkadotSigner(alicePublicKey, "Sr25519", alice.sign); + const aliceAddress = ss58Address(alicePublicKey); + + const origin = Enum("system", Enum("Signed", aliceAddress)); + const beneficiary = { + parents: 0, + interior: XcmV5Junctions.X1(XcmV5Junction.AccountId32({ + id: Binary.fromHex("0x9818ff3c27d256631065ecabf0c50e02551e5c5342b8669486c1e566fcbf847f") + })), + } + const expectedMessageId = "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2"; + + const message = XcmVersionedXcm.V5([ + XcmV5Instruction.WithdrawAsset([ + { + id: { + parents: 1, + interior: XcmV5Junctions.Here(), + }, + fun: XcmV3MultiassetFungibility.Fungible(1_000_000_000n), + }, + ]), + + XcmV5Instruction.ClearOrigin(), + + XcmV5Instruction.BuyExecution({ + fees: { + id: { + parents: 1, + interior: XcmV5Junctions.Here(), + }, + fun: XcmV3MultiassetFungibility.Fungible(500_000_000n), + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + + XcmV5Instruction.DepositReserveAsset({ + assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.All()), + dest: { + parents: 1, + interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(2034)), + }, + xcm: [ + XcmV5Instruction.BuyExecution({ + fees: { + id: { + parents: 1, + interior: XcmV5Junctions.Here(), + }, + fun: XcmV3MultiassetFungibility.Fungible(500_000_000n), + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + + XcmV5Instruction.DepositAsset({ + assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.All()), + beneficiary, + }), + ], + }), + + XcmV5Instruction.SetTopic(Binary.fromHex(expectedMessageId)), + ]); + + const weight: any = await para1Api.apis.XcmPaymentApi.query_xcm_weight(message); + if (weight.success !== true) { + console.error("❌ Failed to query XCM weight:", weight.error); + para1Client.destroy(); + return; + } + + const tx: any = para1Api.tx.PolkadotXcm.execute({ + message, + max_weight: weight.value, + }); + const decodedCall: any = tx.decodedCall; + console.log("πŸ‘€ Executing XCM:", JSON.stringify(decodedCall, toHuman, 2)); + + try { + const dryRunResult: any = await para1Api.apis.DryRunApi.dry_run_call( + origin, + decodedCall, + XCM_VERSION, + ); + console.log("πŸ“¦ Dry run result:", JSON.stringify(dryRunResult.value, toHuman, 2)); + + const executionResult = dryRunResult.value.execution_result; + if (!dryRunResult.success || !executionResult.success) { + console.error("❌ Local dry run failed!"); + } else { + console.log("βœ… Local dry run successful."); + + const emittedEvents: [any] = dryRunResult.value.emitted_events; + const polkadotXcmSentEvent = emittedEvents.find(event => + event.type === "PolkadotXcm" && event.value.type === "Sent" + ); + if (polkadotXcmSentEvent === undefined) { + console.log(`⚠️ PolkadotXcm.Sent is only available in runtimes built from stable2503-5 or later.`); + } else { + let para2BlockBefore = await para2Client.getFinalizedBlock(); + const extrinsic = await tx.signAndSubmit(aliceSigner); + const para1Block = extrinsic.block; + console.log(`πŸ“¦ Finalised on ${para1Name} in block #${para1Block.number}: ${para1Block.hash}`); + + if (!extrinsic.ok) { + const dispatchError = extrinsic.dispatchError; + if (dispatchError.type === "Module") { + const modErr: any = dispatchError.value; + console.error(`❌ Dispatch error in module: ${modErr.type} β†’ ${modErr.value?.type}`); + } else { + console.error("❌ Dispatch error:", JSON.stringify(dispatchError, toHuman, 2)); + } + } + + const sentEvents: any = await para1Api.event.PolkadotXcm.Sent.pull(); + if (sentEvents.length > 0) { + const sentMessageId = sentEvents[0].payload.message_id.asHex(); + console.log(`πŸ“£ Last message sent on ${para1Name}: ${sentMessageId}`); + if (sentMessageId === expectedMessageId) { + console.log(`βœ… Sent Message ID on ${para1Name} matched.`); + } else { + console.error(`❌ Sent Message ID [${sentMessageId}] on ${para1Name} doesn't match expected Message ID [${expectedMessageId}].`); + } + await assertProcessedMessageId(para2Client, para2Api, para2Name, para2BlockBefore, expectedMessageId); + } else { + console.log(`πŸ“£ No Sent events on ${para1Name} found.`); + } + } + } + } finally { + para1Client.destroy(); + para2Client.destroy(); + } +} + +main().catch(console.error); +``` + +```bash +npx tsx deposit-reserve-asset-with-set-topic.ts +``` + +### Forwarded XCM Example + +Your manual `SetTopic` is preserved by the runtime: + +
+
+[
+  {
+    "type": "ReserveAssetDeposited",
+    "value": [...]
+  },
+  {
+    "type": "ClearOrigin"
+  },
+  {
+    "type": "BuyExecution",
+    "value": {...}
+  },
+  {
+    "type": "DepositAsset",
+    "value": {...}
+  },
+  {
+    "type": "SetTopic",
+    "value": "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2"
+  }
+]
+  
+
+ +### Message Trace Output + +
+ npx tsx limited-reserve-transfer-assets.ts + βœ… Local dry run successful. + πŸ“¦ Finalised on Polkadot Hub in block #9471830: 0x98bd858739b3b5dd558def60cbd85d5e7fb2f4e33b0c00e1895e316541d727d9 + πŸ“£ Last message sent on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Sent Message ID on Polkadot Hub matched. + πŸ“¦ Finalised on Hydration in block #8749233: 0xe1413c5126698d7189d6f55a38e62d07ea4915078c2b1f3914d70f670e79e162 + πŸ“£ Last message processed on Hydration: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Processed Message ID on Hydration matched. +
+ +## Scenario 3: Multi-hop XCM Transfer with Manual `SetTopic` + +### Overview + +- **Origin:** Polkadot Hub +- **Destination:** Hydration +- **Topic:** Manually assigned and preserved over multiple hops (including remote XCMs) +- **Goal:** Enable consistent tracing across multi-hop XCM flows + +### Run the Script + +Create and run `initiate-reserve-withdraw-with-set-topic.ts`: + +```ts +import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; +import {withPolkadotSdkCompat} from "polkadot-api/polkadot-sdk-compat"; +import {getPolkadotSigner} from "polkadot-api/signer"; +import {getWsProvider} from "polkadot-api/ws-provider/web"; +import { + assetHub, + hydration, + XcmV2MultiassetWildFungibility, + XcmV3MultiassetFungibility, + XcmV3WeightLimit, + XcmV5AssetFilter, + XcmV5Instruction, + XcmV5Junction, + XcmV5Junctions, + XcmV5WildAsset, + XcmVersionedXcm, +} from "@polkadot-api/descriptors"; +import {sr25519CreateDerive} from "@polkadot-labs/hdkd"; +import { + DEV_PHRASE, + entropyToMiniSecret, + mnemonicToEntropy, + ss58Address, +} from "@polkadot-labs/hdkd-helpers"; + +const XCM_VERSION = 5; +const MAX_RETRIES = 8; // Number of attempts to wait for block finalisation + +const toHuman = (_key: any, value: any) => { + if (typeof value === "bigint") { + return Number(value); + } + + if (value && typeof value === "object" && typeof value.asHex === "function") { + return value.asHex(); + } + + return value; +}; + +async function assertProcessedMessageId( + client: PolkadotClient, + api: any, + name: String, + blockBefore: BlockInfo, + expectedMessageId: String, +) { + let processedMessageId = undefined; + for (let i = 0; i < MAX_RETRIES; i++) { + const blockAfter = await client.getFinalizedBlock(); + if (blockAfter.number == blockBefore.number) { + const waiting = 1_000 * (2 ** i); + console.log(`⏳ Waiting ${waiting / 1_000}s for ${name} block to be finalised (${i + 1}/${MAX_RETRIES})...`); + await new Promise((resolve) => setTimeout(resolve, waiting)); + continue; + } + + console.log(`πŸ“¦ Finalised on ${name} in block #${blockAfter.number}: ${blockAfter.hash}`); + const processedEvents = await api.event.MessageQueue.Processed.pull(); + const processingFailedEvents = await api.event.MessageQueue.ProcessingFailed.pull(); + if (processedEvents.length > 0) { + processedMessageId = processedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message processed on ${name}: ${processedMessageId}`); + break; + } else if (processingFailedEvents.length > 0) { + processedMessageId = processingFailedEvents[0].payload.id.asHex(); + console.log(`πŸ“£ Last message ProcessingFailed on ${name}: ${processedMessageId}`); + break; + } else { + console.log(`πŸ“£ No Processed events on ${name} found.`); + blockBefore = blockAfter; // Update the block before to the latest one + } + } + + if (processedMessageId === expectedMessageId) { + console.log(`βœ… Processed Message ID on ${name} matched.`); + } else if (processedMessageId === undefined) { + console.error(`❌ Processed Message ID on ${name} is undefined. Try increasing MAX_RETRIES to wait for block finalisation.`); + } else { + console.error(`❌ Processed Message ID [${processedMessageId}] on ${name} doesn't match expected Message ID [${expectedMessageId}].`); + } +} + +async function main() { + const para1Name = "Polkadot Hub"; + const para1Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8000")), + ); + const para1Api = para1Client.getTypedApi(assetHub); + + const para2Name = "Hydration"; + const para2Client = createClient( + withPolkadotSdkCompat(getWsProvider("ws://localhost:8001")), + ); + const para2Api = para2Client.getTypedApi(hydration); + + const entropy = mnemonicToEntropy(DEV_PHRASE); + const miniSecret = entropyToMiniSecret(entropy); + const derive = sr25519CreateDerive(miniSecret); + const alice = derive("//Alice"); + const alicePublicKey = alice.publicKey; + const aliceSigner = getPolkadotSigner(alicePublicKey, "Sr25519", alice.sign); + const aliceAddress = ss58Address(alicePublicKey); + + const origin = Enum("system", Enum("Signed", aliceAddress)); + const beneficiary = { + parents: 0, + interior: XcmV5Junctions.X1(XcmV5Junction.AccountId32({ + id: Binary.fromHex("0x9818ff3c27d256631065ecabf0c50e02551e5c5342b8669486c1e566fcbf847f") + })), + } + const tokenId = XcmV5Junction.GeneralIndex(1337n); // Change to another token if FailedToTransactAsset("Funds are unavailable") + const assetId = { + parents: 0, + interior: XcmV5Junctions.X2([ + XcmV5Junction.PalletInstance(50), + tokenId, + ]), + }; + const giveId = { + parents: 1, + interior: XcmV5Junctions.X3([ + XcmV5Junction.Parachain(1000), + XcmV5Junction.PalletInstance(50), + tokenId, + ]), + }; + const giveFun = XcmV3MultiassetFungibility.Fungible(1_000_000n); + const dest = { + parents: 1, + interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(2034)), + }; + const wantId = { + parents: 1, + interior: XcmV5Junctions.Here(), + }; + const wantFun = XcmV3MultiassetFungibility.Fungible(2_000_000_000n); // Adjust the exchange rate if xcm_error is NoDeal + const expectedMessageId = "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2"; + + const message = XcmVersionedXcm.V5([ + XcmV5Instruction.WithdrawAsset([{ + id: assetId, + fun: giveFun, + }]), + + XcmV5Instruction.SetFeesMode({jit_withdraw: true}), + + XcmV5Instruction.DepositReserveAsset({ + assets: XcmV5AssetFilter.Wild( + XcmV5WildAsset.AllOf({ + id: assetId, + fun: XcmV2MultiassetWildFungibility.Fungible(), + })), + dest, + xcm: [ + XcmV5Instruction.BuyExecution({ + fees: { + id: giveId, + fun: giveFun, + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + + XcmV5Instruction.ExchangeAsset({ + give: XcmV5AssetFilter.Wild( + XcmV5WildAsset.AllOf({ + id: giveId, + fun: XcmV2MultiassetWildFungibility.Fungible(), + }), + ), + want: [{ + id: wantId, + fun: wantFun, + }], + maximal: false, + }), + + XcmV5Instruction.InitiateReserveWithdraw({ + assets: XcmV5AssetFilter.Wild( + XcmV5WildAsset.AllOf({ + id: wantId, + fun: XcmV2MultiassetWildFungibility.Fungible(), + }), + ), + reserve: { + parents: 1, + interior: XcmV5Junctions.X1( + XcmV5Junction.Parachain(1000), + ), + }, + xcm: [ + XcmV5Instruction.BuyExecution({ + fees: { + id: wantId, + fun: wantFun, + }, + weight_limit: XcmV3WeightLimit.Unlimited(), + }), + + XcmV5Instruction.DepositAsset({ + assets: XcmV5AssetFilter.Wild( + XcmV5WildAsset.AllOf({ + id: wantId, + fun: XcmV2MultiassetWildFungibility.Fungible(), + }), + ), + beneficiary, + }), + + XcmV5Instruction.SetTopic(Binary.fromHex(expectedMessageId)), // Ensure the same topic is also set on remote XCM calls + ], + }), + ], + }), + + XcmV5Instruction.SetTopic(Binary.fromHex(expectedMessageId)), + ]); + + const weight: any = await para1Api.apis.XcmPaymentApi.query_xcm_weight(message); + if (weight.success !== true) { + console.error("❌ Failed to query XCM weight:", weight.error); + para1Client.destroy(); + return; + } + + const tx: any = para1Api.tx.PolkadotXcm.execute({ + message, + max_weight: weight.value, + }); + const decodedCall: any = tx.decodedCall; + console.log("πŸ‘€ Executing XCM:", JSON.stringify(decodedCall, toHuman, 2)); + + try { + const dryRunResult: any = await para1Api.apis.DryRunApi.dry_run_call( + origin, + decodedCall, + XCM_VERSION, + ); + console.log("πŸ“¦ Dry run result:", JSON.stringify(dryRunResult.value, toHuman, 2)); + + const executionResult = dryRunResult.value.execution_result; + if (!dryRunResult.success || !executionResult.success) { + console.error("❌ Local dry run failed!"); + } else { + console.log("βœ… Local dry run successful."); + + const emittedEvents: [any] = dryRunResult.value.emitted_events; + const polkadotXcmSentEvent = emittedEvents.find(event => + event.type === "PolkadotXcm" && event.value.type === "Sent" + ); + if (polkadotXcmSentEvent === undefined) { + console.log(`⚠️ PolkadotXcm.Sent is only available in runtimes built from stable2503-5 or later.`); + } else { + let para2BlockBefore = await para2Client.getFinalizedBlock(); + const extrinsic = await tx.signAndSubmit(aliceSigner); + const para1Block = extrinsic.block; + console.log(`πŸ“¦ Finalised on ${para1Name} in block #${para1Block.number}: ${para1Block.hash}`); + + if (!extrinsic.ok) { + const dispatchError = extrinsic.dispatchError; + if (dispatchError.type === "Module") { + const modErr: any = dispatchError.value; + console.error(`❌ Dispatch error in module: ${modErr.type} β†’ ${modErr.value?.type}`); + } else { + console.error("❌ Dispatch error:", JSON.stringify(dispatchError, toHuman, 2)); + } + } + + const sentEvents: any = await para1Api.event.PolkadotXcm.Sent.pull(); + if (sentEvents.length > 0) { + const sentMessageId = sentEvents[0].payload.message_id.asHex(); + console.log(`πŸ“£ Last message sent on ${para1Name}: ${sentMessageId}`); + if (sentMessageId === expectedMessageId) { + console.log(`βœ… Sent Message ID on ${para1Name} matched.`); + } else { + console.error(`❌ Sent Message ID [${sentMessageId}] on ${para1Name} doesn't match expected Message ID [${expectedMessageId}].`); + } + await assertProcessedMessageId(para2Client, para2Api, para2Name, para2BlockBefore, expectedMessageId); + await assertProcessedMessageId(para1Client, para1Api, para1Name, para1Block, expectedMessageId); + } else { + console.log(`πŸ“£ No Sent events on ${para1Name} found.`); + } + } + } + } finally { + para1Client.destroy(); + para2Client.destroy(); + } +} + +main().catch(console.error); +``` + +```bash +npx tsx initiate-reserve-withdraw-with-set-topic.ts +``` + +### Forwarded XCM Example (Hydration) + +The runtime preserves your `SetTopic` throughout the multi-hop flow: + +
+
+[
+  {
+    "type": "ReserveAssetDeposited",
+    "value": [...]
+  },
+  {
+    "type": "ClearOrigin"
+  },
+  {
+    "type": "BuyExecution",
+    "value": {...}
+  },
+  {
+    "type": "ExchangeAsset",
+    "value": {...}
+  },
+  {
+    "type": "InitiateReserveWithdraw",
+    "value": {
+      "assets": {...},
+      "reserve": {...},
+      "xcm": [
+        {
+          "type": "BuyExecution",
+          "value": {...}
+        },
+        {
+          "type": "DepositAsset",
+          "value": {...}
+        },
+        {
+          "type": "SetTopic",
+          "value": "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2"
+        }
+      ]
+    }
+  },
+  {
+    "type": "SetTopic",
+    "value": "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2"
+  }
+]
+  
+
+ +### End-to-End Message Trace Output + +The same `message_id` is present in all relevant events across chains: + +
+ npx tsx initiate-reserve-withdraw-with-set-topic.ts + βœ… Local dry run successful. + πŸ“¦ Finalised on Polkadot Hub in block #9471831: 0x2620f7e29765fc953263b7835711011616702c9d82ef5306fe3ef4196cb75cab + πŸ“£ Last message sent on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Sent Message ID on Polkadot Hub matched. + πŸ“¦ Finalised on Hydration in block #8749235: 0xafe7f6149b1773a8d3d229040cda414aafd64baaeffa37fb4a5b2a542308b2d6 + πŸ“£ Last message processed on Hydration: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Processed Message ID on Hydration matched. + πŸ“¦ Finalised on Polkadot Hub in block #9471832: 0x7c150b69e3562694f0573e4fee73dfb86f3ab71b808679a1777586ff24643e9a + πŸ“£ Last message processed on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Processed Message ID on Polkadot Hub matched. +
+ +## Troubleshooting on Running Scripts + +### Processed Message ID is `undefined` + +If you see the following error when running a script: + +> ❌ Processed Message ID on Hydration is undefined. Try increasing MAX_RETRIES to wait for block finalisation. + +This usually means that the message has not yet been processed within the default retry window. + +Increase the `MAX_RETRIES` value in your script to give the chain more time: + +```ts +const MAX_RETRIES = 8; // Number of attempts to wait for block finalisation +``` + +### `PolkadotXcm.Sent` Event Not Found + +If you encounter an error indicating that `PolkadotXcm.Sent` is unavailable: + +> ⚠️ PolkadotXcm.Sent is only available in runtimes built from stable2503-5 or later. + +Ensure that `wasm-override` is updated to runtime version 1.6.0+, or to any runtime built from `stable2503-5` or later. + +For details on updating your workspace, see [Setting Up Your Workspace](#setting-up-your-workspace). + +## Summary + +This guide demonstrated: + +- How `SetTopic` and `message_id` enable tracing and correlating XCMs across chains +- How to interpret and debug XCM failure cases +- How to manually and automatically manage topics for multi-hop flows +- The legacy workaround for older runtimes with derived IDs + +With these scenarios and debugging steps, you can confidently develop, trace, and troubleshoot XCM workflows across chains. +--- END CONTENT --- + Doc-Content: https://docs.polkadot.com/tutorials/interoperability/xcm-transfers/from-relaychain-to-parachain/ --- BEGIN CONTENT --- --- diff --git a/llms.txt b/llms.txt index 43a669e24..4e2a1d7ad 100644 --- a/llms.txt +++ b/llms.txt @@ -25,6 +25,7 @@ - [Transact](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/from-apps/transact.md): Learn how to execute arbitrary calls on remote chains using the Transact instruction, enabling cross-chain function execution and remote pallet interactions. - [Transfers](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/from-apps/transfers.md): Learn how to perform cross-chain asset transfers using XCM, including teleport, reserve transfers, and handling different asset types across parachains. - [XCM Guides](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/index.md): Comprehensive guides for implementing XCM functionality in applications and understanding cross-chain interactions. +- [XCM Observability](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-observability.md): A conceptual overview of XCM observability in Polkadot, covering message correlation, tracing, and debugging features in modern runtimes. - [XCM Runtime APIs](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-runtime-apis.md): Learn about XCM Runtime APIs in Polkadot for cross-chain communication. Explore the APIs to simulate and test XCM messages before execution on the network. - [Networks](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/networks.md): Explore the Polkadot ecosystem networks and learn the unique purposes of each, tailored for blockchain innovation, testing, and enterprise-grade solutions. - [Add a Pallet to the Runtime](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/parachains/customize-parachain/add-existing-pallets.md): Learn how to include and configure pallets in a Polkadot SDK-based runtime, from adding dependencies to implementing necessary traits. @@ -88,7 +89,7 @@ - [Storage](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/toolkit/integrations/storage.md): Explore decentralized storage solutions for your Polkadot dApp. Discover key integrations, such as Crust and IPFS, for robust, censorship-resistant data storage. - [Transaction Construction](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/toolkit/integrations/transaction-construction.md): Understand how to construct, sign, and broadcast transactions in the Polkadot ecosystem using various tools and libraries. - [Wallets](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/toolkit/integrations/wallets.md): Explore blockchain wallets. Securely manage digital assets with hot wallets for online access or cold wallets for offline, enhanced security. -- [Interoperability](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/toolkit/interoperability/index.md): Explore Polkadot's XCM tooling ecosystem, featuring the Asset Transfer API and other utilities for implementing cross-chain messaging and transfers. +- [Interoperability](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/toolkit/interoperability/index.md): Explore Polkadot's XCM tooling ecosystem, featuring utilities for implementing cross-chain messaging and transfers. - [ParaSpell XCM SDK](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/toolkit/interoperability/paraspell-xcm-sdk/index.md): A powerful open-source library that simplifies XCM integration, enabling developers to easily build interoperable dApps on Polkadot. - [XCM Tools](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/toolkit/interoperability/xcm-tools.md): Explore essential XCM tools across Polkadot, crafted to enhance cross-chain functionality and integration within the ecosystem. - [E2E Testing on Polkadot SDK Chains](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/toolkit/parachains/e2e-testing/index.md): Discover a suite of tools for E2E testing on Polkadot SDK-based blockchains, including configuration management, automation, and debugging utilities. @@ -181,6 +182,7 @@ - [Opening HRMP Channels Between Parachains](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-channels/para-to-para.md): Learn how to open HRMP channels between parachains on Polkadot. Discover the step-by-step process for establishing uni- and bidirectional communication. - [Opening HRMP Channels with System Parachains](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-channels/para-to-system.md): Learn how to open HRMP channels with Polkadot system parachains. Discover the process for establishing bi-directional communication using a single XCM message. - [XCM Fee Estimation](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-fee-estimation.md): This tutorial demonstrates how to estimate the fees for teleporting assets from the Paseo Asset Hub parachain to the Paseo Bridge Hub chain. +- [XCM Observability in Action](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-observability-in-action.md): A hands-on guide to tracing, correlating, and debugging cross-chain XCMs using observability tools in the Polkadot SDK. - [XCM Transfers from Relay Chain to Parachain](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-transfers/from-relaychain-to-parachain.md): Learn how to perform a reserve-backed asset transfer between a relay chain and a parachain using XCM for cross-chain interoperability. - [XCM Transfers](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-transfers/index.md): Explore tutorials on performing transfers between different consensus systems using XCM technology to enable cross-chain interoperability. - [Fast Track a Governance Proposal](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/onchain-governance/fast-track-gov-proposal.md): Learn how to fast-track governance proposals in Polkadot's OpenGov using Chopsticks. Simulate, test, and execute proposals confidently. diff --git a/tutorials/interoperability/.nav.yml b/tutorials/interoperability/.nav.yml index c366ac17e..14c4686fe 100644 --- a/tutorials/interoperability/.nav.yml +++ b/tutorials/interoperability/.nav.yml @@ -5,3 +5,4 @@ nav: - xcm-transfers - 'Replay and Dry Run XCMs': replay-and-dry-run-xcms.md - 'XCM Fee Estimation': xcm-fee-estimation.md + - 'XCM Observability in Action': xcm-observability-in-action-in-action.md diff --git a/tutorials/interoperability/xcm-observability-in-action.md b/tutorials/interoperability/xcm-observability-in-action.md new file mode 100644 index 000000000..5e33a98dd --- /dev/null +++ b/tutorials/interoperability/xcm-observability-in-action.md @@ -0,0 +1,245 @@ +--- +title: XCM Observability in Action +description: A hands-on guide to tracing, correlating, and debugging cross-chain XCMs using observability tools in the Polkadot SDK. +--- + +# XCM Observability in Action + +## Introduction + +Cross-Consensus Messaging (XCM) powers interoperability in the Polkadot ecosystem, but tracing flows across multiple chains is challenging in practice. + +This tutorial walks through a **hands-on scenario**: sending assets between parachains and tracing the resulting XCM across origin and destination chains. Along the way, you will: + +- Capture `message_id` and [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} for tracking +- Correlate [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events across chains +- Apply manual topic tagging for custom multi-hop flows + +For background concepts and best practices, see the companion page: [XCM Observability](/develop/interoperability/xcm-observability){target=\_blank}. + +## Prerequisites + +Before you begin, make sure you've: + +- [Chopsticks](/develop/toolkit/parachains/fork-chains/chopsticks/get-started/){target=\_blank} installed +- Access to local or remote parachain endpoints +- An origin chain running runtime **`stable2503-5`** or later +- A TypeScript development environment with essential tools +- Familiarity with replaying or dry-running XCMs + +If you're new to replay or dry-run XCMs, see [Replay and Dry Run XCMs Using Chopsticks](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank}. + +## Setting Up Your Workspace + +1. Create a project directory: + + ```bash + mkdir -p xcm-obs-demo && cd xcm-obs-demo + ``` + +2. Install Chopsticks globally: + + ```bash + npm install -g @acala-network/chopsticks@latest + ``` + +3. Download 1.6.0 runtime, which is built from **`stable2503-5` or later**: + + ```bash + mkdir -p wasms + wget https://github.com/polkadot-fellows/runtimes/releases/download/v1.6.0/asset-hub-polkadot_runtime-v1006000.compact.compressed.wasm -O wasms/asset-hub-polkadot_v1.6.0.wasm + ``` + +4. Download config of Polkadot Hub: + + ```bash + mkdir -p configs + wget https://raw.githubusercontent.com/AcalaNetwork/chopsticks/master/configs/polkadot-asset-hub.yml -O configs/polkadot-hub-override.yaml + ``` + +5. Edit `configs/polkadot-hub-override.yaml` to include: + + ```yaml title="configs/polkadot-hub-override.yaml" + ... + db: ./db.sqlite + wasm-override: wasms/asset-hub-polkadot_v1.6.0.wasm + + import-storage: + ... + ``` + +6. Fork the relevant chains locally using Chopsticks: + + ```bash + npx @acala-network/chopsticks xcm -r polkadot -p configs/polkadot-hub-override.yaml -p hydradx + ``` + + β†’ See the [Fork a Chain with Chopsticks guide](/tutorials/polkadot-sdk/testing/fork-live-chains/){target=\_blank} for detailed instructions. + +7. Open a **new terminal** in the same folder and initialise a Node.js project: + + ```bash + npm init -y && npm pkg set type="module" + ``` + +8. Install TypeScript and Polkadot dependencies: + + ```bash + npm install --save-dev typescript @types/node tsx + npm install polkadot-api @polkadot-labs/hdkd @polkadot-labs/hdkd-helpers + npm install @noble/hashes + ``` + +9. Initialise TypeScript: + + ```bash + npx tsc --init + ``` + +10. Add descriptors: + + ```bash + npx papi add assetHub -w ws://localhost:8000 + npx papi add hydration -w ws://localhost:8001 + ``` + +## Scenario 1: XCM Flow with Implicit `SetTopic` + +### Overview + +- **Origin:** Polkadot Hub +- **Destination:** Hydration +- **Extrinsic:** `limited_reserve_transfer_assets` (high-level) +- **Topic:** Set automatically by the runtime + +### Run the Script + +Create and run `limited-reserve-transfer-assets.ts`: + +```ts +--8<-- 'code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets.ts' +``` + +```bash +npx tsx limited-reserve-transfer-assets.ts +``` + +### Forwarded XCM Example + +The runtime adds a `SetTopic` to the forwarded XCM automatically: + +--8<-- 'code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm.html' + +### Trace Events + +| Chain | Event | Field | Notes | +|--------------|--------------------------|--------------|----------------------------------------| +| Polkadot Hub | `PolkadotXcm.Sent` | `message_id` | Matches the topic in the forwarded XCM | +| Hydration | `MessageQueue.Processed` | `id` | Matches origin's `message_id` | + +> ⚠️ Dry run generated topics may differ from actual execution. + +### Message Trace Output + +--8<-- 'code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets-result.html' + +## Scenario 2: XCM Transfer with Manual `SetTopic` + +### Overview + +- **Origin:** Polkadot Hub +- **Destination:** Hydration +- **Topic:** Manually assigned +- **Goal:** Ensure traceability in custom multi-hop flows + +### Run the Script + +Create and run `deposit-reserve-asset-with-set-topic.ts`: + +```ts +--8<-- 'code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic.ts' +``` + +```bash +npx tsx deposit-reserve-asset-with-set-topic.ts +``` + +### Forwarded XCM Example + +Your manual `SetTopic` is preserved by the runtime: + +--8<-- 'code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-custom-topic.html' + +### Message Trace Output + +--8<-- 'code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic-result.html' + +## Scenario 3: Multi-hop XCM Transfer with Manual `SetTopic` + +### Overview + +- **Origin:** Polkadot Hub +- **Destination:** Hydration +- **Topic:** Manually assigned and preserved over multiple hops (including remote XCMs) +- **Goal:** Enable consistent tracing across multi-hop XCM flows + +### Run the Script + +Create and run `initiate-reserve-withdraw-with-set-topic.ts`: + +```ts +--8<-- 'code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic.ts' +``` + +```bash +npx tsx initiate-reserve-withdraw-with-set-topic.ts +``` + +### Forwarded XCM Example (Hydration) + +The runtime preserves your `SetTopic` throughout the multi-hop flow: + +--8<-- 'code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-remote-topic.html' + +### End-to-End Message Trace Output + +The same `message_id` is present in all relevant events across chains: + +--8<-- 'code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic-result.html' + +## Troubleshooting on Running Scripts + +### Processed Message ID is `undefined` + +If you see the following error when running a script: + +> ❌ Processed Message ID on Hydration is undefined. Try increasing MAX_RETRIES to wait for block finalisation. + +This usually means that the message has not yet been processed within the default retry window. + +Increase the `MAX_RETRIES` value in your script to give the chain more time: + +```ts +const MAX_RETRIES = 8; // Number of attempts to wait for block finalisation +``` + +### `PolkadotXcm.Sent` Event Not Found + +If you encounter an error indicating that `PolkadotXcm.Sent` is unavailable: + +> ⚠️ PolkadotXcm.Sent is only available in runtimes built from stable2503-5 or later. + +Ensure that `wasm-override` is updated to runtime version 1.6.0+, or to any runtime built from `stable2503-5` or later. + +For details on updating your workspace, see [Setting Up Your Workspace](#setting-up-your-workspace). + +## Summary + +This guide demonstrated: + +- How `SetTopic` and `message_id` enable tracing and correlating XCMs across chains +- How to interpret and debug XCM failure cases +- How to manually and automatically manage topics for multi-hop flows +- The legacy workaround for older runtimes with derived IDs + +With these scenarios and debugging steps, you can confidently develop, trace, and troubleshoot XCM workflows across chains. From ffede2ed66ad667c2392e50d148ada8a101cb000 Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Mon, 15 Sep 2025 14:06:13 -0400 Subject: [PATCH 2/6] formatting updates, correct nav filepath to fix 404 --- tutorials/interoperability/.nav.yml | 2 +- .../xcm-observability-in-action.md | 120 +++++++++--------- 2 files changed, 64 insertions(+), 58 deletions(-) diff --git a/tutorials/interoperability/.nav.yml b/tutorials/interoperability/.nav.yml index 14c4686fe..4c7096f87 100644 --- a/tutorials/interoperability/.nav.yml +++ b/tutorials/interoperability/.nav.yml @@ -5,4 +5,4 @@ nav: - xcm-transfers - 'Replay and Dry Run XCMs': replay-and-dry-run-xcms.md - 'XCM Fee Estimation': xcm-fee-estimation.md - - 'XCM Observability in Action': xcm-observability-in-action-in-action.md + - 'XCM Observability in Action': xcm-observability-in-action.md diff --git a/tutorials/interoperability/xcm-observability-in-action.md b/tutorials/interoperability/xcm-observability-in-action.md index 5e33a98dd..98c356e62 100644 --- a/tutorials/interoperability/xcm-observability-in-action.md +++ b/tutorials/interoperability/xcm-observability-in-action.md @@ -1,56 +1,53 @@ --- title: XCM Observability in Action -description: A hands-on guide to tracing, correlating, and debugging cross-chain XCMs using observability tools in the Polkadot SDK. +description: Follow this step-by-step guide to trace, correlate, and debug cross-chain XCMs using observability tools in the Polkadot SDK. --- # XCM Observability in Action ## Introduction -Cross-Consensus Messaging (XCM) powers interoperability in the Polkadot ecosystem, but tracing flows across multiple chains is challenging in practice. +Cross-Consensus Messaging (XCM) enables interoperability within the Polkadot ecosystem; however, tracing flows across multiple chains is challenging in practice. -This tutorial walks through a **hands-on scenario**: sending assets between parachains and tracing the resulting XCM across origin and destination chains. Along the way, you will: +Follow this tutorial to send assets between parachains and trace the resulting XCM across the origin and destination chains. By completing this tutorial, you will: -- Capture `message_id` and [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} for tracking -- Correlate [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events across chains -- Apply manual topic tagging for custom multi-hop flows - -For background concepts and best practices, see the companion page: [XCM Observability](/develop/interoperability/xcm-observability){target=\_blank}. +- Capture `message_id` and [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} for tracking. +- Correlate [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events across chains. +- Apply manual topic tagging for custom multi-hop flows. ## Prerequisites -Before you begin, make sure you've: - -- [Chopsticks](/develop/toolkit/parachains/fork-chains/chopsticks/get-started/){target=\_blank} installed -- Access to local or remote parachain endpoints -- An origin chain running runtime **`stable2503-5`** or later -- A TypeScript development environment with essential tools -- Familiarity with replaying or dry-running XCMs +Before you begin, ensure you have the following: -If you're new to replay or dry-run XCMs, see [Replay and Dry Run XCMs Using Chopsticks](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank}. +- [Node.js and npm installed](https://nodejs.org/en/download){target=\_blank}. +- [Chopsticks](/develop/toolkit/parachains/fork-chains/chopsticks/get-started/){target=\_blank} installed. +- Access to local or remote parachain endpoints. +- An origin chain running runtime `stable2503-5` or later. +- A TypeScript development environment with essential tools. +- Familiarity with [replaying or dry-running XCMs](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank}. -## Setting Up Your Workspace +## Set Up Your Workspace -1. Create a project directory: +1. Run the following command to create a new project directory: ```bash mkdir -p xcm-obs-demo && cd xcm-obs-demo ``` -2. Install Chopsticks globally: +2. Install Chopsticks globally using the command: ```bash npm install -g @acala-network/chopsticks@latest ``` -3. Download 1.6.0 runtime, which is built from **`stable2503-5` or later**: +3. Next, use the following command to download the 1.6.0 runtime, which is built from `stable2503-5` or later: ```bash mkdir -p wasms wget https://github.com/polkadot-fellows/runtimes/releases/download/v1.6.0/asset-hub-polkadot_runtime-v1006000.compact.compressed.wasm -O wasms/asset-hub-polkadot_v1.6.0.wasm ``` -4. Download config of Polkadot Hub: +4. Download the config for Polkadot Hub: ```bash mkdir -p configs @@ -68,15 +65,17 @@ If you're new to replay or dry-run XCMs, see [Replay and Dry Run XCMs Using Chop ... ``` -6. Fork the relevant chains locally using Chopsticks: +6. Use the following command to fork the relevant chains locally using Chopsticks: ```bash npx @acala-network/chopsticks xcm -r polkadot -p configs/polkadot-hub-override.yaml -p hydradx ``` - β†’ See the [Fork a Chain with Chopsticks guide](/tutorials/polkadot-sdk/testing/fork-live-chains/){target=\_blank} for detailed instructions. + !!! tip + + See the [Fork a Chain with Chopsticks](/tutorials/polkadot-sdk/testing/fork-live-chains/){target=\_blank} guide for detailed instructions. -7. Open a **new terminal** in the same folder and initialise a Node.js project: +7. Open a new terminal in the same folder and initialize a Node.js project: ```bash npm init -y && npm pkg set type="module" @@ -90,7 +89,7 @@ If you're new to replay or dry-run XCMs, see [Replay and Dry Run XCMs Using Chop npm install @noble/hashes ``` -9. Initialise TypeScript: +9. Initialize TypeScript using the command: ```bash npx tsc --init @@ -103,26 +102,32 @@ If you're new to replay or dry-run XCMs, see [Replay and Dry Run XCMs Using Chop npx papi add hydration -w ws://localhost:8001 ``` -## Scenario 1: XCM Flow with Implicit `SetTopic` +## XCM Flow with Implicit `SetTopic` -### Overview +Assume the following values for this scenario: -- **Origin:** Polkadot Hub -- **Destination:** Hydration -- **Extrinsic:** `limited_reserve_transfer_assets` (high-level) -- **Topic:** Set automatically by the runtime +- **Origin**: Polkadot Hub +- **Destination**: Hydration +- **Extrinsic**: `limited_reserve_transfer_assets` (high-level) +- **Topic**: Set automatically by the runtime -### Run the Script +Follow these steps to complete the implicit `SetTopic` flow: -Create and run `limited-reserve-transfer-assets.ts`: +1. Create a file named `limited-reserve-transfer-assets.ts` and add the following code: -```ts ---8<-- 'code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets.ts' -``` + ```ts + --8<-- 'code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets.ts' + ``` -```bash -npx tsx limited-reserve-transfer-assets.ts -``` +2. Run your script with the following command: + + ```bash + npx tsx limited-reserve-transfer-assets.ts + ``` + +3. You will see terminal output similar to the following: + + --8<-- 'code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets-result.html' ### Forwarded XCM Example @@ -137,32 +142,31 @@ The runtime adds a `SetTopic` to the forwarded XCM automatically: | Polkadot Hub | `PolkadotXcm.Sent` | `message_id` | Matches the topic in the forwarded XCM | | Hydration | `MessageQueue.Processed` | `id` | Matches origin's `message_id` | -> ⚠️ Dry run generated topics may differ from actual execution. +!!! note + Dry run-generated topics may differ from actual execution. -### Message Trace Output +## XCM Flow with Manual `SetTopic` ---8<-- 'code/tutorials/interoperability/xcm-observability-in-action/limited-reserve-transfer-assets-result.html' +Assume the following values for this scenario: -## Scenario 2: XCM Transfer with Manual `SetTopic` +- **Origin**: Polkadot Hub +- **Destination**: Hydration +- **Topic**: Manually assigned +- **Goal**: Ensure traceability in custom multi-hop flows -### Overview +Follow these steps to complete the manual `SetTopic` flow: -- **Origin:** Polkadot Hub -- **Destination:** Hydration -- **Topic:** Manually assigned -- **Goal:** Ensure traceability in custom multi-hop flows +1. Create a new file named `deposit-reserve-asset-with-set-topic.ts` and add the following code: -### Run the Script + ```ts + --8<-- 'code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic.ts' + ``` -Create and run `deposit-reserve-asset-with-set-topic.ts`: +2. Run your script with the following command: -```ts ---8<-- 'code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic.ts' -``` - -```bash -npx tsx deposit-reserve-asset-with-set-topic.ts -``` + ```bash + npx tsx deposit-reserve-asset-with-set-topic.ts + ``` ### Forwarded XCM Example @@ -243,3 +247,5 @@ This guide demonstrated: - The legacy workaround for older runtimes with derived IDs With these scenarios and debugging steps, you can confidently develop, trace, and troubleshoot XCM workflows across chains. + +To learn more before you begin, see the companion page: [XCM Observability](/develop/interoperability/xcm-observability){target=\_blank}. \ No newline at end of file From 58fcc29d517c0077e2d4b24f09900b8735a3264e Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Mon, 15 Sep 2025 15:18:17 -0400 Subject: [PATCH 3/6] update navs, formatting updates, etc. --- develop/interoperability/.nav.yml | 1 + develop/interoperability/xcm-observability.md | 111 ++++++++---------- .../xcm-observability-in-action.md | 82 ++++++------- 3 files changed, 92 insertions(+), 102 deletions(-) diff --git a/develop/interoperability/.nav.yml b/develop/interoperability/.nav.yml index 0e53c1d92..73be133c0 100644 --- a/develop/interoperability/.nav.yml +++ b/develop/interoperability/.nav.yml @@ -6,6 +6,7 @@ nav: - 'XCM Runtime Configuration': xcm-config.md - 'Send Messages': send-messages.md - 'XCM Runtime APIs': xcm-runtime-apis.md + - 'XCM Observability': xcm-observability.md - 'Test and Debug': test-and-debug.md - xcm-guides - versions diff --git a/develop/interoperability/xcm-observability.md b/develop/interoperability/xcm-observability.md index ea5340d3a..2a2955b6f 100644 --- a/develop/interoperability/xcm-observability.md +++ b/develop/interoperability/xcm-observability.md @@ -1,66 +1,59 @@ --- title: XCM Observability -description: A conceptual overview of XCM observability in Polkadot, covering message correlation, tracing, and debugging features in modern runtimes. +description: This conceptual overview of XCM observability in Polkadot covers message correlation, tracing, and debugging features in modern runtimes. --- # XCM Observability ## Introduction -Cross-Consensus Messaging (XCM) powers interoperability across the Polkadot ecosystem, but tracing and debugging these flows across multiple chains is challenging. +Cross-Consensus Messaging (XCM) enables interoperability across the Polkadot ecosystem; however, tracing and debugging these flows across multiple chains is a challenging task. This overview introduces the observability features in modern Polkadot runtimes and the Polkadot SDK that make XCMs easier to trace and debug, including: -This guide introduces the observability features in modern Polkadot runtimes and the Polkadot SDK that make XCMs easier to trace and debug. +- The [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} instruction and `message_id` to track XCMs across chains. +- The use of [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events to correlate messages. +- Derived message ID workarounds for older runtimes. +- The use of indexers and Chopsticks replay to diagnose failed or incomplete XCMs. -You'll learn how to: +!!! tip + For a hands-on walkthrough, see the companion tutorial: [XCM Observability in Action](/tutorials/interoperability/xcm-observability-in-action){target=\_blank}. -- Use the [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} instruction and `message_id` to track XCMs across chains -- Correlate messages through [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events -- Apply derived message ID workarounds for older runtimes -- Diagnose failed or incomplete XCMs with indexers and Chopsticks replay +## Topic Register -For a hands-on walkthrough, see the companion tutorial: [XCM Observability in Action](/tutorials/interoperability/xcm-observability-in-action){target=\_blank}. - -## Core Concepts - -### What is `SetTopic`? +[`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} is an XCM instruction that sets a 32-byte topic register inside the message. The topic serves as a logical identifier, or `message_id`, for XCM, allowing you to group and trace related messages across chains. When executing XCMs (via [`limited_reserve_transfer_assets`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.limited_reserve_transfer_assets){target=\_blank}, other extrinsics, or raw calls from the [`PolkadotXcm` pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html){target=\_blank}) on system chains, the runtime automatically appends a [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} instruction if one is missing. -- **[`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank}** is an XCM instruction that sets a 32-byte topic register inside the message. -- The topic acts as a **logical identifier** (`message_id`) for the XCM, allowing you to group and trace related messages across chains. - -⚠️ **Note:** When using [`WithUniqueTopic`](https://paritytech.github.io/polkadot-sdk/master/staging_xcm_builder/struct.WithUniqueTopic.html){target=\_blank}, the topic ID is guaranteed to be unique if `WithUniqueTopic` automatically appends the `SetTopic` instruction. However, if the message already includes a `SetTopic` instruction, the uniqueness of the ID depends on the message creator and is not guaranteed. +!!! note + When using [`WithUniqueTopic`](https://paritytech.github.io/polkadot-sdk/master/staging_xcm_builder/struct.WithUniqueTopic.html){target=\_blank}, the topic ID is guaranteed to be unique if `WithUniqueTopic` automatically appends the `SetTopic` instruction. However, if the message already includes a `SetTopic` instruction, the uniqueness of the ID depends on the message creator and is not guaranteed. -### Observability Features +## Observability Features -Runtimes built from **`stable2503-5` or later** provide key observability features for tracing and correlating XCMs across chains: +Runtimes built from `stable2503-5` or later provide key observability features for tracing and correlating XCMs across chains: -- **[`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank}** emitted on the origin chain when an XCM is sent. -- **[`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank}** emitted on the destination chain when the XCM is processed. -- The **`message_id` in the `Sent` event matches the `id` in the `Processed` event**, enabling reliable cross-chain correlation. This `message_id` is derived from the `SetTopic` instruction. +- [**`PolkadotXcm.Sent`**](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank}: Emitted on the origin chain when an XCM is sent. +- [**`MessageQueue.Processed`**](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank}: Emitted on the destination chain when the XCM is processed. +- The `message_id` in the `Sent` event matches the `id` in the `Processed` event, enabling reliable cross-chain correlation. This `message_id` is derived from the `SetTopic` instruction. | Chain Role | Event | Field | Purpose | |-------------------|--------------------------|--------------|------------------------------------| | Origin chain | `PolkadotXcm.Sent` | `message_id` | Identifies the sent XCM | | Destination chain | `MessageQueue.Processed` | `id` | Confirms processing of the message | -Matching these IDs lets you **correlate** an origin message with its destination processing. +Matching these IDs allows you to correlate an origin message with its destination processing. -### Why Not `XcmpQueue.XcmpMessageSent`? +!!! question "Why not use `XcmpQueue.XcmpMessageSent`?" + The event [`XcmpQueue.XcmpMessageSent`](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcmp_queue/pallet/enum.Event.html#variant.XcmpMessageSent){target=\_blank} contains a `message_hash` unrelated to `SetTopic`. It is not reliable for cross-chain tracing and should be avoided for observability purposes. -- The event [`XcmpQueue.XcmpMessageSent`](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcmp_queue/pallet/enum.Event.html#variant.XcmpMessageSent){target=\_blank} contains a `message_hash` unrelated to `SetTopic`. -- It is **not reliable for cross-chain tracing** and should be avoided for observability purposes. +## Automatic vs Manual `SetTopic` -### Automatic vs Manual `SetTopic` - -- The runtime **automatically appends a `SetTopic` instruction** if one is missing at the end of an XCM. -- When using high-level extrinsics such as `limited_reserve_transfer_assets`, **you do not need to set a topic manually**; the runtime handles it for you. -- If you **manually craft an XCM** (e.g., via [`execute`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.execute){target=\_blank} or [`send`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.send){target=\_blank}), you **can supply your own `SetTopic`**, but it **must be the final instruction** to be respected. -- In **multi-hop XCM flows**, manually setting the topic ensures **consistent tracing across all hops**. Any **remote XCM calls** embedded inside the XCM must also include a `SetTopic` at the end to preserve the same `message_id` throughout the cross-chain flow. +- The runtime automatically appends a `SetTopic` instruction if one is missing at the end of an XCM. +- When using high-level extrinsics such as `limited_reserve_transfer_assets`, you do not need to set a topic manually; the runtime handles it for you. +- If you manually craft an XCM (e.g., via [`execute`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.execute){target=\_blank} or [`send`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.send){target=\_blank}), you can supply your own `SetTopic`, but it must be the final instruction to be respected. +- In multi-hop XCM flows, manually setting the topic ensures consistent tracing across all hops. Any remote XCM calls embedded inside the XCM must also include a `SetTopic` at the end to preserve the same `message_id` throughout the cross-chain flow. ## Message Lifecycle -Here is a high-level overview of an XCM lifecycle with observability events: +The following is a high-level overview of an XCM lifecycle with observability events: ```mermaid flowchart TD @@ -81,13 +74,12 @@ flowchart TD Origin --> Destination ``` -* The `SetTopic` ensures a consistent `message_id` is passed and visible in these events. -* For multi-hop flows, the same `message_id` travels through all chains. +- The `SetTopic` ensures a consistent `message_id` is passed and visible in these events. +- For multi-hop flows, the same `message_id` travels through all chains. ## Workaround for Older Runtimes -* Runtimes prior to **`stable2503-5`** emit a **derived `forwarded_id`** instead of the original topic in downstream `Processed` events. -* The forwarded ID is calculated as: +Runtimes prior to `stable2503-5` emit a derived `forwarded_id` instead of the original topic in downstream `Processed` events. The forwarded ID is calculated as: ```rust fn forward_id_for(original_id: &XcmHash) -> XcmHash { @@ -101,7 +93,7 @@ fn forward_id_for(original_id: &XcmHash) -> XcmHash { |-----------------------|---------------------------------------------------------------| | `0x5c082b47...` | `0xb3ae32fd...` == blake2_256("forward_id_for" + original_id) | -Tools and indexers tracing messages across mixed runtime versions should check **both** the original and forwarded IDs. +Tools and indexers tracing messages across mixed runtime versions should check both the original and forwarded IDs. ```ts --8<-- 'code/develop/interoperability/xcm-observability/forward-id-for.ts' @@ -109,41 +101,38 @@ Tools and indexers tracing messages across mixed runtime versions should check * ## Failure Event Handling -When an XCM fails, the transaction **rolls back** and no explicit failure event is emitted on-chain. - -### How to Detect Failures - -1. **View Nested Dispatch Errors via Indexers** +When an XCM fails, the transaction rolls back and no explicit failure event is emitted on-chain. The following are some ways to check for XCM failure: - Most indexers display nested errors indicating why an XCM failed, e.g.: +1. **View nested dispatch errors via indexers**: Most indexers display nested errors indicating why an XCM failed, e.g.: --8<-- 'code/develop/interoperability/xcm-observability/execution-with-error.html' - Common errors include missing assets, exceeded execution limits, or invalid asset locations. This nested error reporting, introduced in runtimes from **`stable2506` onward**, usually suffices to diagnose typical issues. + Common errors include missing assets, exceeded execution limits, or invalid asset locations. This nested error reporting, introduced in runtimes from `stable2506` onward, usually suffices to diagnose typical issues. -2. **Replay Using Chopsticks for Full Logs** +2. **Replay using Chopsticks for full logs**: For detailed troubleshooting, try the following steps: - For detailed troubleshooting: + - Replay the failing extrinsic with logging enabled. + - See exactly which instruction failed and why. + - View complete error chains like `FailedToTransactAsset` or `AssetNotFound`. - - Replay the failing extrinsic with logging enabled - - See exactly which instruction failed and why - - View complete error chains like `FailedToTransactAsset` or `AssetNotFound` + This approach is invaluable for multi-hop flows and complex custom XCMs. - This approach is invaluable for multi-hop flows and complex custom XCMs. +## Recommended Debugging Workflow -### Recommended Debugging Workflow +Follow these steps when debugging XCM failures: -1. Start with indexer or API error output -2. If unclear, use Chopsticks to replay the exact transaction -3. Inspect logs for the failing XCM instruction and reason -4. Adjust weight limits, asset locations, or message construction as needed +1. Start with indexer or API error output. +2. If unclear, use Chopsticks to replay the exact transaction. +3. Inspect logs for the failing XCM instruction and reason. +4. Adjust weight limits, asset locations, or message construction as needed. -β†’ See [Replay and Dry Run XCMs Using Chopsticks](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank} for replay instructions. +!!! tip + See [Replay and Dry Run XCMs Using Chopsticks](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank} for replay instructions. ## Best Practices -- When manually setting `SetTopic`, **always place it as the final instruction** in your XCM to ensure it is respected by the runtime. -- Prefer **automatic topic insertion** via high-level extrinsics for simplicity. -- If your use case involves multi-hop or custom XCMs, **manually set `SetTopic`** (including remote XCM calls) to guarantee consistent tracing. -- Ensure your `message_id` values are unique **if** you require deduplication or strict correlation. +- When manually setting `SetTopic`, always place it as the final instruction in your XCM to ensure it is respected by the runtime. +- Prefer automatic topic insertion via high-level extrinsics for simplicity. +- If your use case involves multi-hop or custom XCMs, manually set `SetTopic` (including remote XCM calls) to guarantee consistent tracing. +- Ensure your `message_id` values are unique if you require deduplication or strict correlation. - When supporting legacy runtimes, be aware of the `forward_id_for` pattern. diff --git a/tutorials/interoperability/xcm-observability-in-action.md b/tutorials/interoperability/xcm-observability-in-action.md index 98c356e62..5bb5dd017 100644 --- a/tutorials/interoperability/xcm-observability-in-action.md +++ b/tutorials/interoperability/xcm-observability-in-action.md @@ -131,7 +131,7 @@ Follow these steps to complete the implicit `SetTopic` flow: ### Forwarded XCM Example -The runtime adds a `SetTopic` to the forwarded XCM automatically: +The following example illustrates the runtime adding a `SetTopic` to the forwarded XCM automatically: --8<-- 'code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm.html' @@ -168,84 +168,84 @@ Follow these steps to complete the manual `SetTopic` flow: npx tsx deposit-reserve-asset-with-set-topic.ts ``` +3. You will see terminal output similar to the following: + + --8<-- 'code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic-result.html' + ### Forwarded XCM Example -Your manual `SetTopic` is preserved by the runtime: +The following example illustrates that the runtime preserves the manual `SetTopic` value: --8<-- 'code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-custom-topic.html' -### Message Trace Output +## Multi-hop XCM Transfer with Manual `SetTopic` ---8<-- 'code/tutorials/interoperability/xcm-observability-in-action/deposit-reserve-asset-with-set-topic-result.html' +Assume the following values for this scenario: -## Scenario 3: Multi-hop XCM Transfer with Manual `SetTopic` +- **Origin**: Polkadot Hub +- **Destination**: Hydration +- **Topic**: Manually assigned and preserved over multiple hops (including remote XCMs) +- **Goal**: Enable consistent tracing across multi-hop XCM flows -### Overview +Follow these steps to complete the multi-hop manual `SetTopic` flow: -- **Origin:** Polkadot Hub -- **Destination:** Hydration -- **Topic:** Manually assigned and preserved over multiple hops (including remote XCMs) -- **Goal:** Enable consistent tracing across multi-hop XCM flows +1. Create a new file named `initiate-reserve-withdraw-with-set-topic.ts` and add the following code: -### Run the Script + ```ts + --8<-- 'code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic.ts' + ``` -Create and run `initiate-reserve-withdraw-with-set-topic.ts`: +2. Run your script with the following command: -```ts ---8<-- 'code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic.ts' -``` + ```bash + npx tsx initiate-reserve-withdraw-with-set-topic.ts + ``` -```bash -npx tsx initiate-reserve-withdraw-with-set-topic.ts -``` +3. You will see terminal output similar to the following. Note, the same `message_id` is present in all relevant events across chains: + + --8<-- 'code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic-result.html' ### Forwarded XCM Example (Hydration) -The runtime preserves your `SetTopic` throughout the multi-hop flow: +The following example illustrates how runtime preserves your `SetTopic` throughout the multi-hop flow: --8<-- 'code/tutorials/interoperability/xcm-observability-in-action/forwarded-xcm-remote-topic.html' -### End-to-End Message Trace Output - -The same `message_id` is present in all relevant events across chains: +## Script Troubleshooting Tips ---8<-- 'code/tutorials/interoperability/xcm-observability-in-action/initiate-reserve-withdraw-with-set-topic-result.html' +### Error: "Processed Message ID is `undefined`" -## Troubleshooting on Running Scripts - -### Processed Message ID is `undefined` - -If you see the following error when running a script: +This error usually means that the message has not yet been processed within the default retry window. If you see the following error when running a script: > ❌ Processed Message ID on Hydration is undefined. Try increasing MAX_RETRIES to wait for block finalisation. -This usually means that the message has not yet been processed within the default retry window. - -Increase the `MAX_RETRIES` value in your script to give the chain more time: +Update your script to increase the `MAX_RETRIES` value to give the chain more time: ```ts const MAX_RETRIES = 8; // Number of attempts to wait for block finalisation ``` -### `PolkadotXcm.Sent` Event Not Found +### Error: `PolkadotXcm.Sent` Event Not Found -If you encounter an error indicating that `PolkadotXcm.Sent` is unavailable: +If you encounter an error indicating that `PolkadotXcm.Sent` is unavailable, like the following: > ⚠️ PolkadotXcm.Sent is only available in runtimes built from stable2503-5 or later. -Ensure that `wasm-override` is updated to runtime version 1.6.0+, or to any runtime built from `stable2503-5` or later. +This error usually means that your runtime needs to be updated. Ensure that `wasm-override` is updated to runtime version 1.6.0+, or to any runtime built from `stable2503-5` or later. For details on updating your workspace, see [Setting Up Your Workspace](#setting-up-your-workspace). -## Summary +## Conclusion -This guide demonstrated: +Congratulations! By completing this guide, you should now understand: -- How `SetTopic` and `message_id` enable tracing and correlating XCMs across chains -- How to interpret and debug XCM failure cases -- How to manually and automatically manage topics for multi-hop flows -- The legacy workaround for older runtimes with derived IDs +- How `SetTopic` and `message_id` enable tracing and correlating XCMs across chains. +- How to interpret and debug XCM failure cases. +- How to manually and automatically manage topics for multi-hop flows. +- How to use the legacy workaround for older runtimes with derived IDs. With these scenarios and debugging steps, you can confidently develop, trace, and troubleshoot XCM workflows across chains. -To learn more before you begin, see the companion page: [XCM Observability](/develop/interoperability/xcm-observability){target=\_blank}. \ No newline at end of file +## Additional Resources + +To learn more about XCM Observability features and best practices, see [XCM Observability](/develop/interoperability/xcm-observability){target=\_blank}. \ No newline at end of file From 57bf86b541ff95e7d0620fe3ccf9760df92b54d9 Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Mon, 15 Sep 2025 15:26:21 -0400 Subject: [PATCH 4/6] fresh llms --- llms-full.txt | 375 +++++++++++++++++++++++++------------------------- llms.txt | 4 +- 2 files changed, 187 insertions(+), 192 deletions(-) diff --git a/llms-full.txt b/llms-full.txt index dc27dd58d..51b343508 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -3163,67 +3163,60 @@ Doc-Content: https://docs.polkadot.com/develop/interoperability/xcm-observabilit --- BEGIN CONTENT --- --- title: XCM Observability -description: A conceptual overview of XCM observability in Polkadot, covering message correlation, tracing, and debugging features in modern runtimes. +description: This conceptual overview of XCM observability in Polkadot covers message correlation, tracing, and debugging features in modern runtimes. --- # XCM Observability ## Introduction -Cross-Consensus Messaging (XCM) powers interoperability across the Polkadot ecosystem, but tracing and debugging these flows across multiple chains is challenging. +Cross-Consensus Messaging (XCM) enables interoperability across the Polkadot ecosystem; however, tracing and debugging these flows across multiple chains is a challenging task. This overview introduces the observability features in modern Polkadot runtimes and the Polkadot SDK that make XCMs easier to trace and debug, including: -This guide introduces the observability features in modern Polkadot runtimes and the Polkadot SDK that make XCMs easier to trace and debug. +- The [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} instruction and `message_id` to track XCMs across chains. +- The use of [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events to correlate messages. +- Derived message ID workarounds for older runtimes. +- The use of indexers and Chopsticks replay to diagnose failed or incomplete XCMs. -You'll learn how to: - -- Use the [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} instruction and `message_id` to track XCMs across chains -- Correlate messages through [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events -- Apply derived message ID workarounds for older runtimes -- Diagnose failed or incomplete XCMs with indexers and Chopsticks replay - -For a hands-on walkthrough, see the companion tutorial: [XCM Observability in Action](/tutorials/interoperability/xcm-observability-in-action){target=\_blank}. +!!! tip + For a hands-on walkthrough, see the companion tutorial: [XCM Observability in Action](/tutorials/interoperability/xcm-observability-in-action){target=\_blank}. -## Core Concepts +## Topic Register -### What is `SetTopic`? +[`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} is an XCM instruction that sets a 32-byte topic register inside the message. The topic serves as a logical identifier, or `message_id`, for XCM, allowing you to group and trace related messages across chains. When executing XCMs (via [`limited_reserve_transfer_assets`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.limited_reserve_transfer_assets){target=\_blank}, other extrinsics, or raw calls from the [`PolkadotXcm` pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html){target=\_blank}) on system chains, the runtime automatically appends a [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} instruction if one is missing. -- **[`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank}** is an XCM instruction that sets a 32-byte topic register inside the message. -- The topic acts as a **logical identifier** (`message_id`) for the XCM, allowing you to group and trace related messages across chains. - -⚠️ **Note:** When using [`WithUniqueTopic`](https://paritytech.github.io/polkadot-sdk/master/staging_xcm_builder/struct.WithUniqueTopic.html){target=\_blank}, the topic ID is guaranteed to be unique if `WithUniqueTopic` automatically appends the `SetTopic` instruction. However, if the message already includes a `SetTopic` instruction, the uniqueness of the ID depends on the message creator and is not guaranteed. +!!! note + When using [`WithUniqueTopic`](https://paritytech.github.io/polkadot-sdk/master/staging_xcm_builder/struct.WithUniqueTopic.html){target=\_blank}, the topic ID is guaranteed to be unique if `WithUniqueTopic` automatically appends the `SetTopic` instruction. However, if the message already includes a `SetTopic` instruction, the uniqueness of the ID depends on the message creator and is not guaranteed. -### Observability Features +## Observability Features -Runtimes built from **`stable2503-5` or later** provide key observability features for tracing and correlating XCMs across chains: +Runtimes built from `stable2503-5` or later provide key observability features for tracing and correlating XCMs across chains: -- **[`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank}** emitted on the origin chain when an XCM is sent. -- **[`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank}** emitted on the destination chain when the XCM is processed. -- The **`message_id` in the `Sent` event matches the `id` in the `Processed` event**, enabling reliable cross-chain correlation. This `message_id` is derived from the `SetTopic` instruction. +- [**`PolkadotXcm.Sent`**](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank}: Emitted on the origin chain when an XCM is sent. +- [**`MessageQueue.Processed`**](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank}: Emitted on the destination chain when the XCM is processed. +- The `message_id` in the `Sent` event matches the `id` in the `Processed` event, enabling reliable cross-chain correlation. This `message_id` is derived from the `SetTopic` instruction. | Chain Role | Event | Field | Purpose | |-------------------|--------------------------|--------------|------------------------------------| | Origin chain | `PolkadotXcm.Sent` | `message_id` | Identifies the sent XCM | | Destination chain | `MessageQueue.Processed` | `id` | Confirms processing of the message | -Matching these IDs lets you **correlate** an origin message with its destination processing. - -### Why Not `XcmpQueue.XcmpMessageSent`? +Matching these IDs allows you to correlate an origin message with its destination processing. -- The event [`XcmpQueue.XcmpMessageSent`](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcmp_queue/pallet/enum.Event.html#variant.XcmpMessageSent){target=\_blank} contains a `message_hash` unrelated to `SetTopic`. -- It is **not reliable for cross-chain tracing** and should be avoided for observability purposes. +!!! question "Why not use `XcmpQueue.XcmpMessageSent`?" + The event [`XcmpQueue.XcmpMessageSent`](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcmp_queue/pallet/enum.Event.html#variant.XcmpMessageSent){target=\_blank} contains a `message_hash` unrelated to `SetTopic`. It is not reliable for cross-chain tracing and should be avoided for observability purposes. -### Automatic vs Manual `SetTopic` +## Automatic vs Manual `SetTopic` -- The runtime **automatically appends a `SetTopic` instruction** if one is missing at the end of an XCM. -- When using high-level extrinsics such as `limited_reserve_transfer_assets`, **you do not need to set a topic manually**; the runtime handles it for you. -- If you **manually craft an XCM** (e.g., via [`execute`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.execute){target=\_blank} or [`send`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.send){target=\_blank}), you **can supply your own `SetTopic`**, but it **must be the final instruction** to be respected. -- In **multi-hop XCM flows**, manually setting the topic ensures **consistent tracing across all hops**. Any **remote XCM calls** embedded inside the XCM must also include a `SetTopic` at the end to preserve the same `message_id` throughout the cross-chain flow. +- The runtime automatically appends a `SetTopic` instruction if one is missing at the end of an XCM. +- When using high-level extrinsics such as `limited_reserve_transfer_assets`, you do not need to set a topic manually; the runtime handles it for you. +- If you manually craft an XCM (e.g., via [`execute`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.execute){target=\_blank} or [`send`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.send){target=\_blank}), you can supply your own `SetTopic`, but it must be the final instruction to be respected. +- In multi-hop XCM flows, manually setting the topic ensures consistent tracing across all hops. Any remote XCM calls embedded inside the XCM must also include a `SetTopic` at the end to preserve the same `message_id` throughout the cross-chain flow. ## Message Lifecycle -Here is a high-level overview of an XCM lifecycle with observability events: +The following is a high-level overview of an XCM lifecycle with observability events: ```mermaid flowchart TD @@ -3244,13 +3237,12 @@ flowchart TD Origin --> Destination ``` -* The `SetTopic` ensures a consistent `message_id` is passed and visible in these events. -* For multi-hop flows, the same `message_id` travels through all chains. +- The `SetTopic` ensures a consistent `message_id` is passed and visible in these events. +- For multi-hop flows, the same `message_id` travels through all chains. ## Workaround for Older Runtimes -* Runtimes prior to **`stable2503-5`** emit a **derived `forwarded_id`** instead of the original topic in downstream `Processed` events. -* The forwarded ID is calculated as: +Runtimes prior to `stable2503-5` emit a derived `forwarded_id` instead of the original topic in downstream `Processed` events. The forwarded ID is calculated as: ```rust fn forward_id_for(original_id: &XcmHash) -> XcmHash { @@ -3264,7 +3256,7 @@ fn forward_id_for(original_id: &XcmHash) -> XcmHash { |-----------------------|---------------------------------------------------------------| | `0x5c082b47...` | `0xb3ae32fd...` == blake2_256("forward_id_for" + original_id) | -Tools and indexers tracing messages across mixed runtime versions should check **both** the original and forwarded IDs. +Tools and indexers tracing messages across mixed runtime versions should check both the original and forwarded IDs. ```ts import {blake2b} from "@noble/hashes/blake2"; @@ -3303,13 +3295,9 @@ if (forwardedIdHex === expectedForwardedId) { ## Failure Event Handling -When an XCM fails, the transaction **rolls back** and no explicit failure event is emitted on-chain. - -### How to Detect Failures - -1. **View Nested Dispatch Errors via Indexers** +When an XCM fails, the transaction rolls back and no explicit failure event is emitted on-chain. The following are some ways to check for XCM failure: - Most indexers display nested errors indicating why an XCM failed, e.g.: +1. **View nested dispatch errors via indexers**: Most indexers display nested errors indicating why an XCM failed, e.g.:
@@ -3331,33 +3319,34 @@ When an XCM fails, the transaction **rolls back** and no explicit failure event
   
- Common errors include missing assets, exceeded execution limits, or invalid asset locations. This nested error reporting, introduced in runtimes from **`stable2506` onward**, usually suffices to diagnose typical issues. + Common errors include missing assets, exceeded execution limits, or invalid asset locations. This nested error reporting, introduced in runtimes from `stable2506` onward, usually suffices to diagnose typical issues. -2. **Replay Using Chopsticks for Full Logs** +2. **Replay using Chopsticks for full logs**: For detailed troubleshooting, try the following steps: - For detailed troubleshooting: + - Replay the failing extrinsic with logging enabled. + - See exactly which instruction failed and why. + - View complete error chains like `FailedToTransactAsset` or `AssetNotFound`. - - Replay the failing extrinsic with logging enabled - - See exactly which instruction failed and why - - View complete error chains like `FailedToTransactAsset` or `AssetNotFound` + This approach is invaluable for multi-hop flows and complex custom XCMs. - This approach is invaluable for multi-hop flows and complex custom XCMs. +## Recommended Debugging Workflow -### Recommended Debugging Workflow +Follow these steps when debugging XCM failures: -1. Start with indexer or API error output -2. If unclear, use Chopsticks to replay the exact transaction -3. Inspect logs for the failing XCM instruction and reason -4. Adjust weight limits, asset locations, or message construction as needed +1. Start with indexer or API error output. +2. If unclear, use Chopsticks to replay the exact transaction. +3. Inspect logs for the failing XCM instruction and reason. +4. Adjust weight limits, asset locations, or message construction as needed. -β†’ See [Replay and Dry Run XCMs Using Chopsticks](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank} for replay instructions. +!!! tip + See [Replay and Dry Run XCMs Using Chopsticks](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank} for replay instructions. ## Best Practices -- When manually setting `SetTopic`, **always place it as the final instruction** in your XCM to ensure it is respected by the runtime. -- Prefer **automatic topic insertion** via high-level extrinsics for simplicity. -- If your use case involves multi-hop or custom XCMs, **manually set `SetTopic`** (including remote XCM calls) to guarantee consistent tracing. -- Ensure your `message_id` values are unique **if** you require deduplication or strict correlation. +- When manually setting `SetTopic`, always place it as the final instruction in your XCM to ensure it is respected by the runtime. +- Prefer automatic topic insertion via high-level extrinsics for simplicity. +- If your use case involves multi-hop or custom XCMs, manually set `SetTopic` (including remote XCM calls) to guarantee consistent tracing. +- Ensure your `message_id` values are unique if you require deduplication or strict correlation. - When supporting legacy runtimes, be aware of the `forward_id_for` pattern. --- END CONTENT --- @@ -31074,57 +31063,54 @@ Doc-Content: https://docs.polkadot.com/tutorials/interoperability/xcm-observabil --- BEGIN CONTENT --- --- title: XCM Observability in Action -description: A hands-on guide to tracing, correlating, and debugging cross-chain XCMs using observability tools in the Polkadot SDK. +description: Follow this step-by-step guide to trace, correlate, and debug cross-chain XCMs using observability tools in the Polkadot SDK. --- # XCM Observability in Action ## Introduction -Cross-Consensus Messaging (XCM) powers interoperability in the Polkadot ecosystem, but tracing flows across multiple chains is challenging in practice. - -This tutorial walks through a **hands-on scenario**: sending assets between parachains and tracing the resulting XCM across origin and destination chains. Along the way, you will: +Cross-Consensus Messaging (XCM) enables interoperability within the Polkadot ecosystem; however, tracing flows across multiple chains is challenging in practice. -- Capture `message_id` and [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} for tracking -- Correlate [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events across chains -- Apply manual topic tagging for custom multi-hop flows +Follow this tutorial to send assets between parachains and trace the resulting XCM across the origin and destination chains. By completing this tutorial, you will: -For background concepts and best practices, see the companion page: [XCM Observability](/develop/interoperability/xcm-observability){target=\_blank}. +- Capture `message_id` and [`SetTopic([u8; 32])`](https://github.com/polkadot-fellows/xcm-format#settopic){target=\_blank} for tracking. +- Correlate [`PolkadotXcm.Sent`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/enum.Event.html#variant.Sent){target=\_blank} and [`MessageQueue.Processed`](https://paritytech.github.io/polkadot-sdk/master/pallet_message_queue/pallet/enum.Event.html#variant.Processed){target=\_blank} events across chains. +- Apply manual topic tagging for custom multi-hop flows. ## Prerequisites -Before you begin, make sure you've: +Before you begin, ensure you have the following: -- [Chopsticks](/develop/toolkit/parachains/fork-chains/chopsticks/get-started/){target=\_blank} installed -- Access to local or remote parachain endpoints -- An origin chain running runtime **`stable2503-5`** or later -- A TypeScript development environment with essential tools -- Familiarity with replaying or dry-running XCMs +- [Node.js and npm installed](https://nodejs.org/en/download){target=\_blank}. +- [Chopsticks](/develop/toolkit/parachains/fork-chains/chopsticks/get-started/){target=\_blank} installed. +- Access to local or remote parachain endpoints. +- An origin chain running runtime `stable2503-5` or later. +- A TypeScript development environment with essential tools. +- Familiarity with [replaying or dry-running XCMs](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank}. -If you're new to replay or dry-run XCMs, see [Replay and Dry Run XCMs Using Chopsticks](/tutorials/interoperability/replay-and-dry-run-xcms/){target=\_blank}. +## Set Up Your Workspace -## Setting Up Your Workspace - -1. Create a project directory: +1. Run the following command to create a new project directory: ```bash mkdir -p xcm-obs-demo && cd xcm-obs-demo ``` -2. Install Chopsticks globally: +2. Install Chopsticks globally using the command: ```bash npm install -g @acala-network/chopsticks@latest ``` -3. Download 1.6.0 runtime, which is built from **`stable2503-5` or later**: +3. Next, use the following command to download the 1.6.0 runtime, which is built from `stable2503-5` or later: ```bash mkdir -p wasms wget https://github.com/polkadot-fellows/runtimes/releases/download/v1.6.0/asset-hub-polkadot_runtime-v1006000.compact.compressed.wasm -O wasms/asset-hub-polkadot_v1.6.0.wasm ``` -4. Download config of Polkadot Hub: +4. Download the config for Polkadot Hub: ```bash mkdir -p configs @@ -31142,15 +31128,17 @@ If you're new to replay or dry-run XCMs, see [Replay and Dry Run XCMs Using Chop ... ``` -6. Fork the relevant chains locally using Chopsticks: +6. Use the following command to fork the relevant chains locally using Chopsticks: ```bash npx @acala-network/chopsticks xcm -r polkadot -p configs/polkadot-hub-override.yaml -p hydradx ``` - β†’ See the [Fork a Chain with Chopsticks guide](/tutorials/polkadot-sdk/testing/fork-live-chains/){target=\_blank} for detailed instructions. + !!! tip + + See the [Fork a Chain with Chopsticks](/tutorials/polkadot-sdk/testing/fork-live-chains/){target=\_blank} guide for detailed instructions. -7. Open a **new terminal** in the same folder and initialise a Node.js project: +7. Open a new terminal in the same folder and initialize a Node.js project: ```bash npm init -y && npm pkg set type="module" @@ -31164,7 +31152,7 @@ If you're new to replay or dry-run XCMs, see [Replay and Dry Run XCMs Using Chop npm install @noble/hashes ``` -9. Initialise TypeScript: +9. Initialize TypeScript using the command: ```bash npx tsc --init @@ -31177,21 +31165,21 @@ If you're new to replay or dry-run XCMs, see [Replay and Dry Run XCMs Using Chop npx papi add hydration -w ws://localhost:8001 ``` -## Scenario 1: XCM Flow with Implicit `SetTopic` +## XCM Flow with Implicit `SetTopic` -### Overview +Assume the following values for this scenario: -- **Origin:** Polkadot Hub -- **Destination:** Hydration -- **Extrinsic:** `limited_reserve_transfer_assets` (high-level) -- **Topic:** Set automatically by the runtime +- **Origin**: Polkadot Hub +- **Destination**: Hydration +- **Extrinsic**: `limited_reserve_transfer_assets` (high-level) +- **Topic**: Set automatically by the runtime -### Run the Script +Follow these steps to complete the implicit `SetTopic` flow: -Create and run `limited-reserve-transfer-assets.ts`: +1. Create a file named `limited-reserve-transfer-assets.ts` and add the following code: -```ts -import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; + ```ts + import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; import {withPolkadotSdkCompat} from "polkadot-api/polkadot-sdk-compat"; import {getPolkadotSigner} from "polkadot-api/signer"; import {getWsProvider} from "polkadot-api/ws-provider/web"; @@ -31377,15 +31365,29 @@ async function main() { } main().catch(console.error); -``` + ``` -```bash -npx tsx limited-reserve-transfer-assets.ts -``` +2. Run your script with the following command: + + ```bash + npx tsx limited-reserve-transfer-assets.ts + ``` + +3. You will see terminal output similar to the following: + +
+ npx tsx limited-reserve-transfer-assets.ts + βœ… Local dry run successful. + πŸ“¦ Finalised on Polkadot Hub in block #9477291: 0xf54cecc017762c714bbdf3e82d72ed90886257ca17d32ec6dc8ea20e28110af8 + πŸ“£ Last message sent on Polkadot Hub: 0x20432393771dc049cea4900565a936d169b8ebdd64efa351890766df918615a4 + πŸ“¦ Finalised on Hydration in block #8761211: 0xa4c493ba9328f38174aa7a9ade0779654839e9d3c83b2bafc60d4e5b7de6a00f + πŸ“£ Last message processed on Hydration: 0x20432393771dc049cea4900565a936d169b8ebdd64efa351890766df918615a4 + βœ… Processed Message ID on Hydration matched. +
### Forwarded XCM Example -The runtime adds a `SetTopic` to the forwarded XCM automatically: +The following example illustrates the runtime adding a `SetTopic` to the forwarded XCM automatically:
@@ -31420,35 +31422,24 @@ The runtime adds a `SetTopic` to the forwarded XCM automatically:
 | Polkadot Hub | `PolkadotXcm.Sent`       | `message_id` | Matches the topic in the forwarded XCM |
 | Hydration    | `MessageQueue.Processed` | `id`         | Matches origin's `message_id`          |
 
-> ⚠️ Dry run generated topics may differ from actual execution.
-
-### Message Trace Output
-
-
- npx tsx limited-reserve-transfer-assets.ts - βœ… Local dry run successful. - πŸ“¦ Finalised on Polkadot Hub in block #9477291: 0xf54cecc017762c714bbdf3e82d72ed90886257ca17d32ec6dc8ea20e28110af8 - πŸ“£ Last message sent on Polkadot Hub: 0x20432393771dc049cea4900565a936d169b8ebdd64efa351890766df918615a4 - πŸ“¦ Finalised on Hydration in block #8761211: 0xa4c493ba9328f38174aa7a9ade0779654839e9d3c83b2bafc60d4e5b7de6a00f - πŸ“£ Last message processed on Hydration: 0x20432393771dc049cea4900565a936d169b8ebdd64efa351890766df918615a4 - βœ… Processed Message ID on Hydration matched. -
+!!! note + Dry run-generated topics may differ from actual execution. -## Scenario 2: XCM Transfer with Manual `SetTopic` +## XCM Flow with Manual `SetTopic` -### Overview +Assume the following values for this scenario: -- **Origin:** Polkadot Hub -- **Destination:** Hydration -- **Topic:** Manually assigned -- **Goal:** Ensure traceability in custom multi-hop flows +- **Origin**: Polkadot Hub +- **Destination**: Hydration +- **Topic**: Manually assigned +- **Goal**: Ensure traceability in custom multi-hop flows -### Run the Script +Follow these steps to complete the manual `SetTopic` flow: -Create and run `deposit-reserve-asset-with-set-topic.ts`: +1. Create a new file named `deposit-reserve-asset-with-set-topic.ts` and add the following code: -```ts -import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; + ```ts + import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; import {withPolkadotSdkCompat} from "polkadot-api/polkadot-sdk-compat"; import {getPolkadotSigner} from "polkadot-api/signer"; import {getWsProvider} from "polkadot-api/ws-provider/web"; @@ -31684,15 +31675,30 @@ async function main() { } main().catch(console.error); -``` + ``` -```bash -npx tsx deposit-reserve-asset-with-set-topic.ts -``` +2. Run your script with the following command: + + ```bash + npx tsx deposit-reserve-asset-with-set-topic.ts + ``` + +3. You will see terminal output similar to the following: + +
+ npx tsx limited-reserve-transfer-assets.ts + βœ… Local dry run successful. + πŸ“¦ Finalised on Polkadot Hub in block #9471830: 0x98bd858739b3b5dd558def60cbd85d5e7fb2f4e33b0c00e1895e316541d727d9 + πŸ“£ Last message sent on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Sent Message ID on Polkadot Hub matched. + πŸ“¦ Finalised on Hydration in block #8749233: 0xe1413c5126698d7189d6f55a38e62d07ea4915078c2b1f3914d70f670e79e162 + πŸ“£ Last message processed on Hydration: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Processed Message ID on Hydration matched. +
### Forwarded XCM Example -Your manual `SetTopic` is preserved by the runtime: +The following example illustrates that the runtime preserves the manual `SetTopic` value:
@@ -31720,34 +31726,21 @@ Your manual `SetTopic` is preserved by the runtime:
   
-### Message Trace Output - -
- npx tsx limited-reserve-transfer-assets.ts - βœ… Local dry run successful. - πŸ“¦ Finalised on Polkadot Hub in block #9471830: 0x98bd858739b3b5dd558def60cbd85d5e7fb2f4e33b0c00e1895e316541d727d9 - πŸ“£ Last message sent on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 - βœ… Sent Message ID on Polkadot Hub matched. - πŸ“¦ Finalised on Hydration in block #8749233: 0xe1413c5126698d7189d6f55a38e62d07ea4915078c2b1f3914d70f670e79e162 - πŸ“£ Last message processed on Hydration: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 - βœ… Processed Message ID on Hydration matched. -
- -## Scenario 3: Multi-hop XCM Transfer with Manual `SetTopic` +## Multi-hop XCM Transfer with Manual `SetTopic` -### Overview +Assume the following values for this scenario: -- **Origin:** Polkadot Hub -- **Destination:** Hydration -- **Topic:** Manually assigned and preserved over multiple hops (including remote XCMs) -- **Goal:** Enable consistent tracing across multi-hop XCM flows +- **Origin**: Polkadot Hub +- **Destination**: Hydration +- **Topic**: Manually assigned and preserved over multiple hops (including remote XCMs) +- **Goal**: Enable consistent tracing across multi-hop XCM flows -### Run the Script +Follow these steps to complete the multi-hop manual `SetTopic` flow: -Create and run `initiate-reserve-withdraw-with-set-topic.ts`: +1. Create a new file named `initiate-reserve-withdraw-with-set-topic.ts` and add the following code: -```ts -import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; + ```ts + import {Binary, createClient, Enum, type BlockInfo, type PolkadotClient} from "polkadot-api"; import {withPolkadotSdkCompat} from "polkadot-api/polkadot-sdk-compat"; import {getPolkadotSigner} from "polkadot-api/signer"; import {getWsProvider} from "polkadot-api/ws-provider/web"; @@ -32038,15 +32031,33 @@ async function main() { } main().catch(console.error); -``` + ``` -```bash -npx tsx initiate-reserve-withdraw-with-set-topic.ts -``` +2. Run your script with the following command: + + ```bash + npx tsx initiate-reserve-withdraw-with-set-topic.ts + ``` + +3. You will see terminal output similar to the following. Note, the same `message_id` is present in all relevant events across chains: + +
+ npx tsx initiate-reserve-withdraw-with-set-topic.ts + βœ… Local dry run successful. + πŸ“¦ Finalised on Polkadot Hub in block #9471831: 0x2620f7e29765fc953263b7835711011616702c9d82ef5306fe3ef4196cb75cab + πŸ“£ Last message sent on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Sent Message ID on Polkadot Hub matched. + πŸ“¦ Finalised on Hydration in block #8749235: 0xafe7f6149b1773a8d3d229040cda414aafd64baaeffa37fb4a5b2a542308b2d6 + πŸ“£ Last message processed on Hydration: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Processed Message ID on Hydration matched. + πŸ“¦ Finalised on Polkadot Hub in block #9471832: 0x7c150b69e3562694f0573e4fee73dfb86f3ab71b808679a1777586ff24643e9a + πŸ“£ Last message processed on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 + βœ… Processed Message ID on Polkadot Hub matched. +
### Forwarded XCM Example (Hydration) -The runtime preserves your `SetTopic` throughout the multi-hop flow: +The following example illustrates how runtime preserves your `SetTopic` throughout the multi-hop flow:
@@ -32095,60 +32106,44 @@ The runtime preserves your `SetTopic` throughout the multi-hop flow:
   
-### End-to-End Message Trace Output - -The same `message_id` is present in all relevant events across chains: - -
- npx tsx initiate-reserve-withdraw-with-set-topic.ts - βœ… Local dry run successful. - πŸ“¦ Finalised on Polkadot Hub in block #9471831: 0x2620f7e29765fc953263b7835711011616702c9d82ef5306fe3ef4196cb75cab - πŸ“£ Last message sent on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 - βœ… Sent Message ID on Polkadot Hub matched. - πŸ“¦ Finalised on Hydration in block #8749235: 0xafe7f6149b1773a8d3d229040cda414aafd64baaeffa37fb4a5b2a542308b2d6 - πŸ“£ Last message processed on Hydration: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 - βœ… Processed Message ID on Hydration matched. - πŸ“¦ Finalised on Polkadot Hub in block #9471832: 0x7c150b69e3562694f0573e4fee73dfb86f3ab71b808679a1777586ff24643e9a - πŸ“£ Last message processed on Polkadot Hub: 0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2 - βœ… Processed Message ID on Polkadot Hub matched. -
- -## Troubleshooting on Running Scripts +## Script Troubleshooting Tips -### Processed Message ID is `undefined` +### Error: "Processed Message ID is `undefined`" -If you see the following error when running a script: +This error usually means that the message has not yet been processed within the default retry window. If you see the following error when running a script: > ❌ Processed Message ID on Hydration is undefined. Try increasing MAX_RETRIES to wait for block finalisation. -This usually means that the message has not yet been processed within the default retry window. - -Increase the `MAX_RETRIES` value in your script to give the chain more time: +Update your script to increase the `MAX_RETRIES` value to give the chain more time: ```ts const MAX_RETRIES = 8; // Number of attempts to wait for block finalisation ``` -### `PolkadotXcm.Sent` Event Not Found +### Error: `PolkadotXcm.Sent` Event Not Found -If you encounter an error indicating that `PolkadotXcm.Sent` is unavailable: +If you encounter an error indicating that `PolkadotXcm.Sent` is unavailable, like the following: > ⚠️ PolkadotXcm.Sent is only available in runtimes built from stable2503-5 or later. -Ensure that `wasm-override` is updated to runtime version 1.6.0+, or to any runtime built from `stable2503-5` or later. +This error usually means that your runtime needs to be updated. Ensure that `wasm-override` is updated to runtime version 1.6.0+, or to any runtime built from `stable2503-5` or later. For details on updating your workspace, see [Setting Up Your Workspace](#setting-up-your-workspace). -## Summary +## Conclusion -This guide demonstrated: +Congratulations! By completing this guide, you should now understand: -- How `SetTopic` and `message_id` enable tracing and correlating XCMs across chains -- How to interpret and debug XCM failure cases -- How to manually and automatically manage topics for multi-hop flows -- The legacy workaround for older runtimes with derived IDs +- How `SetTopic` and `message_id` enable tracing and correlating XCMs across chains. +- How to interpret and debug XCM failure cases. +- How to manually and automatically manage topics for multi-hop flows. +- How to use the legacy workaround for older runtimes with derived IDs. With these scenarios and debugging steps, you can confidently develop, trace, and troubleshoot XCM workflows across chains. + +## Additional Resources + +To learn more about XCM Observability features and best practices, see [XCM Observability](/develop/interoperability/xcm-observability){target=\_blank}. --- END CONTENT --- Doc-Content: https://docs.polkadot.com/tutorials/interoperability/xcm-transfers/from-relaychain-to-parachain/ diff --git a/llms.txt b/llms.txt index 4e2a1d7ad..708d07483 100644 --- a/llms.txt +++ b/llms.txt @@ -25,7 +25,7 @@ - [Transact](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/from-apps/transact.md): Learn how to execute arbitrary calls on remote chains using the Transact instruction, enabling cross-chain function execution and remote pallet interactions. - [Transfers](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/from-apps/transfers.md): Learn how to perform cross-chain asset transfers using XCM, including teleport, reserve transfers, and handling different asset types across parachains. - [XCM Guides](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/index.md): Comprehensive guides for implementing XCM functionality in applications and understanding cross-chain interactions. -- [XCM Observability](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-observability.md): A conceptual overview of XCM observability in Polkadot, covering message correlation, tracing, and debugging features in modern runtimes. +- [XCM Observability](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-observability.md): This conceptual overview of XCM observability in Polkadot covers message correlation, tracing, and debugging features in modern runtimes. - [XCM Runtime APIs](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-runtime-apis.md): Learn about XCM Runtime APIs in Polkadot for cross-chain communication. Explore the APIs to simulate and test XCM messages before execution on the network. - [Networks](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/networks.md): Explore the Polkadot ecosystem networks and learn the unique purposes of each, tailored for blockchain innovation, testing, and enterprise-grade solutions. - [Add a Pallet to the Runtime](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/parachains/customize-parachain/add-existing-pallets.md): Learn how to include and configure pallets in a Polkadot SDK-based runtime, from adding dependencies to implementing necessary traits. @@ -182,7 +182,7 @@ - [Opening HRMP Channels Between Parachains](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-channels/para-to-para.md): Learn how to open HRMP channels between parachains on Polkadot. Discover the step-by-step process for establishing uni- and bidirectional communication. - [Opening HRMP Channels with System Parachains](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-channels/para-to-system.md): Learn how to open HRMP channels with Polkadot system parachains. Discover the process for establishing bi-directional communication using a single XCM message. - [XCM Fee Estimation](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-fee-estimation.md): This tutorial demonstrates how to estimate the fees for teleporting assets from the Paseo Asset Hub parachain to the Paseo Bridge Hub chain. -- [XCM Observability in Action](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-observability-in-action.md): A hands-on guide to tracing, correlating, and debugging cross-chain XCMs using observability tools in the Polkadot SDK. +- [XCM Observability in Action](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-observability-in-action.md): Follow this step-by-step guide to trace, correlate, and debug cross-chain XCMs using observability tools in the Polkadot SDK. - [XCM Transfers from Relay Chain to Parachain](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-transfers/from-relaychain-to-parachain.md): Learn how to perform a reserve-backed asset transfer between a relay chain and a parachain using XCM for cross-chain interoperability. - [XCM Transfers](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/interoperability/xcm-transfers/index.md): Explore tutorials on performing transfers between different consensus systems using XCM technology to enable cross-chain interoperability. - [Fast Track a Governance Proposal](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/tutorials/onchain-governance/fast-track-gov-proposal.md): Learn how to fast-track governance proposals in Polkadot's OpenGov using Chopsticks. Simulate, test, and execute proposals confidently. From 5551dd360536f1d8f3760e1e9e7b0d35fc62cca6 Mon Sep 17 00:00:00 2001 From: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:27:38 -0400 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- develop/interoperability/xcm-observability.md | 2 +- tutorials/interoperability/xcm-observability-in-action.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/develop/interoperability/xcm-observability.md b/develop/interoperability/xcm-observability.md index 2a2955b6f..9ea065b0f 100644 --- a/develop/interoperability/xcm-observability.md +++ b/develop/interoperability/xcm-observability.md @@ -1,6 +1,6 @@ --- title: XCM Observability -description: This conceptual overview of XCM observability in Polkadot covers message correlation, tracing, and debugging features in modern runtimes. +description: A conceptual overview of XCM observability in Polkadot covers message correlation, tracing, and debugging features in modern runtimes. --- # XCM Observability diff --git a/tutorials/interoperability/xcm-observability-in-action.md b/tutorials/interoperability/xcm-observability-in-action.md index 5bb5dd017..6a9d16294 100644 --- a/tutorials/interoperability/xcm-observability-in-action.md +++ b/tutorials/interoperability/xcm-observability-in-action.md @@ -47,7 +47,7 @@ Before you begin, ensure you have the following: wget https://github.com/polkadot-fellows/runtimes/releases/download/v1.6.0/asset-hub-polkadot_runtime-v1006000.compact.compressed.wasm -O wasms/asset-hub-polkadot_v1.6.0.wasm ``` -4. Download the config for Polkadot Hub: +4. Download the config file for Polkadot Hub: ```bash mkdir -p configs From ef9329b6f937f8b162770fd4ba1cd24cab8ee08e Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:33:05 +0100 Subject: [PATCH 6/6] Update wording on manual `SetTopic` (#1002) * Update wording for custom SetTopic * Update wording for custom SetTopic * Update wording for custom SetTopic --- develop/interoperability/xcm-observability.md | 4 +++- llms-full.txt | 10 ++++++---- llms.txt | 2 +- .../interoperability/xcm-observability-in-action.md | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/develop/interoperability/xcm-observability.md b/develop/interoperability/xcm-observability.md index 9ea065b0f..28104830c 100644 --- a/develop/interoperability/xcm-observability.md +++ b/develop/interoperability/xcm-observability.md @@ -48,7 +48,7 @@ Matching these IDs allows you to correlate an origin message with its destinatio - The runtime automatically appends a `SetTopic` instruction if one is missing at the end of an XCM. - When using high-level extrinsics such as `limited_reserve_transfer_assets`, you do not need to set a topic manually; the runtime handles it for you. -- If you manually craft an XCM (e.g., via [`execute`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.execute){target=\_blank} or [`send`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.send){target=\_blank}), you can supply your own `SetTopic`, but it must be the final instruction to be respected. +- When manually crafting an XCM via [`execute`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.execute){target=\_blank} or [`send`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.send){target=\_blank}), you can include a `SetTopic` as the final instruction; otherwise, `WithUniqueTopic` will append its own unique ID, overriding earlier `SetTopic` instructions. - In multi-hop XCM flows, manually setting the topic ensures consistent tracing across all hops. Any remote XCM calls embedded inside the XCM must also include a `SetTopic` at the end to preserve the same `message_id` throughout the cross-chain flow. ## Message Lifecycle @@ -99,6 +99,8 @@ Tools and indexers tracing messages across mixed runtime versions should check b --8<-- 'code/develop/interoperability/xcm-observability/forward-id-for.ts' ``` +> Note: `@noble/hashes` and `polkadot-api` are required dependencies for this code to work. + ## Failure Event Handling When an XCM fails, the transaction rolls back and no explicit failure event is emitted on-chain. The following are some ways to check for XCM failure: diff --git a/llms-full.txt b/llms-full.txt index 51b343508..49bca7a77 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -3163,7 +3163,7 @@ Doc-Content: https://docs.polkadot.com/develop/interoperability/xcm-observabilit --- BEGIN CONTENT --- --- title: XCM Observability -description: This conceptual overview of XCM observability in Polkadot covers message correlation, tracing, and debugging features in modern runtimes. +description: A conceptual overview of XCM observability in Polkadot covers message correlation, tracing, and debugging features in modern runtimes. --- # XCM Observability @@ -3211,7 +3211,7 @@ Matching these IDs allows you to correlate an origin message with its destinatio - The runtime automatically appends a `SetTopic` instruction if one is missing at the end of an XCM. - When using high-level extrinsics such as `limited_reserve_transfer_assets`, you do not need to set a topic manually; the runtime handles it for you. -- If you manually craft an XCM (e.g., via [`execute`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.execute){target=\_blank} or [`send`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.send){target=\_blank}), you can supply your own `SetTopic`, but it must be the final instruction to be respected. +- When manually crafting an XCM via [`execute`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.execute){target=\_blank} or [`send`](https://paritytech.github.io/polkadot-sdk/master/pallet_xcm/pallet/struct.Pallet.html#method.send){target=\_blank}), you can include a `SetTopic` as the final instruction; otherwise, `WithUniqueTopic` will append its own unique ID, overriding earlier `SetTopic` instructions. - In multi-hop XCM flows, manually setting the topic ensures consistent tracing across all hops. Any remote XCM calls embedded inside the XCM must also include a `SetTopic` at the end to preserve the same `message_id` throughout the cross-chain flow. ## Message Lifecycle @@ -3293,6 +3293,8 @@ if (forwardedIdHex === expectedForwardedId) { } ``` +> Note: `@noble/hashes` and `polkadot-api` are required dependencies for this code to work. + ## Failure Event Handling When an XCM fails, the transaction rolls back and no explicit failure event is emitted on-chain. The following are some ways to check for XCM failure: @@ -31110,7 +31112,7 @@ Before you begin, ensure you have the following: wget https://github.com/polkadot-fellows/runtimes/releases/download/v1.6.0/asset-hub-polkadot_runtime-v1006000.compact.compressed.wasm -O wasms/asset-hub-polkadot_v1.6.0.wasm ``` -4. Download the config for Polkadot Hub: +4. Download the config file for Polkadot Hub: ```bash mkdir -p configs @@ -31148,7 +31150,7 @@ Before you begin, ensure you have the following: ```bash npm install --save-dev typescript @types/node tsx - npm install polkadot-api @polkadot-labs/hdkd @polkadot-labs/hdkd-helpers + npm install polkadot-api @polkadot-labs/hdkd @polkadot-labs/hdkd-helpers npm install @noble/hashes ``` diff --git a/llms.txt b/llms.txt index 708d07483..ca2c804c8 100644 --- a/llms.txt +++ b/llms.txt @@ -25,7 +25,7 @@ - [Transact](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/from-apps/transact.md): Learn how to execute arbitrary calls on remote chains using the Transact instruction, enabling cross-chain function execution and remote pallet interactions. - [Transfers](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/from-apps/transfers.md): Learn how to perform cross-chain asset transfers using XCM, including teleport, reserve transfers, and handling different asset types across parachains. - [XCM Guides](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-guides/index.md): Comprehensive guides for implementing XCM functionality in applications and understanding cross-chain interactions. -- [XCM Observability](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-observability.md): This conceptual overview of XCM observability in Polkadot covers message correlation, tracing, and debugging features in modern runtimes. +- [XCM Observability](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-observability.md): A conceptual overview of XCM observability in Polkadot covers message correlation, tracing, and debugging features in modern runtimes. - [XCM Runtime APIs](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/interoperability/xcm-runtime-apis.md): Learn about XCM Runtime APIs in Polkadot for cross-chain communication. Explore the APIs to simulate and test XCM messages before execution on the network. - [Networks](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/networks.md): Explore the Polkadot ecosystem networks and learn the unique purposes of each, tailored for blockchain innovation, testing, and enterprise-grade solutions. - [Add a Pallet to the Runtime](https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/refs/heads/master/develop/parachains/customize-parachain/add-existing-pallets.md): Learn how to include and configure pallets in a Polkadot SDK-based runtime, from adding dependencies to implementing necessary traits. diff --git a/tutorials/interoperability/xcm-observability-in-action.md b/tutorials/interoperability/xcm-observability-in-action.md index 6a9d16294..c04d49299 100644 --- a/tutorials/interoperability/xcm-observability-in-action.md +++ b/tutorials/interoperability/xcm-observability-in-action.md @@ -85,7 +85,7 @@ Before you begin, ensure you have the following: ```bash npm install --save-dev typescript @types/node tsx - npm install polkadot-api @polkadot-labs/hdkd @polkadot-labs/hdkd-helpers + npm install polkadot-api @polkadot-labs/hdkd @polkadot-labs/hdkd-helpers npm install @noble/hashes ```