UNPKG

@axelar-network/axelarjs-sdk

Version:
933 lines (932 loc) 54.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AxelarGMPRecoveryAPI = exports.getCosmosSigner = exports.RouteDir = exports.GMPErrorMap = void 0; const types_1 = require("../types"); const AxelarRecoveryApi_1 = require("./AxelarRecoveryApi"); const EVMClient_1 = __importDefault(require("./client/EVMClient")); const IAxelarExecutable_1 = __importDefault(require("../abi/IAxelarExecutable")); const ethers_1 = require("ethers"); const AxelarQueryAPI_1 = require("../AxelarQueryAPI"); const chain_1 = __importDefault(require("./constants/chain")); const contractEventHelper_1 = require("./helpers/contractEventHelper"); const erc20Abi_json_1 = __importDefault(require("../abi/erc20Abi.json")); const AxelarGateway_1 = require("../AxelarGateway"); const providerHelper_1 = require("./helpers/providerHelper"); const transactions_1 = require("@mysten/sui/transactions"); const bcs_1 = require("@mysten/sui/bcs"); const error_1 = require("./constants/error"); const helpers_1 = require("./helpers"); const utils_1 = require("../../utils"); const types_2 = require("@axelar-network/axelarjs-types/axelar/evm/v1beta1/types"); const utils_2 = require("ethers/lib/utils"); const s3_1 = __importDefault(require("./constants/s3")); const stargate_1 = require("@cosmjs/stargate"); const cosmosGasReceiverOptions_1 = require("./constants/cosmosGasReceiverOptions"); const chains_1 = require("../../chains"); const StellarSdk = __importStar(require("@stellar/stellar-sdk")); const stellarHelper_1 = require("./helpers/stellarHelper"); exports.GMPErrorMap = { [AxelarRecoveryApi_1.GMPStatus.CANNOT_FETCH_STATUS]: types_1.ApproveGatewayError.FETCHING_STATUS_FAILED, [AxelarRecoveryApi_1.GMPStatus.DEST_EXECUTED]: types_1.ApproveGatewayError.ALREADY_EXECUTED, [AxelarRecoveryApi_1.GMPStatus.DEST_GATEWAY_APPROVED]: types_1.ApproveGatewayError.ALREADY_APPROVED, }; var RouteDir; (function (RouteDir) { RouteDir["COSMOS_TO_EVM"] = "cosmos_to_evm"; RouteDir["EVM_TO_COSMOS"] = "evm_to_cosmos"; RouteDir["EVM_TO_EVM"] = "evm_to_evm"; RouteDir["COSMOS_TO_COSMOS"] = "cosmos_to_cosmos"; })(RouteDir || (exports.RouteDir = RouteDir = {})); const getCosmosSigner = (rpcUrl, offlineDirectSigner) => __awaiter(void 0, void 0, void 0, function* () { return stargate_1.SigningStargateClient.connectWithSigner(rpcUrl, offlineDirectSigner); }); exports.getCosmosSigner = getCosmosSigner; function matchesOriginalTokenPayment(token, denomOnSrcChain) { return token === "autocalculate" || (token === null || token === void 0 ? void 0 : token.denom) === denomOnSrcChain; } function getIBCDenomOnSrcChain(denomOnAxelar, selectedChain, chainConfigs) { const asset = chainConfigs["assets"][denomOnAxelar !== null && denomOnAxelar !== void 0 ? denomOnAxelar : "uaxl"]; const assetOnSrcChain = asset["chain_aliases"][selectedChain.chainName.toLowerCase()]; const ibcDenom = assetOnSrcChain === null || assetOnSrcChain === void 0 ? void 0 : assetOnSrcChain.ibcDenom; if (!ibcDenom) { throw new Error("cannot find token that matches original gas payment"); } return ibcDenom; } class AxelarGMPRecoveryAPI extends AxelarRecoveryApi_1.AxelarRecoveryApi { constructor(config) { super(config); this.axelarQueryApi = new AxelarQueryAPI_1.AxelarQueryAPI({ environment: config.environment, axelarRpcUrl: this.axelarRpcUrl, axelarLcdUrl: this.axelarLcdUrl, }); } getCidFromSrcTxHash(destChainId, messageId, eventIndex) { var _a; const chainId = (_a = chain_1.default[this.environment].networkInfo[destChainId.toLowerCase()]) === null || _a === void 0 ? void 0 : _a.chainId; return (0, helpers_1.getCommandId)(messageId, eventIndex, chainId); } doesTxMeetConfirmHt(chain, txHash, provider) { return __awaiter(this, void 0, void 0, function* () { const confirmations = yield this.getSigner(chain, { useWindowEthereum: false, provider }) .provider.getTransactionReceipt(txHash) .then((receipt) => __awaiter(this, void 0, void 0, function* () { if (!receipt) { const gmpTx = yield this.fetchGMPTransaction(txHash); const currentBlock = yield this.getSigner(chain, { useWindowEthereum: false, provider, }).provider.getBlockNumber(); return currentBlock - gmpTx.call.blockNumber; } return receipt.confirmations; })); return this.axelarQueryApi .getConfirmationHeight(chain) .then((minConfirmHeight) => minConfirmHeight <= confirmations) .catch(() => undefined); }); } isEVMEventFailed(eventResponse) { var _a; if (!eventResponse) return undefined; return [types_2.Event_Status.STATUS_FAILED, types_2.Event_Status.STATUS_UNSPECIFIED].includes((_a = eventResponse.event) === null || _a === void 0 ? void 0 : _a.status); } isEVMEventConfirmed(eventResponse) { var _a; if (!eventResponse) return undefined; return ((_a = eventResponse.event) === null || _a === void 0 ? void 0 : _a.status) === types_2.Event_Status.STATUS_CONFIRMED; } isEVMEventCompleted(eventResponse) { var _a; if (!eventResponse) return undefined; return ((_a = eventResponse.event) === null || _a === void 0 ? void 0 : _a.status) === types_2.Event_Status.STATUS_COMPLETED; } getEvmEvent(srcChainId, destChainId, srcTxHash, srcTxEventIndex, evmWalletDetails) { return __awaiter(this, void 0, void 0, function* () { var _a; const eventIndex = srcTxEventIndex !== null && srcTxEventIndex !== void 0 ? srcTxEventIndex : (yield this.getEventIndex(srcChainId, srcTxHash, evmWalletDetails) .then((index) => index) .catch(() => -1)); if (eventIndex === -1) { return { success: false, errorMessage: `getEvmEvent(): could not find event index for ${srcTxHash}`, commandId: "", eventResponse: {}, infoLog: "", }; } const eventResponse = yield this.axelarQueryApi.getEVMEvent(srcChainId, srcTxHash, eventIndex); const isCallContract = ((_a = eventResponse === null || eventResponse === void 0 ? void 0 : eventResponse.event) === null || _a === void 0 ? void 0 : _a.contractCall) ? true : false; const messageId = isCallContract ? `${srcTxHash}-${eventIndex}` : srcTxHash; const commandId = this.getCidFromSrcTxHash(destChainId, messageId, eventIndex); if (!eventResponse || this.isEVMEventFailed(eventResponse)) { const errorMessage = this.isEVMEventFailed(eventResponse) ? `getEvmEvent(): event on source chain is not successful for: ${srcTxHash}` : `getEvmEvent(): could not determine status of event: ${srcTxHash}`; return { success: false, errorMessage, commandId, eventResponse: {}, infoLog: `srcTxHash: ${srcTxHash}, generated commandId: ${commandId}`, }; } return { success: true, commandId, eventResponse, errorMessage: "", infoLog: `${srcTxHash} correspondes to command ID: ${commandId}`, }; }); } findEventAndConfirmIfNeeded(srcChain, destChain, txHash, txEventIndex, evmWalletDetails) { return __awaiter(this, void 0, void 0, function* () { if (this.debugMode) console.debug(`confirmation: checking whether ${txHash} needs to be confirmed on Axelar`); const evmEvent = yield this.getEvmEvent(srcChain, destChain, txHash, txEventIndex); const { infoLog: getEvmEventInfoLog } = evmEvent; if (this.debugMode) console.debug(`confirmation: ${getEvmEventInfoLog}`); if (this.isEVMEventCompleted(evmEvent.eventResponse) || this.isEVMEventConfirmed(evmEvent.eventResponse)) { return { success: true, commandId: evmEvent.commandId, eventResponse: evmEvent.eventResponse, infoLogs: [ `confirmation: event for ${txHash} was already detected on the network and did not need to be confirmed`, ], }; } else { const isConfirmFinalized = yield this.doesTxMeetConfirmHt(srcChain, txHash, evmWalletDetails.provider); if (!isConfirmFinalized) { const minConfirmLevel = yield this.axelarQueryApi.getConfirmationHeight(srcChain); return { success: false, commandId: evmEvent.commandId, eventResponse: evmEvent.eventResponse, infoLogs: [], errorMessage: `findEventAndConfirmIfNeeded(): ${txHash} is not confirmed on ${srcChain}. The minimum confirmation height is ${minConfirmLevel}`, }; } const confirmTx = yield this.confirmGatewayTx(txHash, srcChain).catch(() => undefined); if (!confirmTx) { return { success: false, commandId: evmEvent.commandId, eventResponse: evmEvent.eventResponse, infoLogs: [], errorMessage: `findEventAndConfirmIfNeeded(): could not confirm transaction on Axelar`, }; } const updatedEvent = yield this.getEvmEvent(srcChain, destChain, txHash, txEventIndex, evmWalletDetails); if (this.isEVMEventCompleted(updatedEvent === null || updatedEvent === void 0 ? void 0 : updatedEvent.eventResponse)) { return { success: true, commandId: updatedEvent.commandId, eventResponse: updatedEvent.eventResponse, infoLogs: [ `confirmation: successfully confirmed ${txHash} on Axelar; confirmed event was finalized`, getEvmEventInfoLog, ], }; } else { return { success: false, eventResponse: evmEvent.eventResponse, commandId: updatedEvent.commandId, errorMessage: `findEventAndConfirmIfNeeded(): could not confirm and finalize event successfully: ${txHash};. Your transaction may not have enough confirmations yet.`, infoLogs: [ `confirmation: successfully confirmed ${txHash} on Axelar; confirmed event was unable to be finalized`, getEvmEventInfoLog, ], }; } } }); } findBatchAndSignIfNeeded(commandId, destChainId) { return __awaiter(this, void 0, void 0, function* () { let signTxLog = ""; try { const batchData = yield (0, utils_1.retry)(() => this.fetchBatchData(destChainId, commandId), 10, 3000); if (batchData) { signTxLog = `signing: batch data exists so do not need to sign. commandId: ${commandId}, batchId: ${batchData.batch_id}`; if (this.debugMode) console.debug(signTxLog); return { success: true, infoLogs: [signTxLog], }; } else { const signCommandTx = yield this.signCommands(destChainId); signTxLog = `signing: signed batch for commandId (${commandId}) in tx: ${signCommandTx.transactionHash}`; if (this.debugMode) console.debug(signTxLog); return { success: true, signCommandTx, infoLogs: [signTxLog], }; } } catch (e) { return { success: false, errorMessage: `findBatchAndSignIfNeeded(): issue retrieving and signing command data: ${commandId}`, infoLogs: [signTxLog], }; } }); } findBatchAndApproveGateway(commandId, destChainId, wallet) { return __awaiter(this, void 0, void 0, function* () { if (this.debugMode) console.debug(`broadcasting: checking for command ID: ${commandId} to broadcast`); return (0, utils_1.retry)(() => __awaiter(this, void 0, void 0, function* () { const batchData = yield this.fetchBatchData(destChainId, commandId); if (!batchData) { return Promise.reject(`findBatchAndApproveGateway(): unable to retrieve batch data for ${commandId}`); } const commandData = batchData.command_ids.find((t) => t === commandId); if (!commandData) { return Promise.reject(`findBatchAndApproveGateway(): unable to retrieve command ID (${commandId}) in batch data`); } if (batchData.status === "BATCHED_COMMANDS_STATUS_SIGNING") { return Promise.reject(`findBatchAndApproveGateway(): batch ID ${batchData.batch_id} signing in process`); } else if (batchData.status === "BATCHED_COMMANDS_STATUS_SIGNED") { const approveTx = yield this.sendApproveTx(destChainId, batchData.execute_data, wallet); return { success: true, approveTx, infoLogs: [ `broadcasting: batch ID ${batchData.batch_id} broadcasted to ${destChainId}`, ], }; } else { return Promise.reject(`findBatchAndApproveGateway(): batch ID ${batchData.batch_id} is in an unknown state for command data: ${commandId}`); } }), 3, 10).catch((error) => { return { success: false, errorMessage: error.message || // error can be both a string or an object with a message property error || `findBatchAndApproveGatewayIfNeeded(): issue retrieving and broadcasting command data: ${commandId}`, infoLogs: [], }; }); }); } manualRelayToDestChain(txHash_1, txLogIndex_1, txEventIndex_1, evmWalletDetails_1) { return __awaiter(this, arguments, void 0, function* (txHash, txLogIndex, txEventIndex, evmWalletDetails, escapeAfterConfirm = true, messageId) { const { callTx, status } = yield this.queryTransactionStatus(txHash, txLogIndex); /**first check if transaction is already executed */ if (exports.GMPErrorMap[status]) return { success: false, error: exports.GMPErrorMap[status], }; const srcChain = callTx.chain; const destChain = callTx.returnValues.destinationChain; const eventIndex = txEventIndex !== null && txEventIndex !== void 0 ? txEventIndex : callTx._logIndex; const srcChainInfo = yield this.getChainInfo(srcChain); const destChainInfo = yield this.getChainInfo(destChain); const routeDir = this.getRouteDir(srcChainInfo, destChainInfo); const _evmWalletDetails = evmWalletDetails || { useWindowEthereum: true }; if (routeDir === RouteDir.COSMOS_TO_EVM) { return this.recoverCosmosToEvmTx(txHash, _evmWalletDetails, messageId); } else if (routeDir === RouteDir.EVM_TO_COSMOS) { return this.recoverEvmToCosmosTx(srcChain, txHash, eventIndex, _evmWalletDetails); } else if (routeDir === RouteDir.COSMOS_TO_COSMOS) { return this.recoverCosmosToCosmosTx(txHash); } else { return this.recoverEvmToEvmTx(srcChain, destChain, txHash, eventIndex, _evmWalletDetails, escapeAfterConfirm); } }); } getRouteDir(srcChain, destChain) { if (srcChain.module === "axelarnet" && destChain.module === "evm") { return RouteDir.COSMOS_TO_EVM; } else if (srcChain.module === "evm" && destChain.module === "axelarnet") { return RouteDir.EVM_TO_COSMOS; } else if (srcChain.module === "axelarnet" && destChain.module === "axelarnet") { return RouteDir.COSMOS_TO_COSMOS; } else { return RouteDir.EVM_TO_EVM; } } recoverCosmosToCosmosTx(txHash) { return __awaiter(this, void 0, void 0, function* () { const gmpTx = yield this.fetchGMPTransaction(txHash); // Fetch all necessary data to send the route message tx const payload = gmpTx.call.returnValues.payload; const messageId = gmpTx.call.id; // Send the route message tx const routeMessageTx = yield this.routeMessageRequest(messageId, payload, -1).catch(() => undefined); // If the `success` flag is false, return the error response if (!routeMessageTx) { return { success: false, error: "Failed to send RouteMessage to Axelar", }; } // Return the success response return { success: true, routeMessageTx, infoLogs: [`Successfully sent RouteMessage tx for given tx hash ${txHash}`], }; }); } recoverEvmToCosmosTx(srcChain, txHash, txEventIndex, evmWalletDetails) { return __awaiter(this, void 0, void 0, function* () { // Check if the tx is confirmed on the source chain const isConfirmed = yield this.doesTxMeetConfirmHt(srcChain, txHash, evmWalletDetails === null || evmWalletDetails === void 0 ? void 0 : evmWalletDetails.provider); if (!isConfirmed) { const minConfirmLevel = yield this.axelarQueryApi.getConfirmationHeight(srcChain); return { success: false, error: `${txHash} is not confirmed on ${srcChain}. The minimum confirmation height is ${minConfirmLevel}`, }; } // ConfirmGatewayTx and check if it is successfully executed const confirmTx = yield this.confirmGatewayTx(txHash, srcChain).catch(() => undefined); if (!confirmTx) { return { success: false, error: "Failed to send ConfirmGatewayTx to Axelar", }; } // Fetch all necessary data to send the route message tx const payload = yield this.fetchGMPTransaction(txHash).then((data) => data.call.returnValues.payload); const eventIndex = txEventIndex !== null && txEventIndex !== void 0 ? txEventIndex : (yield this.getEventIndex(srcChain, txHash)); // Send the route message tx const routeMessageTx = yield this.routeMessageRequest(txHash, payload, eventIndex).catch(() => undefined); // If the `success` flag is false, return the error response if (!routeMessageTx) { return { success: false, error: "Failed to send RouteMessage to Axelar", }; } // Return the success response return { success: true, confirmTx, routeMessageTx, infoLogs: [ `Successfully sent ConfirmGatewayTx tx for given tx hash ${txHash}`, `Successfully sent RouteMessage tx for given tx hash ${txHash}`, ], }; }); } recoverCosmosToEvmTx(txHash, evmWalletDetails, msgIdParam) { return __awaiter(this, void 0, void 0, function* () { const txDetails = yield this.fetchGMPTransaction(txHash); const { messageId: msgIdFromAxelarscan, payload, destinationChain, } = txDetails.call.returnValues; const { command_id: commandId } = txDetails; const messageId = msgIdParam !== null && msgIdParam !== void 0 ? msgIdParam : msgIdFromAxelarscan; // Send RouteMessageTx const routeMessageTx = yield this.routeMessageRequest(messageId, payload, -1).catch(() => undefined); if (!routeMessageTx) { return { success: false, error: "Failed to send RouteMessage to Axelar", }; } // Dispatch a SignCommand transaction and an Approve transaction to the Gateway contract. const response = yield this.signAndApproveGateway(commandId, destinationChain, evmWalletDetails); // If the response.success is false, we will return the error response if (!response.success) { return { success: false, error: response.error, }; } // Otherwise, we will return the success response const { signCommandTx, infoLogs: signTxLogs } = response; return { success: true, routeMessageTx, signCommandTx, infoLogs: [`Successfully sent RouteMessage tx for given ${txHash}`, ...signTxLogs], }; }); } recoverEvmToEvmTx(srcChain_1, destChain_1, txHash_1, txEventIndex_1, evmWalletDetails_1) { return __awaiter(this, arguments, void 0, function* (srcChain, destChain, txHash, txEventIndex, evmWalletDetails, escapeAfterConfirm = true) { try { // ConfirmGatewayTx and check if it is successfully executed const confirmTxRequest = yield this.findEventAndConfirmIfNeeded(srcChain, destChain, txHash, txEventIndex, evmWalletDetails); // If the `success` flag is false, we will return the error response if (!(confirmTxRequest === null || confirmTxRequest === void 0 ? void 0 : confirmTxRequest.success)) { return { success: false, error: confirmTxRequest.errorMessage || types_1.ApproveGatewayError.ERROR_BATCHED_COMMAND, }; } const { infoLogs: confirmTxLogs, commandId, confirmTx } = confirmTxRequest; // If the `escapeAfterConfirm` flag is set to true, we will return the `confirmTx` and `infoLogs` immediately without signing the batch. if (confirmTx && escapeAfterConfirm) { return { success: true, confirmTx, infoLogs: confirmTxLogs, }; } // Find the batch and sign it const response = yield this.signAndApproveGateway(commandId, destChain, evmWalletDetails); // If the response.success is false, we will return the error response if (!response.success) { return { success: false, error: response.error, }; } // Otherwise, we will return the success response const { signCommandTx, approveTx, infoLogs: signTxLogs } = response; return { success: true, confirmTx, signCommandTx, approveTx, infoLogs: [...confirmTxLogs, ...signTxLogs], }; // If more code is required here, you can add it below. } catch (e) { return { success: false, error: e.message || types_1.ApproveGatewayError.CONFIRM_COMMAND_FAILED, }; } }); } signAndApproveGateway(commandId, destChain, evmWalletDetails) { return __awaiter(this, void 0, void 0, function* () { try { const signTxRequest = yield this.findBatchAndSignIfNeeded(commandId, destChain); if (!(signTxRequest === null || signTxRequest === void 0 ? void 0 : signTxRequest.success)) { return { success: false, error: signTxRequest.errorMessage || types_1.ApproveGatewayError.SIGN_COMMAND_FAILED, }; } const broadcastTxRequest = yield this.findBatchAndApproveGateway(commandId, destChain, evmWalletDetails); if (!(broadcastTxRequest === null || broadcastTxRequest === void 0 ? void 0 : broadcastTxRequest.success)) { return { success: false, error: broadcastTxRequest.errorMessage || types_1.ApproveGatewayError.ERROR_BROADCAST_EVENT, }; } return { success: true, signCommandTx: signTxRequest.signCommandTx, approveTx: broadcastTxRequest.approveTx, infoLogs: [...(signTxRequest.infoLogs || []), ...(broadcastTxRequest.infoLogs || [])], }; } catch (e) { return { success: false, error: e.message || `Error signing and approving gateway for commandId: ${commandId}`, }; } }); } /** * Check if given transaction is already executed. * @param txHash string - transaction hash * @returns Promise<boolean> - true if transaction is already executed */ isExecuted(txHash) { return __awaiter(this, void 0, void 0, function* () { const txStatus = yield this.queryTransactionStatus(txHash).catch(() => undefined); return (txStatus === null || txStatus === void 0 ? void 0 : txStatus.status) === AxelarRecoveryApi_1.GMPStatus.DEST_EXECUTED; }); } /** * Check if given transaction is already confirmed. * @param txHash string - transaction hash * @returns Promise<boolean> - true if transaction is already confirmed */ isConfirmed(txHash) { return __awaiter(this, void 0, void 0, function* () { const txStatus = yield this.queryTransactionStatus(txHash).catch(() => undefined); return [AxelarRecoveryApi_1.GMPStatus.SRC_GATEWAY_CONFIRMED, AxelarRecoveryApi_1.GMPStatus.DEST_GATEWAY_APPROVED].includes(this.parseGMPStatus(txStatus === null || txStatus === void 0 ? void 0 : txStatus.status)); }); } /** * Calculate the gas fee in native token for executing a transaction at the destination chain using the source chain's gas price. * @param txHash string - transaction hash * @param sourceChain EVMChain - source chain * @param destinationChain EVMChain - destination chain * @param gasTokenSymbol string - gas token symbol * @param options QueryGasFeeOptions - options * @returns Promise<string> - The gas fee to be paid at source chain */ calculateNativeGasFee(txHash, sourceChain, destinationChain, estimatedGasUsed, options) { return __awaiter(this, void 0, void 0, function* () { yield (0, utils_1.throwIfInvalidChainIds)([sourceChain, destinationChain], this.environment); const provider = options.provider || (0, providerHelper_1.getDefaultProvider)(sourceChain, this.environment); const receipt = yield provider.getTransactionReceipt(txHash); const paidGasFee = (0, contractEventHelper_1.getNativeGasAmountFromTxReceipt)(receipt) || "0"; const hasTxBeenConfirmed = (yield this.isConfirmed(txHash)) || false; options.shouldSubtractBaseFee = hasTxBeenConfirmed; return this.subtractGasFee(sourceChain, destinationChain, paidGasFee, estimatedGasUsed, options); }); } /** * Calculate the gas fee in an ERC-20 tokens for executing a transaction at the destination chain using the source chain's gas price. * @param txHash string - transaction hash * @param sourceChain EVMChain - source chain * @param destinationChain EVMChain - destination chain * @param gasTokenSymbol string - gas token symbol * @param options QueryGasFeeOptions - options * @returns Promise<string> - The gas fee to be paid at source chain */ calculateGasFee(txHash, sourceChain, destinationChain, estimatedGasUsed, options) { return __awaiter(this, void 0, void 0, function* () { yield (0, utils_1.throwIfInvalidChainIds)([sourceChain, destinationChain], this.environment); const provider = options.provider || (0, providerHelper_1.getDefaultProvider)(sourceChain, this.environment); const receipt = yield provider.getTransactionReceipt(txHash); const paidGasFee = (0, contractEventHelper_1.getGasAmountFromTxReceipt)(receipt) || "0"; return this.subtractGasFee(sourceChain, destinationChain, paidGasFee, estimatedGasUsed, options); }); } getEventIndex(chain, txHash, evmWalletDetails) { return __awaiter(this, void 0, void 0, function* () { const signer = this.getSigner(chain, evmWalletDetails || { useWindowEthereum: false }); const receipt = yield signer.provider.getTransactionReceipt(txHash).catch(() => undefined); if (!receipt) { const gmpTx = yield this.fetchGMPTransaction(txHash).catch(() => undefined); if (!gmpTx) return -1; return parseInt(gmpTx.call._logIndex); } else { const eventIndex = (0, contractEventHelper_1.getEventIndexFromTxReceipt)(receipt); return eventIndex; } }); } addGasToSuiChain(params) { return __awaiter(this, void 0, void 0, function* () { const { amount, messageId, gasParams, refundAddress } = params; const chains = yield (0, chains_1.importS3Config)(this.environment); const suiKey = Object.keys(chains.chains).find((chainName) => chainName.includes("sui")); if (!suiKey) throw new Error("Cannot find sui chain config"); const suiConfig = chains.chains[suiKey]; const gasServiceContract = suiConfig.contracts.GasService; const gasAmount = amount ? BigInt(amount) : (0, utils_2.parseUnits)("0.01", 9).toBigInt(); const tx = new transactions_1.Transaction(); const [gas] = tx.splitCoins(tx.gas, [tx.pure.u64(gasAmount)]); tx.moveCall({ target: `${gasServiceContract.address}::gas_service::add_gas`, arguments: [ tx.object(gasServiceContract.objects.GasService), gas, tx.pure(bcs_1.bcs.string().serialize(messageId).toBytes()), tx.pure.address(refundAddress), tx.pure(bcs_1.bcs.vector(bcs_1.bcs.u8()).serialize((0, utils_2.arrayify)(gasParams)).toBytes()), ], }); return tx; }); } /** * Builds an XDR transaction to add gas payment to the Axelar Gas Service contract. * * This function creates a Stellar transaction that adds gas payment to the Axelar Gas Service. * The payment is made in native XLM token by default and is used to cover the execution costs of * cross-chain messages. * * @example * ```typescript * const xdr = await sdk.addGasToStellarChain{ * senderAddress: 'GCXXX...', // The address that sent the cross-chain message via the `axelar_gateway` * messageId: 'tx-123', * amount: '10000000', // the token amount to pay for the gas fee * spender: 'GXXX...' // The spender pays for the gas fee. * }); * * // Sign with Freighter wallet * const signedXDR = await window.freighter.signTransaction(xdr); * ``` * * @param {AddGasStellarParams} params - The parameters for the add gas transaction * @returns {Promise<string>} The transaction encoded as an XDR string, ready for signing */ addGasToStellarChain(params) { return __awaiter(this, void 0, void 0, function* () { const isDevnet = this.environment === types_1.Environment.DEVNET; // TODO: remove this once this supports on mainnet if (!isDevnet) throw new Error("This method only supports on devnet"); const { senderAddress, messageId, contractAddress, tokenAddress, amount, spender } = params; // TODO: Replace with the value from the config file const contractId = contractAddress || "CDBPOARU5MFSC7ZWXTVPVKDZRHKOPS5RCY2VP2OKOBLCMQM3NKVP6HO7"; const server = new StellarSdk.rpc.Server("https://soroban-testnet.stellar.org"); // this will be StellarSdk.Networks.PUBLIC once mainnet is supported const networkPassphrase = StellarSdk.Networks.TESTNET; const caller = StellarSdk.nativeToScVal(StellarSdk.Address.fromString(senderAddress), { type: "address", }); const contract = new StellarSdk.Contract(contractId); const nativeAssetAddress = StellarSdk.Asset.native().contractId(networkPassphrase); const operation = contract.call("add_gas", caller, StellarSdk.nativeToScVal(messageId, { type: "string" }), StellarSdk.nativeToScVal(spender, { type: "address" }), (0, stellarHelper_1.tokenToScVal)(tokenAddress || nativeAssetAddress, amount || "1")); const spenderAccount = yield server.getAccount(spender); const transaction = new StellarSdk.TransactionBuilder(spenderAccount, { fee: StellarSdk.BASE_FEE, networkPassphrase, }) .addOperation(operation) .setTimeout(30) .build(); return transaction.toXDR(); }); } addGasToCosmosChain(_a) { return __awaiter(this, void 0, void 0, function* () { var _b, _c, _d, _e; var { gasLimit, autocalculateGasOptions, sendOptions } = _a, params = __rest(_a, ["gasLimit", "autocalculateGasOptions", "sendOptions"]); const chainConfigs = yield this.getStaticInfo(); let chainConfig; Object.keys(chainConfigs.chains).forEach((key) => { const potentialConfig = chainConfigs.chains[key]; if (potentialConfig.id === params.chain) { chainConfig = potentialConfig; } }); if (!chainConfig) { throw new Error(`chain ID ${params.chain} not found`); } const { rpc, channelIdToAxelar } = chainConfig; const tx = yield this.fetchGMPTransaction(params.txHash); if (!tx) { return { success: false, info: `${params.txHash} could not be found`, }; } const denom = (_c = (_b = tx.gas_paid) === null || _b === void 0 ? void 0 : _b.returnValues) === null || _c === void 0 ? void 0 : _c.asset; const denomOnSrcChain = getIBCDenomOnSrcChain(denom, chainConfig, chainConfigs); if (!matchesOriginalTokenPayment(params.token, denomOnSrcChain)) { return { success: false, info: `The token you are trying to send does not match the token originally \ used for gas payment. Please send ${denom} instead`, }; } const coin = params.token !== "autocalculate" ? params.token : { denom: denomOnSrcChain, amount: yield this.axelarQueryApi.estimateGasFee(tx.call.chain, tx.call.returnValues.destinationChain, gasLimit, autocalculateGasOptions === null || autocalculateGasOptions === void 0 ? void 0 : autocalculateGasOptions.gasMultipler, denom !== null && denom !== void 0 ? denom : "uaxl"), }; const sender = yield sendOptions.offlineSigner.getAccounts().then(([acc]) => acc === null || acc === void 0 ? void 0 : acc.address); if (!sender) { return { success: false, info: `Could not find sender from designated offlineSigner`, }; } const rpcUrl = (_d = sendOptions.rpcUrl) !== null && _d !== void 0 ? _d : rpc[0]; if (!rpcUrl) { return { success: false, info: "Missing RPC URL. Please pass in an rpcUrl parameter in the sendOptions parameter", }; } const signer = yield (0, exports.getCosmosSigner)(rpcUrl, sendOptions.offlineSigner); const broadcastResult = yield signer.signAndBroadcast(sender, [ { typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", value: { sourcePort: "transfer", sourceChannel: channelIdToAxelar, token: coin, sender, receiver: cosmosGasReceiverOptions_1.COSMOS_GAS_RECEIVER_OPTIONS[this.environment], timeoutTimestamp: (_e = sendOptions.timeoutTimestamp) !== null && _e !== void 0 ? _e : (Date.now() + 90) * 1e9, memo: tx.call.id, }, }, ], sendOptions.txFee); return { success: true, info: "", broadcastResult, }; }); } /** * Pay native token as gas fee for the given transaction hash. * If the transaction details is not valid, it will return an error with reason. * @param chain - source chain * @param txHash - transaction hash * @param estimatedGasUsed - estimated gas used * @param options - options * @returns */ addNativeGas(chain, txHash, estimatedGasUsed, options) { return __awaiter(this, void 0, void 0, function* () { var _a; const evmWalletDetails = (options === null || options === void 0 ? void 0 : options.evmWalletDetails) || { useWindowEthereum: true }; const signer = this.getSigner(chain, evmWalletDetails); const signerAddress = yield signer.getAddress(); const gasReceiverAddress = yield this.axelarQueryApi.getContractAddressFromConfig(chain, "gas_service"); const receipt = yield signer.provider.getTransactionReceipt(txHash); if (!receipt) return (0, error_1.InvalidTransactionError)(chain); const destinationChain = (options === null || options === void 0 ? void 0 : options.destChain) || (0, contractEventHelper_1.getDestinationChainFromTxReceipt)(receipt); const logIndex = (_a = options === null || options === void 0 ? void 0 : options.logIndex) !== null && _a !== void 0 ? _a : (0, contractEventHelper_1.getLogIndexFromTxReceipt)(receipt); // Check if given txHash is valid if (!destinationChain) return (0, error_1.NotGMPTransactionError)(); // Check if the transaction status is already executed or not. const _isExecuted = yield this.isExecuted(txHash); if (_isExecuted) return (0, error_1.AlreadyExecutedError)(); let gasFeeToAdd = options === null || options === void 0 ? void 0 : options.amount; if (!gasFeeToAdd) { gasFeeToAdd = yield this.calculateNativeGasFee(txHash, chain, destinationChain, estimatedGasUsed, { gasMultipler: options === null || options === void 0 ? void 0 : options.gasMultipler, provider: evmWalletDetails.provider, }).catch(() => undefined); } // Check if gas price is queried successfully. if (!gasFeeToAdd) return (0, error_1.GasPriceAPIError)(); // Check if gas fee is not already sufficiently paid. if (gasFeeToAdd === "0") return (0, error_1.AlreadyPaidGasFeeError)(); const refundAddress = (options === null || options === void 0 ? void 0 : options.refundAddress) || signerAddress; const contract = new ethers_1.ethers.Contract(gasReceiverAddress, [ "function addNativeGas(bytes32 txHash,uint256 logIndex,address refundAddress) external payable", ], signer); return contract .addNativeGas(txHash, logIndex, refundAddress, { value: gasFeeToAdd, }) .then((tx) => tx.wait()) .then((tx) => ({ success: true, transaction: tx, })) .catch(error_1.ContractCallError); }); } /** * Pay ERC20 token as gas fee for the given transaction hash. * If the transaction details or `gasTokenAddress` is not valid, it will return an error with reason. * * @param chain EvmChain - The source chain of the transaction hash. * @param txHash string - The transaction hash. * @param gasTokenAddress string - The address of the ERC20 token to pay as gas fee. * @param options AddGasOptions - The options to pay gas fee. * @returns */ addGas(chain, txHash, gasTokenAddress, estimatedGasUsed, options) { return __awaiter(this, void 0, void 0, function* () { var _a; const evmWalletDetails = (options === null || options === void 0 ? void 0 : options.evmWalletDetails) || { useWindowEthereum: true }; const signer = this.getSigner(chain, evmWalletDetails); const signerAddress = yield signer.getAddress(); const gasReceiverAddress = yield this.axelarQueryApi.getContractAddressFromConfig(chain, "gas_service"); const gasTokenContract = new ethers_1.ethers.Contract(gasTokenAddress, erc20Abi_json_1.default, signer.provider); const gasTokenSymbol = yield gasTokenContract.symbol().catch(() => undefined); // Check if given `gasTokenAddress` exists if (!gasTokenSymbol) return (0, error_1.InvalidGasTokenError)(); const axelarGateway = yield AxelarGateway_1.AxelarGateway.create(this.environment, chain, signer.provider); const gatewayGasTokenAddress = yield axelarGateway.getTokenAddress(gasTokenSymbol); // Check if given `gasTokenAddress` is supported by Axelar. if (gatewayGasTokenAddress === ethers_1.ethers.constants.AddressZero) return (0, error_1.UnsupportedGasTokenError)(gasTokenAddress); const receipt = yield signer.provider.getTransactionReceipt(txHash); // Check if transaction exists if (!receipt) return (0, error_1.InvalidTransactionError)(chain); const destinationChain = (options === null || options === void 0 ? void 0 : options.destChain) || (0, contractEventHelper_1.getDestinationChainFromTxReceipt)(receipt); const logIndex = (_a = options === null || options === void 0 ? void 0 : options.logIndex) !== null && _a !== void 0 ? _a : (0, contractEventHelper_1.getLogIndexFromTxReceipt)(receipt); // Check if given txHash is valid if (!destinationChain) return (0, error_1.NotGMPTransactionError)(); // Check if the transaction status is already executed or not. const _isExecuted = yield this.isExecuted(txHash); if (_isExecuted) return (0, error_1.AlreadyExecutedError)(); let gasFeeToAdd = options === null || options === void 0 ? void 0 : options.amount; if (!gasFeeToAdd) { gasFeeToAdd = yield this.calculateGasFee(txHash, chain, destinationChain, estimatedGasUsed, { provider: evmWalletDetails.provider, }).catch(() => undefined); } // Check if gas price is queried successfully. if (!gasFeeToAdd) return (0, error_1.GasPriceAPIError)(); // Check if gas fee is not already sufficiently paid. if (gasFeeToAdd === "0") return (0, error_1.AlreadyPaidGasFeeError)(); const refundAddress = (options === null || options === void 0 ? void 0 : options.refundAddress) || signerAddress; const contract = new ethers_1.ethers.Contract(gasReceiverAddress, new utils_2.Interface([ "function addGas(bytes32 txHash,uint256 txIndex,address gasToken,uint256 gasFeeAmount,address refundAddress) external", ]), signer); return contract .addGas(txHash, logIndex, gasTokenAddress, gasFeeToAdd, refundAddress) .then((tx) => tx.wait()) .then((tx) => ({ success: true, transaction: tx, })) .catch(error_1.ContractCallError); }); } /** * Execute a transaction on the destination chain associated with given `srcTxHash`. * @param srcTxHash - The transaction hash on the source chain. * @param srcTxLogIndex - The log index of the transaction on the source chain. * @param evmWalletDetails - The wallet details to use for executing the transaction. * @returns The result of executing the transaction. */ execute(srcTxHash, srcTxLogIndex, evmWalletDetails) { return __awaiter(this, void 0, void 0, function* () { const response = yield this.queryExecuteParams(srcTxHash, srcTxLogIndex).catch(() => undefined); // Couldn't query the transaction details