UNPKG

@kaiachain/ethers-ext

Version:
150 lines (149 loc) 6.55 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.populateFeePayerAndSignatures = exports.populateChainId = exports.eip155sign = exports.populateGasPrice = exports.populateGasLimit = exports.populateNonce = exports.populateTo = exports.populateFrom = exports.populateFromSync = exports.getTransactionRequest = void 0; const constants_1 = require("@ethersproject/constants"); const logger_1 = require("@ethersproject/logger"); const properties_1 = require("@ethersproject/properties"); const web_1 = require("@ethersproject/web"); const lodash_1 = __importDefault(require("lodash")); const js_ext_core_1 = require("@kaiachain/js-ext-core"); const logger = new logger_1.Logger("@kaiachain/ethers-ext"); // 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 { return (0, properties_1.resolveProperties)(transactionOrRLP); } } exports.getTransactionRequest = getTransactionRequest; // Below populateX() methods are partial replacements to: // - ethers.Signer.checkTransaction() // - ethers.Signer.populateTransaction() // - ethers.JsonRpcSigner.sendUncheckedTransaction() // populateFromSync is a synchronous method so it can be used in checkTransaction(). function populateFromSync(tx, expectedFrom) { // See @ethersproject/abstract-signer/src/index.ts:Signer.checkTransaction() if (!tx.from || tx.from == "0x") { tx.from = expectedFrom; } else { tx.from = Promise.all([ Promise.resolve(tx.from), expectedFrom, ]).then(([from, expectedFrom]) => { if (from?.toLowerCase() != expectedFrom?.toLowerCase()) { logger.throwArgumentError(`from address mismatch (wallet address=${expectedFrom}) (tx.from=${from})`, "transaction", tx); } return from; }); } } exports.populateFromSync = populateFromSync; async function populateFrom(tx, expectedFrom) { populateFromSync(tx, expectedFrom); tx.from = await tx.from; } exports.populateFrom = populateFrom; async function populateTo(tx, provider) { if (!tx.to || tx.to == "0x") { tx.to = constants_1.AddressZero; } else { const address = await provider.resolveName(tx.to); if (address == null) { logger.throwArgumentError("provided ENS name resolves to null", "tx.to", tx.to); } } } 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) { 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(gasLimit.toNumber() * bufferMultiplier); } catch (error) { logger.throwError("cannot estimate gas; transaction may fail or may require manual gas limit", logger_1.Logger.errors.UNPREDICTABLE_GAS_LIMIT, { error: error, tx: tx }); } } } exports.populateGasLimit = populateGasLimit; async function populateGasPrice(tx, provider) { if (!tx.gasPrice) { tx.gasPrice = await provider.getGasPrice(); } } exports.populateGasPrice = populateGasPrice; function eip155sign(key, digest, chainId) { const sig = key.signDigest(digest); sig.v = sig.recoveryParam + chainId * 2 + 35; return sig; } 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); } } 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 == constants_1.AddressZero) { tx.feePayer = expectedFeePayer; } else { if (tx.feePayer.toLowerCase() != expectedFeePayer.toLowerCase()) { logger.throwArgumentError("feePayer address mismatch", "transaction", tx); } } // 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; // Poll for `eth_getTransaction` until the transaction is found in the transaction pool. async function pollTransactionInPool(txhash, provider) { // Retry until the transaction shows up in the txpool // Using poll() like in the ethers.JsonRpcSigner.sendTransaction // https://github.com/ethers-io/ethers.js/blob/v5.7/packages/providers/src.ts/json-rpc-provider.ts#L283 const pollFunc = async () => { const tx = await provider.getTransaction(txhash); if (tx == null) { return undefined; // retry } else { return tx; // success } }; return (0, web_1.poll)(pollFunc); } exports.pollTransactionInPool = pollTransactionInPool;