diff --git a/examples/xc-transactor/esbuild.dev.mjs b/examples/xc-transactor/esbuild.dev.mjs
new file mode 100644
index 00000000..8b010031
--- /dev/null
+++ b/examples/xc-transactor/esbuild.dev.mjs
@@ -0,0 +1,24 @@
+import esbuild from 'esbuild';
+import { wasmLoader } from 'esbuild-plugin-wasm';
+import { createProxyServer } from '../../esbuild.proxy.mjs';
+
+const plugins = [wasmLoader({ mode: 'deferred' })];
+
+const options = {
+ entryPoints: ['src/index.ts'],
+ bundle: true,
+ format: 'esm',
+ platform: 'browser',
+ target: 'esnext',
+ preserveSymlinks: true,
+ treeShaking: true,
+ sourcemap: true,
+ outdir: 'out/',
+ logLevel: 'info',
+};
+
+const ctx = await esbuild.context({ ...options, plugins });
+await ctx.rebuild();
+await ctx.watch();
+const localServer = await ctx.serve({ servedir: './', host: '127.0.0.1' });
+createProxyServer(localServer);
diff --git a/examples/xc-transactor/index.html b/examples/xc-transactor/index.html
new file mode 100644
index 00000000..e230e6f4
--- /dev/null
+++ b/examples/xc-transactor/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ XC Transactor POC
+
+
+
+
+
+
+
+
diff --git a/examples/xc-transactor/package.json b/examples/xc-transactor/package.json
new file mode 100644
index 00000000..15db9b33
--- /dev/null
+++ b/examples/xc-transactor/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "xc-transactor",
+ "private": true,
+ "type": "module",
+ "main": "out/index.js",
+ "scripts": {
+ "dev": "node ./esbuild.dev.mjs"
+ },
+ "devDependencies": {
+ "esbuild": "^0.25.0",
+ "esbuild-plugin-wasm": "^1.0.0"
+ },
+ "dependencies": {
+ "@galacticcouncil/xc": "^0.4.0"
+ }
+}
diff --git a/examples/xc-transactor/src/abi.ts b/examples/xc-transactor/src/abi.ts
new file mode 100644
index 00000000..3c63629c
--- /dev/null
+++ b/examples/xc-transactor/src/abi.ts
@@ -0,0 +1,91 @@
+export const XCM_TRANSACTOR_ABI = [
+ {
+ inputs: [
+ {
+ components: [
+ { internalType: 'uint8', name: 'parents', type: 'uint8' },
+ { internalType: 'bytes[]', name: 'interior', type: 'bytes[]' },
+ ],
+ internalType: 'struct Multilocation',
+ name: 'dest',
+ type: 'tuple',
+ },
+ {
+ components: [
+ { internalType: 'uint8', name: 'parents', type: 'uint8' },
+ { internalType: 'bytes[]', name: 'interior', type: 'bytes[]' },
+ ],
+ internalType: 'struct Multilocation',
+ name: 'feeLocation',
+ type: 'tuple',
+ },
+ {
+ components: [
+ { internalType: 'uint64', name: 'refTime', type: 'uint64' },
+ { internalType: 'uint64', name: 'proofSize', type: 'uint64' },
+ ],
+ internalType: 'struct Weight',
+ name: 'transactRequiredWeightAtMost',
+ type: 'tuple',
+ },
+ { internalType: 'bytes', name: 'call', type: 'bytes' },
+ { internalType: 'uint256', name: 'feeAmount', type: 'uint256' },
+ {
+ components: [
+ { internalType: 'uint64', name: 'refTime', type: 'uint64' },
+ { internalType: 'uint64', name: 'proofSize', type: 'uint64' },
+ ],
+ internalType: 'struct Weight',
+ name: 'overallWeight',
+ type: 'tuple',
+ },
+ { internalType: 'bool', name: 'refund', type: 'bool' },
+ ],
+ name: 'transactThroughSignedMultilocation',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ components: [
+ { internalType: 'uint8', name: 'parents', type: 'uint8' },
+ { internalType: 'bytes[]', name: 'interior', type: 'bytes[]' },
+ ],
+ internalType: 'struct Multilocation',
+ name: 'dest',
+ type: 'tuple',
+ },
+ { internalType: 'address', name: 'feeLocationAddress', type: 'address' },
+ {
+ components: [
+ { internalType: 'uint64', name: 'refTime', type: 'uint64' },
+ { internalType: 'uint64', name: 'proofSize', type: 'uint64' },
+ ],
+ internalType: 'struct Weight',
+ name: 'transactRequiredWeightAtMost',
+ type: 'tuple',
+ },
+ { internalType: 'bytes', name: 'call', type: 'bytes' },
+ { internalType: 'uint256', name: 'feeAmount', type: 'uint256' },
+ {
+ components: [
+ { internalType: 'uint64', name: 'refTime', type: 'uint64' },
+ { internalType: 'uint64', name: 'proofSize', type: 'uint64' },
+ ],
+ internalType: 'struct Weight',
+ name: 'overallWeight',
+ type: 'tuple',
+ },
+ { internalType: 'bool', name: 'refund', type: 'bool' },
+ ],
+ name: 'transactThroughSigned',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+] as const;
+
+export const XCM_TRANSACTOR_V3 =
+ '0x0000000000000000000000000000000000000817' as `0x${string}`;
diff --git a/examples/xc-transactor/src/index.ts b/examples/xc-transactor/src/index.ts
new file mode 100644
index 00000000..160930b8
--- /dev/null
+++ b/examples/xc-transactor/src/index.ts
@@ -0,0 +1,115 @@
+import {
+ acc,
+ addr,
+ Abi,
+ AnyEvmChain,
+ CallType,
+ Precompile,
+} from '@galacticcouncil/xc-core';
+import { EvmCall } from '@galacticcouncil/xc-sdk';
+import { encodeFunctionData } from 'viem';
+
+import { XCM_TRANSACTOR_ABI, XCM_TRANSACTOR_V3 } from './abi';
+import { sign } from './signers';
+import { ctx } from './setup';
+import { encodeParachainJunction } from './utils';
+
+const { config } = ctx;
+
+// Define transfer constraints
+const srcChain = config.getChain('moonbeam') as AnyEvmChain;
+
+const HYDRATION_PARACHAIN_ID = 2034;
+const MOONBEAM_PARACHAIN_ID = 2004;
+
+// Define source & dest accounts
+const srcAddr = '0x03F067F5174DF6E4E49285D32d9Ce1615711BCe9';
+// 7L53bUTBopuwFt3mKUfmkzgGLayYa1Yvn1hAg9v5UMrQzTfh
+
+// HDX XC-20 address on Moonbeam
+const HDX_XC20 = '0xffffffff345dc44ddae98df024eb494321e73fcc' as `0x${string}`;
+
+// Compute derived origin account on Hydration
+const derivedAccount = acc.getMultilocationDerivatedAccount(
+ MOONBEAM_PARACHAIN_ID,
+ srcAddr,
+ 1, // parents = 1 (sibling parachain)
+ false
+);
+const derivedPubKey = addr.Ss58Addr.getPubKey(derivedAccount) as `0x${string}`;
+
+console.log('Moonbeam sender:', srcAddr);
+console.log('Derived origin account on Hydration:', derivedAccount);
+
+// Subcall 1: Fund derived account via PolkadotXcm.transferAssetsToPara32
+// Sends HDX from Moonbeam to the derived account on Hydration.
+// HDX is used as fee asset on Hydration for the subsequent transact call.
+const fundCallData = encodeFunctionData({
+ abi: Abi.PolkadotXcm,
+ functionName: 'transferAssetsToPara32',
+ args: [
+ HYDRATION_PARACHAIN_ID,
+ derivedPubKey,
+ [{ asset: HDX_XC20, amount: 10_000_000_000_000n }], // 10 HDX (12 decimals)
+ 0, // feeAssetItem
+ ],
+});
+
+// Subcall 2: Remote transact via XcmTransactor precompile
+// Executes balances.transferKeepAlive on Hydration from the derived account.
+// Fees are paid in HDX (funded by subcall 1).
+
+// Pre-encoded Hydration call: Balances.transfer_keep_alive(dest, 100000)
+// Encoded via Polkadot.js Apps, can be replaced with any Hydration extrinsic
+const encodedHydrationCall =
+ '0x070382fb02afe02fe5d6c793145a75e6860c4e206682c3ab395a9d2a941eb7c0d30d0b00a0724e1809';
+
+const transactCallData = encodeFunctionData({
+ abi: XCM_TRANSACTOR_ABI,
+ functionName: 'transactThroughSigned',
+ args: [
+ // dest: Hydration (parachain 2034)
+ { parents: 1, interior: [encodeParachainJunction(HYDRATION_PARACHAIN_ID)] },
+ // feeLocationAddress: HDX XC-20 on Moonbeam
+ HDX_XC20,
+ // transactRequiredWeightAtMost
+ { refTime: 1_000_000_000n, proofSize: 50_000n },
+ // call (SCALE-encoded Hydration extrinsic)
+ encodedHydrationCall as `0x${string}`,
+ // feeAmount: 5 HDX (pays XCM execution fees on Hydration, 12 decimals)
+ 5_000_000_000_000n,
+ // overallWeight: overall weight for full XCM program
+ { refTime: 2_000_000_000n, proofSize: 100_000n },
+ // refund surplus
+ true,
+ ],
+});
+
+// Batch both subcalls via Moonbeam Batch precompile (0x0808)
+const batchCallData = encodeFunctionData({
+ abi: Abi.Batch,
+ functionName: 'batchAll',
+ args: [
+ [Precompile.PolkadotXcm, XCM_TRANSACTOR_V3],
+ [0n, 0n],
+ [fundCallData, transactCallData],
+ [],
+ ],
+});
+
+// Dump call info
+console.log('Encoded Hydration call:', encodedHydrationCall);
+console.log('Batched calldata:', batchCallData);
+
+const call: EvmCall = {
+ from: srcAddr,
+ data: batchCallData,
+ type: CallType.Evm,
+ to: Precompile.Batch as `0x${string}`,
+ dryRun: async () => undefined,
+};
+
+console.log(call);
+
+// Sign & send
+await sign(call, srcChain);
diff --git a/examples/xc-transactor/src/setup.ts b/examples/xc-transactor/src/setup.ts
new file mode 100644
index 00000000..83721442
--- /dev/null
+++ b/examples/xc-transactor/src/setup.ts
@@ -0,0 +1,3 @@
+import { createXcContext } from '@galacticcouncil/xc';
+
+export const ctx = await createXcContext();
diff --git a/examples/xc-transactor/src/signers/evm.ts b/examples/xc-transactor/src/signers/evm.ts
new file mode 100644
index 00000000..d026a15d
--- /dev/null
+++ b/examples/xc-transactor/src/signers/evm.ts
@@ -0,0 +1,51 @@
+import { h160 } from '@galacticcouncil/common';
+import { AnyChain, AnyEvmChain } from '@galacticcouncil/xc-core';
+import { Call, EvmCall } from '@galacticcouncil/xc-sdk';
+
+const { H160 } = h160;
+
+export async function signAndSend(
+ call: Call,
+ chain: AnyChain,
+ onTransactionSend: (hash: string | null) => void,
+ onTransactionReceipt: (receipt: any) => void,
+ onError: (error: unknown) => void
+) {
+ const evmChain = chain as AnyEvmChain;
+ const client = evmChain.evmClient;
+ const account = H160.fromAny(call.from);
+
+ const provider = client.getProvider();
+ const signer = client.getSigner(account);
+
+ await signer.switchChain({ id: client.chain.id });
+ await signer.request({ method: 'eth_requestAccounts' });
+
+ const { data, to, value } = call as EvmCall;
+ const estGas = await provider.estimateGas({
+ account: account as `0x${string}`,
+ data: data as `0x${string}`,
+ to: to as `0x${string}`,
+ value: value,
+ });
+ console.log('Est gas: ' + estGas);
+
+ const txHash = await signer.sendTransaction({
+ account: account as `0x${string}`,
+ chain: client.chain,
+ data: data as `0x${string}`,
+ to: to as `0x${string}`,
+ value: value,
+ });
+
+ onTransactionSend(txHash);
+ provider
+ .waitForTransactionReceipt({
+ hash: txHash,
+ })
+ .then((receipt) => onTransactionReceipt(receipt))
+ .catch((error: any) => {
+ console.log(error);
+ onError(error);
+ });
+}
diff --git a/examples/xc-transactor/src/signers/index.ts b/examples/xc-transactor/src/signers/index.ts
new file mode 100644
index 00000000..b6674521
--- /dev/null
+++ b/examples/xc-transactor/src/signers/index.ts
@@ -0,0 +1,20 @@
+import { AnyChain } from '@galacticcouncil/xc-core';
+import { Call } from '@galacticcouncil/xc-sdk';
+
+import * as evm from './evm';
+
+export async function sign(call: Call, chain: AnyChain) {
+ evm.signAndSend(
+ call,
+ chain,
+ (hash) => {
+ console.log('TxHash: ' + hash);
+ },
+ (receipt) => {
+ console.log(receipt);
+ },
+ (error) => {
+ console.error(error);
+ }
+ );
+}
diff --git a/examples/xc-transactor/src/utils.ts b/examples/xc-transactor/src/utils.ts
new file mode 100644
index 00000000..fdb31b0f
--- /dev/null
+++ b/examples/xc-transactor/src/utils.ts
@@ -0,0 +1,17 @@
+// Encode Parachain junction for Moonbeam Multilocation struct
+// Selector 0x00 + 4-byte parachain ID (big-endian)
+export function encodeParachainJunction(id: number): `0x${string}` {
+ return `0x00${id.toString(16).padStart(8, '0')}`;
+}
+
+// Encode PalletInstance junction for Moonbeam Multilocation struct
+// Selector 0x04 + 1-byte pallet index
+export function encodePalletInstanceJunction(index: number): `0x${string}` {
+ return `0x04${index.toString(16).padStart(2, '0')}`;
+}
+
+// Encode GeneralIndex junction for Moonbeam Multilocation struct
+// Selector 0x05 + 16-byte index (big-endian u128)
+export function encodeGeneralIndexJunction(index: number): `0x${string}` {
+ return `0x05${index.toString(16).padStart(32, '0')}`;
+}
diff --git a/examples/xc-transactor/tsconfig.json b/examples/xc-transactor/tsconfig.json
new file mode 100644
index 00000000..ace9d432
--- /dev/null
+++ b/examples/xc-transactor/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "baseUrl": "src",
+ "noImplicitAny": false
+ },
+ "include": ["src/**/*.ts"]
+}
diff --git a/package-lock.json b/package-lock.json
index 28dae5a9..7ef6fb9f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -75,6 +75,15 @@
"esbuild": "^0.25.0"
}
},
+ "examples/xc-transactor": {
+ "dependencies": {
+ "@galacticcouncil/xc": "^0.4.0"
+ },
+ "devDependencies": {
+ "esbuild": "^0.25.0",
+ "esbuild-plugin-wasm": "^1.0.0"
+ }
+ },
"examples/xc-transfer": {
"dependencies": {
"@galacticcouncil/xc": "^0.4.0",
@@ -14764,6 +14773,10 @@
"resolved": "integration-tests/xc-test",
"link": true
},
+ "node_modules/xc-transactor": {
+ "resolved": "examples/xc-transactor",
+ "link": true
+ },
"node_modules/xc-transfer": {
"resolved": "examples/xc-transfer",
"link": true