UNPKG

xcm-lib-zkverify

Version:

XCM asset teleportation and remote EVM calls for ZKVerify

264 lines 11.9 kB
"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