approve: async ({
from,
contractAddress,
privateKey,
stakingProviderName,
amountInWei,
nonce,
estimateGas,
maxPriorityFeePerGas,
gasFee,
feesType,
}) => {
try {
const {currentAllowance, firstTrx, secondTrx} = await retryFunc(
async evmProvider => {
const walletSigner = new ethers.Wallet(privateKey, evmProvider);
const tokenContract = new ethers.Contract(
contractAddress,
localErc20ABI,
walletSigner,
);
const {stakingProviderAddress} =
await EvmStakingProvider.getStakingAddress({
contractAddress,
stakingProviderName,
});
const allowance = await tokenContract.allowance(
from,
stakingProviderAddress,
);
console.log('Current allowance:', allowance.toString());
if (allowance >= amountInWei) {
console.log('Already approved');
return true;
}
let finalEstimateGas = BigInt(estimateGas);
if (typeof finalEstimateGas !== 'bigint') {
finalEstimateGas = await tokenContract[
'transfer(address,uint256)'
].estimateGas(stakingProviderAddress, amountInWei);
}
let finalGasPrice = gasFee;
let finalMaxPriorityFeePerGas = maxPriorityFeePerGas;
if (typeof finalGasPrice !== 'bigint') {
const gasFeeData = await getEtherGasPrice(feesType, evmProvider);
finalGasPrice = gasFeeData?.gasPrice;
finalMaxPriorityFeePerGas = gasFeeData?.maxPriorityFeePerGas;
}
let options = {
type: 2,
// gasLimit: 100000n, // NOTE: this needs to revert before commit (for pending transaction)
gasLimit: finalEstimateGas, // 100000
// maxFeePerGas: 1n, // NOTE: this needs to revert before commit
maxFeePerGas: finalGasPrice,
maxPriorityFeePerGas: validatePriorityFee(
finalMaxPriorityFeePerGas,
finalGasPrice,
),
nonce,
};
if (isEip1559NotSupported(chain_name)) {
delete options.type;
delete options.maxPriorityFeePerGas;
delete options.maxFeePerGas;
options.gasPrice = finalGasPrice;
}
const resetTrx = await tokenContract.approve.populateTransaction(
stakingProviderAddress,
0n,
options,
);
const approveTx = await tokenContract.approve.populateTransaction(
stakingProviderAddress,
amountInWei,
allowance > 0n
? {...options, nonce: options.nonce + 1} // reset will be sent → use N+1
: options,
);
return {
firstTrx: resetTrx,
secondTrx: approveTx,
currentAllowance: allowance,
};
},
null,
);
let trx1, trx2;
if (currentAllowance > 0n) {
console.log('Resetting allowance to 0...');
const approveHash = await createSendTransaction(
new ethers.Wallet(privateKey),
firstTrx,
);
const approveTxHash = approveHash.hash || approveHash;
console.log('Approve first submitted:', approveTxHash);
trx1 = await EVMChain(
chain_name,
undefined,
customRpcUrl,
).waitForConfirmation({
transaction: approveHash,
interval: 5000,
retries: 15,
});
console.log('Approved first confirmed', trx1);
}
const approveHash = await createSendTransaction(
new ethers.Wallet(privateKey),
secondTrx,
);
const approveTxHash = approveHash.hash || approveHash;
console.log('Approve second submitted:', approveTxHash);
trx2 = await EVMChain(
chain_name,
undefined,
customRpcUrl,
).waitForConfirmation({
transaction: approveHash,
interval: 5000,
retries: 15,
});
console.log('Approved second confirmed', trx2);
return {
confirmTransaction: trx2,
transaction1: trx1,
transaction2: trx2,
};
} catch (error) {
console.error('Error in approve transaction', error);
throw new Error(error);
}
},
getEstimateFeForAllowanceApprove: async ({
isFetchNonce,
existingNonce,
from,
contractAddress,
stakingProviderName,
amountInWei,
feesType,
nonce,
allowance,
}) =>
retryFunc(async evmProvider => {
const tokenContract = new ethers.Contract(
contractAddress,
erc20Abi,
evmProvider,
);
const {stakingProviderAddress} =
await EvmStakingProvider.getStakingAddress({
contractAddress,
stakingProviderName,
});
let estimateGas;
try {
estimateGas = await tokenContract.approve.estimateGas(
stakingProviderAddress,
amountInWei,
{from},
);
} catch (error) {
estimateGas = 60000n;
}
// When an existing non-zero allowance must be reset first, the second tx
// writes 0 → amountInWei (SSTORE ~20,000 gas) instead of the estimated
// non-zero → non-zero case (~5,000 gas), so add the difference.
try {
const currentAllowance = await tokenContract.allowance(
from,
stakingProviderAddress,
);
if (currentAllowance > 0n && currentAllowance < amountInWei) {
estimateGas += 20000n;
}
} catch (_) {}
const finalEstimateGas = (estimateGas * 110n) / 100n;
let currentNonce;
if (isFetchNonce || existingNonce == null) {
currentNonce = await EVMChain(chain_name, undefined).getNonce({
customRpcUrl,
address: from,
});
} else if (existingNonce) {
currentNonce = existingNonce;
}
const obj = await calculateTotalFees({
feesType,
evmProvider,
fromAddress: from,
toAddress: stakingProviderAddress,
estimateGas: finalEstimateGas,
isFetchNonce: nonce == null,
existingNonce: currentNonce,
});
return {
...obj,
allowance,
fee: allowance ? obj.fee * 2 : obj.fee,
};
}, null),
readAllowance: async ({
from,
contractAddress,
stakingProviderName,
amountInWei,
}) =>
retryFunc(async evmProvider => {
const tokenContract = new ethers.Contract(
contractAddress,
erc20Abi,
evmProvider,
);
const {stakingProviderAddress} =
await EvmStakingProvider.getStakingAddress({
contractAddress,
stakingProviderName,
});
const allowance = await tokenContract.allowance(
from,
stakingProviderAddress,
);
const isApproved = allowance >= amountInWei;
const needsReset = !isApproved && allowance > 0n;
const required = isApproved ? 0n : amountInWei - allowance;
return {
allowance,
isApproved,
needsReset,
required,
};
}, null),
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)