Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions examples/xc-transactor/esbuild.dev.mjs
Original file line number Diff line number Diff line change
@@ -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);
19 changes: 19 additions & 0 deletions examples/xc-transactor/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!doctype html>

<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>XC Transactor POC</title>
</head>

<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script>
new EventSource('/esbuild').addEventListener('change', () =>
location.reload()
);
</script>
<script type="module" src="./out/index.js"></script>
</body>
</html>
16 changes: 16 additions & 0 deletions examples/xc-transactor/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
91 changes: 91 additions & 0 deletions examples/xc-transactor/src/abi.ts
Original file line number Diff line number Diff line change
@@ -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}`;
115 changes: 115 additions & 0 deletions examples/xc-transactor/src/index.ts
Original file line number Diff line number Diff line change
@@ -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);
3 changes: 3 additions & 0 deletions examples/xc-transactor/src/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createXcContext } from '@galacticcouncil/xc';

export const ctx = await createXcContext();
51 changes: 51 additions & 0 deletions examples/xc-transactor/src/signers/evm.ts
Original file line number Diff line number Diff line change
@@ -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);
});
}
20 changes: 20 additions & 0 deletions examples/xc-transactor/src/signers/index.ts
Original file line number Diff line number Diff line change
@@ -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);
}
);
}
17 changes: 17 additions & 0 deletions examples/xc-transactor/src/utils.ts
Original file line number Diff line number Diff line change
@@ -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')}`;
}
8 changes: 8 additions & 0 deletions examples/xc-transactor/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": "src",
"noImplicitAny": false
},
"include": ["src/**/*.ts"]
}
Loading