UNPKG

@kaiachain/ethers-ext

Version:
171 lines (170 loc) 7.67 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.pollTransactionInPool = exports.poll = exports.sleep = exports.populateFeePayerAndSignatures = exports.populateChainId = exports.eip155sign = exports.populateGasPrice = exports.populateGasLimit = exports.populateNonce = exports.populateTo = exports.populateFrom = exports.getTransactionRequest = void 0; const ethers_1 = require("ethers"); const lodash_1 = __importDefault(require("lodash")); const js_ext_core_1 = require("@kaiachain/js-ext-core"); // Normalize transaction request in Object or RLP format async function getTransactionRequest(transactionOrRLP) { if (lodash_1.default.isString(transactionOrRLP)) { return (0, js_ext_core_1.parseTransaction)(transactionOrRLP); } else { if (transactionOrRLP instanceof ethers_1.Transaction) { return transactionOrRLP.toJSON(); } const resolvedTx = await (0, ethers_1.resolveProperties)(transactionOrRLP); // tx values transformation if (typeof resolvedTx?.type === "string") { resolvedTx.type = (0, js_ext_core_1.parseTxType)(resolvedTx.type); } return resolvedTx; } } exports.getTransactionRequest = getTransactionRequest; async function populateFrom(tx, expectedFrom) { if (!tx.from || tx.from == "0x") { tx.from = expectedFrom; } else { (0, ethers_1.assert)(tx.from?.toString().toLowerCase() === expectedFrom?.toLowerCase(), `from address mismatch (wallet address=${expectedFrom}) (tx.from=${tx.from})`, "INVALID_ARGUMENT", { argument: "from", value: tx.from }); tx.from = expectedFrom; } } exports.populateFrom = populateFrom; async function populateTo(tx, provider) { if (!tx.to || tx.to == "0x") { tx.to = ethers_1.ZeroAddress; } else { tx.to = await (0, ethers_1.resolveAddress)(tx.to, provider); } } exports.populateTo = populateTo; async function populateNonce(tx, provider, fromAddress) { if (!tx.nonce) { tx.nonce = await provider?.getTransactionCount(fromAddress); } } exports.populateNonce = populateNonce; async function populateGasLimit(tx, provider) { (0, ethers_1.assert)(!!provider, "provider is undefined", "MISSING_ARGUMENT"); if (!tx.gasLimit) { // Sometimes Klaytn node's eth_estimateGas may return insufficient amount. // To avoid this, add buffer to the estimated gas. // References: // - ethers.js uses estimateGas result as-is. // - Metamask multiplies by 1 or 1.5 depending on chainId // (https://github.com/MetaMask/metamask-extension/blob/v11.3.0/ui/ducks/send/helpers.js#L126) // TODO: To minimize buffer, add constant intrinsic gas overhead instead of multiplier. try { const bufferMultiplier = 2.5; const gasLimit = await provider?.estimateGas(tx); tx.gasLimit = Math.ceil(Number(gasLimit) * bufferMultiplier); // overflow risk when gasLimit exceed Number.MAX_SAFE_INTEGER } catch (error) { (0, ethers_1.assert)(false, "cannot estimate gas; transaction may fail or may require manual gas limit", "UNKNOWN_ERROR", { data: tx, }); } } } exports.populateGasLimit = populateGasLimit; async function populateGasPrice(tx, provider) { if (!tx.gasPrice) { tx.gasPrice = (await provider?.getFeeData())?.gasPrice?.toString(); // https://github.com/ethers-io/ethers.js/discussions/4219 } } exports.populateGasPrice = populateGasPrice; function eip155sign(key, digest, chainId) { const sig = key.sign(digest); const recoveryParam = sig.v === 27 ? 0 : 1; const v = recoveryParam + +chainId * 2 + 35; return { r: sig.r, s: sig.s, v }; } exports.eip155sign = eip155sign; async function populateChainId(tx, provider) { if (!tx.chainId) { tx.chainId = (0, js_ext_core_1.getChainIdFromSignatureTuples)(tx.txSignatures) ?? (0, js_ext_core_1.getChainIdFromSignatureTuples)(tx.feePayerSignatures) ?? (await provider?.getNetwork())?.chainId.toString(); } } exports.populateChainId = populateChainId; async function populateFeePayerAndSignatures(tx, expectedFeePayer) { // A SenderTxHashRLP returned from caver may have dummy feePayer even if SenderTxHashRLP shouldn't have feePayer. // So ignore AddressZero in the feePayer field. if (!tx.feePayer || tx.feePayer == ethers_1.ZeroAddress) { tx.feePayer = expectedFeePayer; } else { (0, ethers_1.assert)(tx.feePayer.toLowerCase() === expectedFeePayer.toLowerCase(), "feePayer address mismatch", "INVALID_ARGUMENT", { argument: "feePayer", value: tx.feePayer, }); tx.feePayer = expectedFeePayer; } // A SenderTxHashRLP returned from caver may have dummy feePayerSignatures if SenderTxHashRLP shouldn't have feePayerSignatures. // So ignore [ '0x01', '0x', '0x' ] in the feePayerSignatures field. if (lodash_1.default.isArray(tx.feePayerSignatures)) { tx.feePayerSignatures = tx.feePayerSignatures.filter((sig) => { return !(lodash_1.default.isArray(sig) && sig.length == 3 && sig[0] == "0x01" && sig[1] == "0x" && sig[2] == "0x"); }); } } exports.populateFeePayerAndSignatures = populateFeePayerAndSignatures; /** * Delay the execution inside an async function in miliseconds. * * @param time (miliseconds) the amount of time to be delayed. */ function sleep(time) { return new Promise((res, _) => { setTimeout(() => res(), time); }); } exports.sleep = sleep; /** * The poll function implements a retry mechanism for asynchronous operations. * It repeatedly calls the callback function to fetch data and then uses the * verify function to check if the retrieved data meets the desired criteria. * * @param callback A callback function that is responsible for fetching or retrieving data. It should be an asynchronous function that returns a Promise. * @param verify A callback function that determines if the retrieved data meets the desired criteria. It should accept the data returned by callback and return a boolean value (true if the data is valid, false otherwise). * @param retries (optional): An integer specifying the maximum number of times the function will attempt to poll before giving up. Defaults to 100. * @returns A Promise that resolves to the data retrieved by callback when the verify function returns true, or rejects with an error if the maximum number of retries is reached. */ async function poll(callback, verify, retries = 100) { let result; for (let i = 0; i < retries; i++) { try { const output = await callback(); if (output && verify(output)) { result = output; break; } } catch (_) { continue; } await sleep(250); } (0, ethers_1.assert)(result, "Transaction timeout!", "NETWORK_ERROR", { event: "pollTransactionInPool", }); return result; } exports.poll = poll; // Poll for `eth_getTransaction` until the transaction is found in the transaction pool. async function pollTransactionInPool(txhash, provider) { return poll(() => provider.getTransaction(txhash), (value) => typeof value?.hash === "string"); } exports.pollTransactionInPool = pollTransactionInPool;