UNPKG

@axelar-network/axelarjs-sdk

Version:
601 lines 28.6 kB
"use strict"; 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AxelarRecoveryApi = exports.GasPaidStatus = exports.GMPStatus = void 0; const cross_fetch_1 = __importDefault(require("cross-fetch")); const ethers_1 = require("ethers"); const chains_1 = require("../../chains"); const constants_1 = require("../../constants"); const utils_1 = require("../../utils"); const AxelarQueryClient_1 = require("../AxelarQueryClient"); const EVMClient_1 = __importDefault(require("./client/EVMClient")); const cosmos_1 = require("./client/helpers/cosmos"); const chain_1 = __importDefault(require("./constants/chain")); const mappers_1 = require("./helpers/mappers"); const AxelarSigningClient_1 = require("../AxelarSigningClient"); const const_1 = require("../AxelarSigningClient/const"); const proto_signing_1 = require("@cosmjs/proto-signing"); const query_1 = require("@axelar-network/axelarjs-types/axelar/evm/v1beta1/query"); const tx_1 = require("@axelar-network/axelarjs-types/axelar/evm/v1beta1/tx"); const tx_2 = require("@axelar-network/axelarjs-types/axelar/axelarnet/v1beta1/tx"); var GMPStatus; (function (GMPStatus) { GMPStatus["SRC_GATEWAY_CALLED"] = "source_gateway_called"; GMPStatus["DEST_GATEWAY_APPROVED"] = "destination_gateway_approved"; GMPStatus["DEST_EXECUTED"] = "destination_executed"; GMPStatus["EXPRESS_EXECUTED"] = "express_executed"; GMPStatus["DEST_EXECUTE_ERROR"] = "error"; GMPStatus["DEST_EXECUTING"] = "executing"; GMPStatus["APPROVING"] = "approving"; GMPStatus["FORECALLED"] = "forecalled"; GMPStatus["FORECALLED_WITHOUT_GAS_PAID"] = "forecalled_without_gas_paid"; GMPStatus["NOT_EXECUTED"] = "not_executed"; GMPStatus["NOT_EXECUTED_WITHOUT_GAS_PAID"] = "not_executed_without_gas_paid"; GMPStatus["INSUFFICIENT_FEE"] = "insufficient_fee"; GMPStatus["UNKNOWN_ERROR"] = "unknown_error"; GMPStatus["CANNOT_FETCH_STATUS"] = "cannot_fetch_status"; GMPStatus["SRC_GATEWAY_CONFIRMED"] = "confirmed"; })(GMPStatus || (exports.GMPStatus = GMPStatus = {})); var GasPaidStatus; (function (GasPaidStatus) { GasPaidStatus["GAS_UNPAID"] = "gas_unpaid"; GasPaidStatus["GAS_PAID"] = "gas_paid"; GasPaidStatus["GAS_PAID_NOT_ENOUGH_GAS"] = "gas_paid_not_enough_gas"; GasPaidStatus["GAS_PAID_ENOUGH_GAS"] = "gas_paid_enough_gas"; })(GasPaidStatus || (exports.GasPaidStatus = GasPaidStatus = {})); const DEFAULT_SUBSCRIPTION_STRATEGY = { kind: "polling", interval: 15000, }; class AxelarRecoveryApi { constructor(config) { this.axelarQuerySvc = null; this.chainsList = []; const { environment } = config; const links = (0, constants_1.getConfigs)(environment); this.axelarGMPApiUrl = links.axelarGMPApiUrl; this.debugMode = !!config.debug; this.axelarscanBaseApiUrl = links.axelarscanBaseApiUrl; this.recoveryApiUrl = links.recoveryApiUrl; this.wssStatusUrl = links.wssStatus; this.axelarRpcUrl = config.axelarRpcUrl || links.axelarRpcUrl; this.axelarLcdUrl = config.axelarLcdUrl || links.axelarLcdUrl; this.environment = environment; this.config = config; } fetchGMPTransaction(txHash, txLogIndex) { return __awaiter(this, void 0, void 0, function* () { return this.execGet(this.axelarGMPApiUrl, { method: "searchGMP", txHash, txLogIndex, }) .then((data) => data.find((gmpTx) => gmpTx.id.toLowerCase().indexOf(txHash.toLowerCase()) > -1 || gmpTx.call.transactionHash.toLowerCase().indexOf(txHash.toLowerCase()) > -1 // the source transaction hash will be stored at "tx.call.transactionHash", if it is sent from cosmos, otherwise it'll be stored at `tx.id` field. )) .catch(() => undefined); }); } fetchBatchData(chainId, commandId) { return __awaiter(this, void 0, void 0, function* () { /**first check axelarscan API */ const batchData = yield this.execPost(this.axelarscanBaseApiUrl, "/batches", { commandId, }) .then((res) => res[0]) .catch(() => undefined); /**if not found, check last few batches on core in case it is an issue of delayed indexing of data on the axelarscan API */ if (!batchData) { return this.searchRecentBatchesFromCore(chainId, commandId); } return batchData; }); } searchRecentBatchesFromCore(chainId, commandId, batchId) { return __awaiter(this, void 0, void 0, function* () { return (0, utils_1.retry)(() => __awaiter(this, void 0, void 0, function* () { const batchData = yield this.queryBatchedCommands(chainId, batchId).catch(() => undefined); if (!batchData) return; if (!batchData.commandIds.includes(commandId)) return Promise.reject("command id not found in batch"); return (0, mappers_1.mapIntoAxelarscanResponseType)(batchData, chainId); })); }); } parseGMPStatus(response) { const { error, status } = response; if (status === "error" && error) return GMPStatus.DEST_EXECUTE_ERROR; else if (status === "executed") return GMPStatus.DEST_EXECUTED; else if (status === "approved") return GMPStatus.DEST_GATEWAY_APPROVED; else if (status === "called") return GMPStatus.SRC_GATEWAY_CALLED; else if (status === "executing") return GMPStatus.DEST_EXECUTING; else { return status; } } parseGMPError(response) { if (response.error) { return { message: response.error.error.message, txHash: response.error.error.transactionHash, chain: response.error.chain, }; } else if (response.is_insufficient_fee) { return { message: "Insufficient fee", txHash: response.call.transaction.hash, chain: response.call.chain, }; } } queryTransactionStatus(txHash, txLogIndex) { return __awaiter(this, void 0, void 0, function* () { const txDetails = yield this.fetchGMPTransaction(txHash, txLogIndex); if (!txDetails) return { status: GMPStatus.CANNOT_FETCH_STATUS }; const { call, gas_status, gas_paid, executed, express_executed, approved, callback } = txDetails; const gasPaidInfo = { status: gas_status, details: gas_paid, }; // Note: Currently, the GMP API doesn't always return the `total` field in the `time_spent` object // This is a temporary fix to ensure that the `total` field is always present // TODO: Remove this once the API is fixed const timeSpent = txDetails.time_spent; if (timeSpent) { timeSpent.total = timeSpent.total || Object.values(timeSpent).reduce((accumulator, value) => accumulator + value, 0); } return { status: this.parseGMPStatus(txDetails), error: this.parseGMPError(txDetails), timeSpent, gasPaidInfo, callTx: call, executed, expressExecuted: express_executed, approved, callback, }; }); } /** * Subscribe to a transaction status using either a websocket or polling strategy */ subscribeToTx(txHash_1, cb_1) { return __awaiter(this, arguments, void 0, function* (txHash, cb, strategy = DEFAULT_SUBSCRIPTION_STRATEGY) { if (strategy.kind === "websocket") { this.subscribeToTxWSS_EXPERIMENTAL(txHash, cb); } else { this.subscribeToTxPOLLING(txHash, cb, strategy.interval); } }); } /** * Subscribe to a transaction status using a polling strategy */ subscribeToTxPOLLING(txHash_1, cb_1) { return __awaiter(this, arguments, void 0, function* (txHash, cb, interval = 30000 // 30 seconds ) { const intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { const response = yield this.queryTransactionStatus(txHash); cb(response); if (response.status === GMPStatus.DEST_EXECUTED) { clearInterval(intervalId); } }), interval); }); } /** * Subscribe to a transaction status using a websocket strategy (Experimental) */ subscribeToTxWSS_EXPERIMENTAL(txHash, cb) { return __awaiter(this, void 0, void 0, function* () { const conn = new WebSocket(this.wssStatusUrl); conn.onopen = () => { const msg = JSON.stringify({ action: "sendmessage", topic: "subscribeToSrcChainTx", srcTxHash: txHash, }); conn.send(msg); }; conn.onmessage = (event) => { const resData = typeof event.data === "string" ? JSON.parse(event.data) : event.data; if (resData === null || resData === void 0 ? void 0 : resData.txStatus) { resData.txStatus = this.parseGMPStatus({ status: resData.txStatus, error: "" }); } if ((resData === null || resData === void 0 ? void 0 : resData.txStatus) === GMPStatus.DEST_EXECUTED) conn.close(); cb === null || cb === void 0 ? void 0 : cb(resData); }; }); } queryExecuteParams(txHash, txLogIndex) { return __awaiter(this, void 0, void 0, function* () { const data = yield this.fetchGMPTransaction(txHash, txLogIndex); if (!data) return; // Return if approve tx doesn't not exist const approvalTx = data.approved; if (!(approvalTx === null || approvalTx === void 0 ? void 0 : approvalTx.transactionHash)) { return { status: GMPStatus.SRC_GATEWAY_CALLED, }; } // Return if it's already executed const executeTx = data.executed; if (executeTx === null || executeTx === void 0 ? void 0 : executeTx.transactionHash) { return { status: GMPStatus.DEST_EXECUTED, }; } const callTx = data.call; return { status: GMPStatus.DEST_GATEWAY_APPROVED, data: { commandId: approvalTx.returnValues.commandId, destinationChain: approvalTx.chain.toLowerCase(), destinationContractAddress: callTx.returnValues.destinationContractAddress, isContractCallWithToken: callTx.event === "ContractCallWithToken", payload: callTx.returnValues.payload, srcTxInfo: { transactionHash: callTx.transactionHash, transactionIndex: callTx.transactionIndex, logIndex: callTx.logIndex, }, sourceAddress: approvalTx.returnValues.sourceAddress, sourceChain: approvalTx.returnValues.sourceChain, symbol: approvalTx.returnValues.symbol, amount: approvalTx.returnValues.amount && ethers_1.BigNumber.from(approvalTx.returnValues.amount).toString(), }, }; }); } getChainInfo(chainId) { return __awaiter(this, void 0, void 0, function* () { if (this.chainsList.length === 0) { this.chainsList = yield (0, chains_1.loadChains)({ environment: this.environment, }); } const chainInfo = this.chainsList.find((chainInfo) => chainInfo.id.toLowerCase() === chainId.toLowerCase()); if (!chainInfo) { throw new Error(`cannot find chain ${chainId}`); } return chainInfo; }); } getCosmosSignerDetails(cosmosWalletDetails) { if ((cosmosWalletDetails === null || cosmosWalletDetails === void 0 ? void 0 : cosmosWalletDetails.offlineSigner) || (cosmosWalletDetails === null || cosmosWalletDetails === void 0 ? void 0 : cosmosWalletDetails.mnemonic)) { return cosmosWalletDetails; } return undefined; } resolveSelfSigningOptions(optionsOrCosmosWalletDetails, useSelfSigning = false) { if (!optionsOrCosmosWalletDetails || typeof optionsOrCosmosWalletDetails !== "object") { return { cosmosWalletDetails: undefined, evmWalletDetails: undefined, shouldSelfSign: useSelfSigning, }; } if ("offlineSigner" in optionsOrCosmosWalletDetails || "mnemonic" in optionsOrCosmosWalletDetails) { return { cosmosWalletDetails: optionsOrCosmosWalletDetails, evmWalletDetails: undefined, shouldSelfSign: useSelfSigning, }; } const options = optionsOrCosmosWalletDetails; const cosmosSignerDetails = this.getCosmosSignerDetails(options.cosmosWalletDetails); const shouldSelfSign = Boolean(cosmosSignerDetails) || useSelfSigning; if (!cosmosSignerDetails && useSelfSigning) { console.warn("[recovery self-sign options ignored]", { reason: "cosmos signing material missing", }); } return { cosmosWalletDetails: cosmosSignerDetails, evmWalletDetails: options.evmWalletDetails, shouldSelfSign, }; } getCosmosSenderAddress(cosmosWalletDetails) { return __awaiter(this, void 0, void 0, function* () { const cosmosSignerDetails = this.getCosmosSignerDetails(cosmosWalletDetails); if (!cosmosSignerDetails) { return undefined; } if (cosmosSignerDetails.offlineSigner) { const [account] = yield cosmosSignerDetails.offlineSigner.getAccounts(); return account === null || account === void 0 ? void 0 : account.address; } if (cosmosSignerDetails.mnemonic) { const wallet = yield proto_signing_1.DirectSecp256k1HdWallet.fromMnemonic(cosmosSignerDetails.mnemonic, { prefix: "axelar", }); const [account] = yield wallet.getAccounts(); return account === null || account === void 0 ? void 0 : account.address; } return undefined; }); } signRecoveryMessagesWithCosmosWallet(messages, cosmosWalletDetails) { return __awaiter(this, void 0, void 0, function* () { const signingClient = yield AxelarSigningClient_1.AxelarSigningClient.initOrGetAxelarSigningClient({ environment: this.environment, axelarRpcUrl: this.axelarRpcUrl, cosmosBasedWalletDetails: cosmosWalletDetails, options: {}, }); try { return yield signingClient.signThenBroadcast(messages, const_1.STANDARD_FEE); } finally { yield signingClient.disconnect(); } }); } trySelfSignAndBroadcast(actionType_1, context_1, buildMessages_1, cosmosWalletDetails_1) { return __awaiter(this, arguments, void 0, function* (actionType, context, buildMessages, cosmosWalletDetails, shouldSelfSign = false) { if (!shouldSelfSign) { return; } const cosmosSignerDetails = this.getCosmosSignerDetails(cosmosWalletDetails); if (!cosmosSignerDetails) { throw new Error(`Recovery self-signing requires a cosmos wallet signer for ${actionType}`); } try { const messages = yield buildMessages(); return yield this.signRecoveryMessagesWithCosmosWallet(messages, cosmosSignerDetails); } catch (error) { console.error("[recovery self-sign failed]", Object.assign(Object.assign({ action_type: actionType, use_self_signing: true }, context), { error })); throw error; } }); } toAxelarTxResponse(txResponse) { return Object.assign(Object.assign({}, txResponse), { rawLog: txResponse.rawLog || "[]" }); } confirmGatewayTx(txHash_1, chainName_1, optionsOrCosmosWalletDetails_1) { return __awaiter(this, arguments, void 0, function* (txHash, chainName, optionsOrCosmosWalletDetails, legacyUseSelfSigning = false) { const { cosmosWalletDetails, shouldSelfSign } = this.resolveSelfSigningOptions(optionsOrCosmosWalletDetails, legacyUseSelfSigning); const { module, chainIdentifier } = yield this.getChainInfo(chainName); const chain = chainIdentifier[this.environment]; const selfSignedTx = yield this.trySelfSignAndBroadcast("confirm_gateway_tx", { tx_hash: txHash, source_chain: chainName, chain_identifier: chain, }, () => __awaiter(this, void 0, void 0, function* () { const sender = yield this.getCosmosSenderAddress(cosmosWalletDetails); if (!sender) { throw new Error("cosmos signer is not connected"); } return [ { typeUrl: `/${tx_1.protobufPackage}.ConfirmGatewayTxRequest`, value: tx_1.ConfirmGatewayTxRequest.fromPartial({ sender, chain, txId: Buffer.from(ethers_1.utils.arrayify(txHash)), }), }, ]; }), cosmosWalletDetails, shouldSelfSign); if (selfSignedTx) { return this.toAxelarTxResponse(selfSignedTx); } const txBytes = yield this.execRecoveryUrlFetch("/confirm_gateway_tx", { txHash, module, chain, }); return (0, cosmos_1.broadcastCosmosTxBytes)(txBytes, this.axelarRpcUrl); }); } createPendingTransfers(chainName) { return __awaiter(this, void 0, void 0, function* () { const { module, chainIdentifier } = yield this.getChainInfo(chainName); const txBytes = yield this.execRecoveryUrlFetch("/create_pending_transfers", { chain: chainIdentifier[this.environment], module, }); return (0, cosmos_1.broadcastCosmosTxBytes)(txBytes, this.axelarRpcUrl); }); } executePendingTransfers(chainName) { return __awaiter(this, void 0, void 0, function* () { const { module, chainIdentifier } = yield this.getChainInfo(chainName); const txBytes = yield this.execRecoveryUrlFetch("/execute_pending_transfers", { chain: chainIdentifier[this.environment], module, }); return (0, cosmos_1.broadcastCosmosTxBytes)(txBytes, this.axelarRpcUrl); }); } routeMessageRequest(txHash_1, payload_1, logIndex_1, optionsOrCosmosWalletDetails_1) { return __awaiter(this, arguments, void 0, function* (txHash, payload, logIndex, optionsOrCosmosWalletDetails, legacyUseSelfSigning = false) { const { cosmosWalletDetails, shouldSelfSign } = this.resolveSelfSigningOptions(optionsOrCosmosWalletDetails, legacyUseSelfSigning); const id = logIndex === -1 ? `${txHash}` : `${txHash}-${logIndex}`; const selfSignedTx = yield this.trySelfSignAndBroadcast("route_message", { tx_hash: txHash, log_index: logIndex, id, }, () => __awaiter(this, void 0, void 0, function* () { const sender = yield this.getCosmosSenderAddress(cosmosWalletDetails); if (!sender) { throw new Error("cosmos signer is not connected"); } return [ { typeUrl: `/${tx_2.protobufPackage}.RouteMessageRequest`, value: tx_2.RouteMessageRequest.fromPartial({ sender, id, payload: Buffer.from(ethers_1.utils.arrayify(payload)), }), }, ]; }), cosmosWalletDetails, shouldSelfSign); if (selfSignedTx) { return this.toAxelarTxResponse(selfSignedTx); } const txBytes = yield this.execRecoveryUrlFetch("/route_message", { payload, id, }); return (0, cosmos_1.broadcastCosmosTxBytes)(txBytes, this.axelarRpcUrl); }); } signCommands(chainName_1, optionsOrCosmosWalletDetails_1) { return __awaiter(this, arguments, void 0, function* (chainName, optionsOrCosmosWalletDetails, legacyUseSelfSigning = false) { const { cosmosWalletDetails, shouldSelfSign } = this.resolveSelfSigningOptions(optionsOrCosmosWalletDetails, legacyUseSelfSigning); const { module, chainIdentifier } = yield this.getChainInfo(chainName); const chain = chainIdentifier[this.environment]; const selfSignedTx = yield this.trySelfSignAndBroadcast("sign_commands", { destination_chain: chainName, chain_identifier: chain, }, () => __awaiter(this, void 0, void 0, function* () { const sender = yield this.getCosmosSenderAddress(cosmosWalletDetails); if (!sender) { throw new Error("cosmos signer is not connected"); } return [ { typeUrl: `/${tx_1.protobufPackage}.SignCommandsRequest`, value: tx_1.SignCommandsRequest.fromPartial({ sender, chain, }), }, ]; }), cosmosWalletDetails, shouldSelfSign); if (selfSignedTx) { return this.toAxelarTxResponse(selfSignedTx); } const txBytes = yield this.execRecoveryUrlFetch("/sign_commands", { chain, module, }); return (0, cosmos_1.broadcastCosmosTxBytes)(txBytes, this.axelarRpcUrl); }); } queryBatchedCommands(chainId_1) { return __awaiter(this, arguments, void 0, function* (chainId, batchCommandId = "") { if (!this.axelarQuerySvc) this.axelarQuerySvc = yield AxelarQueryClient_1.AxelarQueryClient.initOrGetAxelarQueryClient(this.config); yield (0, utils_1.throwIfInvalidChainIds)([chainId], this.environment); return this.axelarQuerySvc.evm.BatchedCommands(query_1.BatchedCommandsRequest.create({ chain: chainId, id: batchCommandId })); }); } queryGatewayAddress(_a) { return __awaiter(this, arguments, void 0, function* ({ chain }) { if (!this.axelarQuerySvc) this.axelarQuerySvc = yield AxelarQueryClient_1.AxelarQueryClient.initOrGetAxelarQueryClient(this.config); yield (0, utils_1.throwIfInvalidChainIds)([chain], this.environment); return this.axelarQuerySvc.evm.GatewayAddress(query_1.GatewayAddressRequest.create({ chain })); }); } getSignedTxAndBroadcast(chain, data) { return __awaiter(this, void 0, void 0, function* () { yield (0, utils_1.throwIfInvalidChainIds)([chain], this.environment); const gatewayInfo = yield this.queryGatewayAddress({ chain }); const evmClient = new EVMClient_1.default({ rpcUrl: chain_1.default[this.environment].rpcMap[chain], evmWalletDetails: { useWindowEthereum: true }, }); const txRequest = evmClient.buildUnsignedTx(gatewayInfo.address, { data }); const signedTx = yield this.execRecoveryUrlFetch("/sign_evm_tx", { chain, gatewayAddress: gatewayInfo.address, txRequest, }); const tx = yield evmClient.broadcastSignedTx(signedTx); tx.wait(1); return tx; }); } sendApproveTx(chain, data, evmWalletDetails) { return __awaiter(this, void 0, void 0, function* () { yield (0, utils_1.throwIfInvalidChainIds)([chain], this.environment); const gatewayInfo = yield this.queryGatewayAddress({ chain }); const evmClient = new EVMClient_1.default({ rpcUrl: chain_1.default[this.environment].rpcMap[chain], evmWalletDetails, }); const txRequest = evmClient.buildUnsignedTx(gatewayInfo.address, { data }); return this.execRecoveryUrlFetch("/send_evm_tx", { chain, gatewayAddress: gatewayInfo.address, txRequest, }); }); } broadcastEvmTx(chain_2, data_1) { return __awaiter(this, arguments, void 0, function* (chain, data, evmWalletDetails = { useWindowEthereum: true }) { yield (0, utils_1.throwIfInvalidChainIds)([chain], this.environment); const gatewayInfo = yield this.queryGatewayAddress({ chain }); const evmClient = new EVMClient_1.default({ rpcUrl: chain_1.default[this.environment].rpcMap[chain], evmWalletDetails, }); return evmClient.broadcastToGateway(gatewayInfo.address, { data }); }); } execRecoveryUrlFetch(endpoint, params) { return __awaiter(this, void 0, void 0, function* () { return this.execPost(this.recoveryApiUrl, endpoint, params); }); } execPost(base, endpoint, params) { return __awaiter(this, void 0, void 0, function* () { return (0, cross_fetch_1.default)(base + endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params), }) .then((res) => res.json()) .then((res) => res.data); }); } execGet(base, params) { return __awaiter(this, void 0, void 0, function* () { return (0, cross_fetch_1.default)(`${base}?${new URLSearchParams(params).toString()}`, { method: "GET", headers: { "Content-Type": "application/json" }, cache: "no-store", }) .then((res) => res.json()) .then((res) => res.data); }); } get getAxelarGMPApiUrl() { return this.axelarGMPApiUrl; } } exports.AxelarRecoveryApi = AxelarRecoveryApi; //# sourceMappingURL=AxelarRecoveryApi.js.map