xcm-lib-zkverify
Version:
XCM asset teleportation and remote EVM calls for ZKVerify
264 lines • 11.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExecuteRemoteEvmCallService = void 0;
const ethers_1 = require("ethers");
const validation_1 = require("../utils/validation");
const contract_compiler_1 = require("../utils/contract-compiler");
const transaction_executor_1 = require("../utils/transaction-executor");
const api_connection_1 = require("../utils/api-connection");
const config_validator_1 = require("../utils/config-validator");
const util_1 = require("@polkadot/util");
const util_crypto_1 = require("@polkadot/util-crypto");
const types_1 = require("@polkadot/types");
class ExecuteRemoteEvmCallService {
constructor(config) {
// Validate required configuration for remote EVM call operations
if (!config.relayWsEndpoint) {
throw new Error('relayWsEndpoint is required for remote EVM call operations');
}
if (!config.evmParachainWsEndpoint) {
throw new Error('evmParachainWsEndpoint is required for remote EVM call operations');
}
if (!config.relayPrivateKey) {
throw new Error('relayPrivateKey is required for remote EVM call operations');
}
if (config.evmParachainId === undefined) {
throw new Error('evmParachainId is required for remote EVM call operations');
}
config_validator_1.ConfigValidator.validateParachainId(config.evmParachainId);
(0, validation_1.validatePrivateKey)(config.relayPrivateKey, 'Relay private key');
this.config = config;
this.xcmRelayVersion = config.xcmRelayVersion || 'V5';
this.xcmParachainVersion = config.xcmParachainVersion || 'V2';
}
async initialize() {
this.relayApi = await api_connection_1.ApiConnection.createApi(this.config.relayWsEndpoint);
this.parachainApi = await api_connection_1.ApiConnection.createApi(this.config.evmParachainWsEndpoint);
this.relayKeyPair = api_connection_1.ApiConnection.createKeyPair(this.config.relayPrivateKey, 'sr25519');
}
async checkContractExists(contractAddress) {
if (!this.parachainApi) {
throw new Error('Parachain API not initialized');
}
try {
const code = await this.parachainApi.rpc.eth.getCode(contractAddress);
const codeHex = code.toHex();
// If code exists and is not empty (more than just '0x'), a contract is deployed
return codeHex && codeHex !== '0x';
}
catch (error) {
console.error(`Error checking contract existence: ${error}`);
return false;
}
}
calculateDerivedAccount(relayAccountId) {
const registry = new types_1.TypeRegistry();
const family = "ParentChain";
const accType = "AccountId32";
const paraId = null; // No paraId for parent chain accounts
let toHash = new Uint8Array([
...new TextEncoder().encode(family),
...(paraId ? registry.createType("Compact<u32>", paraId).toU8a() : []),
...registry.createType("Compact<u32>", accType.length + 32).toU8a(),
...new TextEncoder().encode(accType),
...relayAccountId,
]);
const hash = (0, util_crypto_1.blake2AsU8a)(toHash, 256);
const derivedAddress = (0, util_1.u8aToHex)(hash.slice(0, 20));
return derivedAddress;
}
async checkDerivedAccountBalance(xcmFee) {
if (!this.relayKeyPair || !this.parachainApi) {
throw new Error('Service not properly initialized');
}
const relayAccountId = this.relayKeyPair.publicKey;
const derivedAddress = this.calculateDerivedAccount(relayAccountId);
try {
const accountInfo = await this.parachainApi.query.system.account(derivedAddress);
const balance = accountInfo.data.free.toString();
console.log(`Derived account ${derivedAddress} balance: ${balance}`);
// Check if balance is sufficient
if (BigInt(balance) < BigInt(xcmFee)) {
throw new Error(`Insufficient balance in derived account ${derivedAddress}. ` +
`Available: ${balance}, Required: ${xcmFee}. ` +
`Please fund the derived account on the parachain before executing remote calls.`);
}
}
catch (error) {
if (error instanceof Error && error.message.includes('Insufficient balance')) {
throw error;
}
throw new Error(`Failed to check derived account balance: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async executeRemoteEvmCall(params, contractPath, abiPath) {
if (!this.relayApi || !this.relayKeyPair) {
throw new Error('Service not initialized. Call initialize() first.');
}
(0, validation_1.validateEvmAddress)(params.contractAddress, 'Contract address');
const contractExists = await this.checkContractExists(params.contractAddress);
if (!contractExists) {
throw new Error(`No contract deployed at address: ${params.contractAddress}`);
}
if (!params.xcmFee) {
throw new Error('XCM_FEE is required');
}
const xcmFee = params.xcmFee;
// Check if derived account has sufficient balance for XCM fees
await this.checkDerivedAccountBalance(xcmFee);
try {
// Get ABI - prioritize abiPath if both are provided for better performance
let contractAbi;
if (abiPath) {
contractAbi = (0, contract_compiler_1.loadAbiDirectly)(abiPath);
}
else if (contractPath) {
contractAbi = (0, contract_compiler_1.loadABIFromContract)(contractPath);
}
else {
throw new Error('Either contractPath or abiPath must be provided');
}
// Create XCM message for remote EVM call
const destination = this.relayApi.createType('XcmVersionedLocation', {
[this.xcmRelayVersion]: {
parents: 0,
interior: {
X1: [{
Parachain: this.config.evmParachainId
}]
}
}
});
// Create the encoded call first for debugging
const encodedCallResult = this.createEvmCallEncoded(params.contractAddress, params.functionName, params.args || [], contractAbi, params.value || '0', params.gasLimit || '100000');
// Create the XCM message
const xcmMessage = this.relayApi.createType('XcmVersionedXcm', {
[this.xcmRelayVersion]: [
{
WithdrawAsset: [{
id: {
parents: 1,
interior: 'Here'
},
fun: {
Fungible: xcmFee
}
}]
},
{
BuyExecution: {
fees: {
id: {
parents: 1,
interior: 'Here'
},
fun: {
Fungible: xcmFee
}
},
weightLimit: 'Unlimited'
}
},
{
Transact: {
originKind: 'SovereignAccount',
fallbackMaxWeight: null,
call: {
encoded: encodedCallResult.encodedArgs
}
}
},
{
RefundSurplus: null
},
{
DepositAsset: {
assets: {
Wild: {
AllCounted: 1
}
},
beneficiary: {
parents: 1,
interior: {
X1: [{
AccountId32: {
network: null,
id: this.relayKeyPair.publicKey
}
}]
}
}
}
}
]
});
const tx = this.relayApi.tx.xcmPallet.send(destination, xcmMessage);
return await transaction_executor_1.TransactionExecutor.executeTransaction(this.relayApi, tx, this.relayKeyPair, { returnSuccess: true, skipNonce: true });
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
}
createEvmCallEncoded(contractAddress, functionName, args, contractAbi, value, gasLimit) {
(0, validation_1.validateEvmAddress)(contractAddress, 'Contract address');
// Create contract interface and encode function call
const contractInterface = new ethers_1.Interface(contractAbi);
const inputData = contractInterface.encodeFunctionData(functionName, args);
const functionSelector = inputData.slice(0, 10);
// Create the EthereumXcmTransaction
const xcmTransaction = this.parachainApi.createType('XcmPrimitivesEthereumXcmEthereumXcmTransaction', {
[this.xcmParachainVersion]: {
gasLimit: gasLimit,
action: {
Call: contractAddress
},
value: value,
input: inputData,
accessList: null
}
});
// Create the ethereumXcm.transact call
const transaction = this.parachainApi.tx.ethereumXcm.transact(xcmTransaction);
const encodedCallData = transaction.toHex();
// Extract pallet and call indices dynamically and create XCM Transact encoding
const [palletIndex, callIndex] = transaction.callIndex;
const encodedArgs = '0x' +
palletIndex.toString(16).padStart(2, '0') +
callIndex.toString(16).padStart(2, '0') +
xcmTransaction.toHex().slice(2);
return {
transaction,
encodedCallData,
encodedArgs,
inputData,
functionSelector
};
}
getRelayAccountAddress() {
if (!this.relayKeyPair) {
throw new Error('Relay keypair not initialized');
}
return this.relayKeyPair.address;
}
async getRelayChainBalance(address) {
if (!this.relayApi) {
throw new Error('Relay API not initialized');
}
const accountInfo = await this.relayApi.query.system.account(address);
return accountInfo.data.free.toString();
}
async disconnect() {
if (this.relayApi) {
await this.relayApi.disconnect();
}
if (this.parachainApi) {
await this.parachainApi.disconnect();
}
}
}
exports.ExecuteRemoteEvmCallService = ExecuteRemoteEvmCallService;
//# sourceMappingURL=execute-remote-evm-call.js.map