@kaiachain/ethers-ext
Version:
ethers.js extension for kaia blockchain
171 lines (170 loc) • 7.67 kB
JavaScript
;
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;