Skip to content

Commit 9933e8a

Browse files
Add multi-hop-with-set-topic.ts
1 parent e723a45 commit 9933e8a

File tree

3 files changed

+482
-39
lines changed

3 files changed

+482
-39
lines changed
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import {Binary, createClient, Enum, Transaction} from "polkadot-api";
2+
import {withPolkadotSdkCompat} from "polkadot-api/polkadot-sdk-compat";
3+
import {getPolkadotSigner} from "polkadot-api/signer";
4+
import {getWsProvider} from "polkadot-api/ws-provider/web";
5+
import {
6+
assetHub,
7+
hydration,
8+
XcmV3MultiassetFungibility,
9+
XcmV3WeightLimit,
10+
XcmV5AssetFilter,
11+
XcmV5Instruction,
12+
XcmV5Junction,
13+
XcmV5Junctions,
14+
XcmV5WildAsset,
15+
XcmVersionedXcm,
16+
} from "@polkadot-api/descriptors";
17+
import {sr25519CreateDerive} from "@polkadot-labs/hdkd";
18+
import {
19+
DEV_PHRASE,
20+
entropyToMiniSecret,
21+
mnemonicToEntropy,
22+
ss58Address,
23+
} from "@polkadot-labs/hdkd-helpers";
24+
25+
const XCM_VERSION = 5;
26+
27+
const toHuman = (_key: any, value: any) => {
28+
if (typeof value === "bigint") {
29+
return Number(value);
30+
}
31+
32+
if (value && typeof value === "object" && typeof value.asHex === "function") {
33+
return value.asHex();
34+
}
35+
36+
return value;
37+
};
38+
39+
async function main() {
40+
const para1Name = "Polkadot Asset Hub";
41+
const para1Client = createClient(
42+
withPolkadotSdkCompat(getWsProvider("ws://localhost:8000")),
43+
);
44+
const para1Api = para1Client.getTypedApi(assetHub);
45+
46+
const para2Name = "Hydration";
47+
const para2Client = createClient(
48+
withPolkadotSdkCompat(getWsProvider("ws://localhost:8001")),
49+
);
50+
const para2Api = para2Client.getTypedApi(hydration);
51+
52+
const entropy = mnemonicToEntropy(DEV_PHRASE);
53+
const miniSecret = entropyToMiniSecret(entropy);
54+
const derive = sr25519CreateDerive(miniSecret);
55+
const alice = derive("//Alice");
56+
const alicePublicKey = alice.publicKey;
57+
const aliceSigner = getPolkadotSigner(alicePublicKey, "Sr25519", alice.sign);
58+
const aliceAddress = ss58Address(alicePublicKey);
59+
60+
const origin = Enum("system", Enum("Signed", aliceAddress));
61+
62+
const expectedMessageId = "0xd60225f721599cb7c6e23cdf4fab26f205e30cd7eb6b5ccf6637cdc80b2339b2";
63+
64+
const message = XcmVersionedXcm.V5([
65+
XcmV5Instruction.WithdrawAsset([
66+
{
67+
id: {
68+
interior: XcmV5Junctions.Here(),
69+
parents: 1,
70+
},
71+
fun: XcmV3MultiassetFungibility.Fungible(1_000_000_000n),
72+
},
73+
]),
74+
XcmV5Instruction.ClearOrigin(),
75+
XcmV5Instruction.BuyExecution({
76+
fees: {
77+
id: {
78+
interior: XcmV5Junctions.Here(),
79+
parents: 1,
80+
},
81+
fun: XcmV3MultiassetFungibility.Fungible(500_000_000n),
82+
},
83+
weight_limit: XcmV3WeightLimit.Unlimited(),
84+
}),
85+
XcmV5Instruction.DepositReserveAsset({
86+
assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.All()),
87+
dest: {
88+
interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(2034)),
89+
parents: 1,
90+
},
91+
xcm: [
92+
XcmV5Instruction.BuyExecution({
93+
fees: {
94+
id: {
95+
interior: XcmV5Junctions.Here(),
96+
parents: 1,
97+
},
98+
fun: XcmV3MultiassetFungibility.Fungible(500_000_000n),
99+
},
100+
weight_limit: XcmV3WeightLimit.Unlimited(),
101+
}),
102+
XcmV5Instruction.DepositAsset({
103+
assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.All()),
104+
beneficiary: {
105+
interior: XcmV5Junctions.X1(
106+
XcmV5Junction.AccountId32({
107+
id: Binary.fromHex(
108+
"0x9818ff3c27d256631065ecabf0c50e02551e5c5342b8669486c1e566fcbf847f",
109+
),
110+
}),
111+
),
112+
parents: 0,
113+
},
114+
}),
115+
],
116+
}),
117+
XcmV5Instruction.SetTopic(Binary.fromHex(expectedMessageId)),
118+
]);
119+
120+
const weight: any =
121+
await para1Api.apis.XcmPaymentApi.query_xcm_weight(message);
122+
if (weight.success !== true) {
123+
console.error("❌ Failed to query XCM weight:", weight.error);
124+
para1Client.destroy();
125+
return;
126+
}
127+
128+
const tx: Transaction<any, string, string, any> =
129+
para1Api.tx.PolkadotXcm.execute({
130+
message,
131+
max_weight: weight.value,
132+
});
133+
const decodedCall = tx.decodedCall as any;
134+
console.log("👀 Executing XCM:", JSON.stringify(decodedCall, toHuman, 2));
135+
136+
try {
137+
const dryRunResult: any = await para1Api.apis.DryRunApi.dry_run_call(
138+
origin,
139+
decodedCall,
140+
XCM_VERSION,
141+
);
142+
console.log("📦 Dry run result:", JSON.stringify(dryRunResult.value, toHuman, 2));
143+
144+
const executionResult = dryRunResult.value.execution_result;
145+
if (!dryRunResult.success || !executionResult.success) {
146+
console.error("❌ Local dry run failed!");
147+
} else {
148+
console.log("✅ Local dry run successful.");
149+
150+
const emittedEvents: [any] = dryRunResult.value.emitted_events;
151+
const polkadotXcmSentEvent = emittedEvents.find(event =>
152+
event.type === "PolkadotXcm" && event.value.type === "Sent"
153+
);
154+
if (polkadotXcmSentEvent === undefined) {
155+
console.log(`⚠️ PolkadotXcm.Sent is available in runtimes built from stable2503-5 or later.`);
156+
} else {
157+
let parachainBlockBefore = await para2Client.getFinalizedBlock();
158+
const extrinsic = await tx.signAndSubmit(aliceSigner);
159+
const block = extrinsic.block;
160+
console.log(`📦 Finalised on ${para1Name} in block #${block.number}: ${block.hash}`);
161+
162+
if (!extrinsic.ok) {
163+
const dispatchError = extrinsic.dispatchError;
164+
if (dispatchError.type === "Module") {
165+
const modErr: any = dispatchError.value;
166+
console.error(`❌ Dispatch error in module: ${modErr.type}${modErr.value?.type}`);
167+
} else {
168+
console.error("❌ Dispatch error:", JSON.stringify(dispatchError, toHuman, 2));
169+
}
170+
}
171+
172+
const sentEvents = await para1Api.event.PolkadotXcm.Sent.pull();
173+
if (sentEvents.length > 0) {
174+
const sentMessageId = sentEvents[0].payload.message_id.asHex();
175+
console.log(`📣 Last message Sent on ${para1Name}: ${sentMessageId}`);
176+
if (sentMessageId === expectedMessageId) {
177+
console.log("✅ Sent message ID matched.");
178+
} else {
179+
console.error("❌ Sent message ID does not match expexted message ID.");
180+
}
181+
182+
let processedMessageId = undefined;
183+
const maxRetries = 8;
184+
for (let i = 0; i < maxRetries; i++) {
185+
const parachainBlockAfter = await para2Client.getFinalizedBlock();
186+
if (parachainBlockAfter.number == parachainBlockBefore.number) {
187+
const waiting = 1_000 * (2 ** i);
188+
console.log(`⏳ Waiting ${waiting}ms for ${para2Name} block to be finalised (${i + 1}/${maxRetries})...`);
189+
await new Promise((resolve) => setTimeout(resolve, waiting));
190+
continue;
191+
}
192+
193+
console.log(`📦 Finalised on ${para2Name} in block #${parachainBlockAfter.number}: ${parachainBlockAfter.hash}`);
194+
const processedEvents = await para2Api.event.MessageQueue.Processed.pull();
195+
const processingFailedEvents = await para2Api.event.MessageQueue.ProcessingFailed.pull();
196+
if (processedEvents.length > 0) {
197+
processedMessageId = processedEvents[0].payload.id.asHex();
198+
console.log(`📣 Last message Processed on ${para2Name}: ${processedMessageId}`);
199+
break;
200+
} else if (processingFailedEvents.length > 0) {
201+
processedMessageId = processingFailedEvents[0].payload.id.asHex();
202+
console.log(`📣 Last message ProcessingFailed on ${para2Name}: ${processedMessageId}`);
203+
break;
204+
} else {
205+
console.log(`📣 No Processed events on ${para2Name} found.`);
206+
parachainBlockBefore = parachainBlockAfter; // Update the block before to the latest one
207+
}
208+
}
209+
210+
if (processedMessageId === expectedMessageId) {
211+
console.log("✅ Processed Message ID matched.");
212+
} else {
213+
console.error("❌ Processed Message ID does not match expected Message ID.");
214+
}
215+
} else {
216+
console.log(`📣 No Sent events on ${para1Name} found.`);
217+
}
218+
}
219+
}
220+
} finally {
221+
para1Client.destroy();
222+
para2Client.destroy();
223+
}
224+
}
225+
226+
main().catch(console.error);

0 commit comments

Comments
 (0)