0

I need to estimate the gas for an erc20 transfer for a wallet that currently has no eth in it. I am trying to pay the lowest fee possible, so I am calculating the maxPriorityFeePerGas and maxFeePerGas with a formula. The problem is the estimate_gas function will throw an error if I use the contract method with the max_fee and max_priority_fee specified:

maxPriorityFeePerGas, maxFeePerGas = eip1559gas.estimate_gas_fees(self.web3, speed='slow')
unicorns = self.web3.eth.contract(address=tx.contract_address, abi=EIP20_ABI)
unicorns.functions.transfer(to_address, qty).estimateGas(
                {'from': '0xB97Fbf5046688aD1A987B4F85A857ded99f82c71', 'maxPriorityFeePerGas': float_to_hex(web3.fromWei(max_fee_priority,'gwei')), 'maxFeePerGas': float_to_hex(web3.fromWei(max_fee, 'gwei'))})


ValueError: {'code': -32000, 'message': 'gas required exceeds allowance (0)'}

But not without those variables, as this works fine:

est = unicorns.functions.transfer(to_address, qty).estimateGas(
                {'from': from_address})

So I tried using the web3.eth method:

w3.eth.estimate_gas({'to': '0xAe44852e3948ebC351Fa7C31dD341F7BD7534c87', 'from': '0xB97Fbf5046688aD1A987B4F85A857ded99f82c71', "data": unicorns.encodeABI('transfer', args=('0xAe44852e3948ebC351Fa7C31dD341F7BD7534c87', qty))})

That gives an estimate that is too low every time, and then the transaction fails: 21644

So what I have been doing as a workaround is just using a static address that I know does have etherum in it, and that works:

est = unicorns.functions.transfer(to_address, sender_bal).estimate_gas(
                {'from': '0x7612E93FF157d1973D0f95Be9E4f0bdF93BAf0DF', 'maxPriorityFeePerGas': max_priority_fee_per_gas, 'maxFeePerGas': max_fee_per_gas}) # address that has eth in it

But then this is not the most accurate way to do it because the gas required could be different depending on the address (something to do with the nonce if I understand correctly). I would like to be able to calculate this dynamically, and accurately.

What is the correct way to do this? I'd like to have a function that i can plug in the address and value to like:

def estimate_gas_erc20(contract_address, from_address, to_address):
        maxPriorityFeePerGas, maxFeePerGas = eip1559gas.estimate_gas_fees(self.web3, speed='slow')
        unicorns = self.web3.eth.contract(address=contract_address, abi=EIP20_ABI)
        sender_bal = unicorns.functions.balanceOf(from_address).call()
        if sender_bal > 0:
            est = unicorns.functions.transfer(to_address, sender_bal).estimate_gas(
                {'from': from_address, 'maxPriorityFeePerGas': max_priority_fee_per_gas, 'maxFeePerGas': max_fee_per_gas})
            cp.normal(f'Est for {contract_address}: {est}')

1 Answer 1

0

One option to avoid the error when using the contract method with the max_fee and max_priority_fee specified is to first check if the from_address has a balance greater than 0 before estimating the gas. If the from_address does not have a balance, you can use the web3.eth method to estimate the gas without specifying the max_fee and max_priority_fee:

maxPriorityFeePerGas, maxFeePerGas = eip1559gas.estimate_gas_fees(self.web3, speed='slow')
unicorns = self.web3.eth.contract(address=tx.contract_address, abi=EIP20_ABI)
sender_bal = unicorns.functions.balanceOf(from_address).call()
if sender_bal > 0:
    est = unicorns.functions.transfer(to_address, 
sender_bal).estimateGas(
    {'from': from_address, 'maxPriorityFeePerGas': max_priority_fee_per_gas, 'maxFeePerGas': max_fee_per_gas})
else:
     est = w3.eth.estimate_gas({'to': to_address, 'from': from_address, "data": unicorns.encodeABI('transfer', args=(to_address, sender_bal))})
     cp.normal(f'Est for {contract_address}: {est}')

Alternatively, you could use the web3.eth.estimate_gas method and specify a default value for the max_fee and max_priority_fee, such as the average gas price. This way, the estimate_gas function will not throw an error and you can still get a gas estimate even if the from_address has no balance:

est = w3.eth.estimate_gas({'to': to_address, 'from': from_address, "data": unicorns.encodeABI('transfer', args=(to_address, sender_bal)), 'maxPriorityFeePerGas': average_gas_price, 'maxFeePerGas': average_gas_price})
cp.normal(f'Est for {contract_address}: {est}')

Note that using the average gas price for the max_fee and max_priority_fee may not give you the most accurate gas estimate, but it should be close enough for most cases. It is always best to double-check the actual gas used in a transaction to ensure it is within the acceptable range.

4
  • Nice ty! Is there any reason to use those parameters in the gas estimate? Because I realised after I posted this that what estimate gas is doing is telling me how many units it is and those parameters are specifying how much to pay for those units right? So I'm wondering if it doesn't make sense to specify these parameters now?
    – Chev_603
    Dec 5, 2022 at 19:03
  • To estimate gas, you need (to stay simple) the gas units * gas_price. Gas price parameters allow you to specify if you want your transaction to be processed fast, or slow, as the highest the price, the more likely it'll be picked up by a node.
    – Yakitori
    Dec 5, 2022 at 19:37
  • The formula post london is units * (base fee + tip) although i think legacy transactions are still supported. But I am wondering if it doesn't make a difference whether you specify those parameters when you just trying to an estimate of units.
    – Chev_603
    Dec 6, 2022 at 7:42
  • The issue I was having with this method is it was underestimating the gas units causing the tx to fail
    – Chev_603
    Dec 6, 2022 at 7:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.