@quantara/sdk
Version:
JavaScript/TypeScript SDK for interacting with Quantara Protocol on Neura Testnet
138 lines (137 loc) • 5.2 kB
JavaScript
import { encodeFunctionData, withRetry } from "viem";
import { GAS_PRICE_BUFFER_MAP, GAS_PRICE_PREMIUM_MAP, getViemChain, MAX_FEE_PER_GAS_MAP, MAX_PRIORITY_FEE_PER_GAS_MAP, } from "../configs/chains";
import { BASIS_POINTS_DIVISOR_BIGINT } from "../configs/factors";
import { bigMath } from "./bigmath";
export async function getGasPrice(client, chainId) {
let maxFeePerGas = MAX_FEE_PER_GAS_MAP[chainId];
const premium = GAS_PRICE_PREMIUM_MAP[chainId] || 0n;
const feeData = await withRetry(() => client.estimateFeesPerGas({
type: "legacy",
chain: getViemChain(chainId),
}), {
delay: 200,
retryCount: 2,
shouldRetry: ({ error }) => {
const isInvalidBlockError = error?.message?.includes("invalid value for value.hash");
return isInvalidBlockError;
},
});
const gasPrice = feeData.gasPrice;
if (maxFeePerGas) {
if (gasPrice !== undefined && gasPrice !== null) {
maxFeePerGas = bigMath.max(gasPrice, maxFeePerGas);
}
// Fetch the latest block to get baseFeePerGas for EIP-1559 fee data
const block = await client.getBlock({ blockTag: "pending" });
if (block.baseFeePerGas !== undefined && block.baseFeePerGas !== null) {
const baseFeePerGas = block.baseFeePerGas;
const maxPriorityFeePerGas = bigMath.max(MAX_PRIORITY_FEE_PER_GAS_MAP[chainId] ?? 0n, premium);
// Calculate maxFeePerGas
const calculatedMaxFeePerGas = baseFeePerGas + maxPriorityFeePerGas + premium;
return {
maxFeePerGas: bigMath.max(maxFeePerGas, calculatedMaxFeePerGas),
maxPriorityFeePerGas: maxPriorityFeePerGas + premium,
};
}
}
if (gasPrice === null || gasPrice === undefined) {
throw new Error("Can't fetch gas price");
}
const bufferBps = GAS_PRICE_BUFFER_MAP[chainId] || 0n;
const buffer = bigMath.mulDiv(gasPrice, bufferBps, BASIS_POINTS_DIVISOR_BIGINT);
return {
gasPrice: gasPrice + buffer + premium,
};
}
export async function getGasLimit(client, account, contractAddress, abi, method, params = [], value) {
const defaultValue = 0n;
if (value === undefined || value === null) {
value = defaultValue;
}
let gasLimit = 0n;
const data = encodeFunctionData({
abi,
functionName: method,
args: params,
});
try {
const estimateGasParams = {
to: contractAddress,
data,
value: BigInt(value),
account,
};
gasLimit = await client.estimateGas(estimateGasParams);
}
catch (error) {
// This call should throw another error instead of the `error`
const callParams = {
to: contractAddress,
data,
value: BigInt(value),
};
if (client.account) {
callParams.account = client.account;
}
await client.call(callParams);
// If not, we throw the original estimateGas error
throw error;
}
if (gasLimit < 22000n) {
gasLimit = 22000n;
}
// Add a 10% buffer to the gas limit
return (gasLimit * 11n) / 10n;
}
export async function callContract(sdk, contractAddress, abi, method, params, opts = {}) {
const txnOpts = {};
const chain = getViemChain(sdk.chainId);
if (opts.value) {
txnOpts.value = BigInt(opts.value);
}
const clients = [sdk.publicClient];
const data = encodeFunctionData({
abi,
functionName: method,
args: params,
});
const txnCalls = clients.map(async (client) => {
const txnInstance = { ...txnOpts };
async function retrieveGasLimit() {
return opts.gasLimit
? BigInt(opts.gasLimit)
: await getGasLimit(client, sdk.config.account, contractAddress, abi, method, params, opts.value !== undefined ? BigInt(opts.value) : undefined);
}
const gasLimitPromise = retrieveGasLimit().then((gasLimit) => {
txnInstance.gas = gasLimit;
});
const gasPriceDataPromise = getGasPrice(sdk.publicClient, sdk.chainId).then((gasPriceData) => {
if (gasPriceData.gasPrice !== undefined) {
txnInstance.gasPrice = gasPriceData.gasPrice;
}
else {
txnInstance.maxFeePerGas = gasPriceData.maxFeePerGas;
txnInstance.maxPriorityFeePerGas = gasPriceData.maxPriorityFeePerGas;
}
});
await Promise.all([gasLimitPromise, gasPriceDataPromise]);
return sdk.walletClient.sendTransaction({
to: contractAddress,
data,
chain,
...txnInstance,
});
});
const res = await Promise.any(txnCalls)
.catch((error) => {
if (error.errors && error.errors.length > 1) {
// eslint-disable-next-line no-console
console.error("All transactions failed", ...error.errors);
}
throw error.errors ? error.errors[0] : error;
})
.catch((error) => {
throw error;
});
return res;
}