As a disclaimer Rob and I work together so I have an unfair advantage here but thought it would be helpful to give a very detailed breakdown of what is going on...
TLDR;
Likely an unhandled error in metamask gas estimation (uses eth_estimateGas
) - you'll need to log metamask js console to see if this is true. See the following link about getting error messages from eth_estimateGas
.
Analysis
The GAS_LIMITS.BASE_TOKEN_ESTIMATE
is hardcoded at 100000
here:
This is then used by the send function and as per comment uses BASE_TOKEN_ESTIMATE
as a fallback
// Set a basic gasLimit in the event that other estimation fails
...
gasLimit =
draftTransaction.asset.type === AssetType.token ||
draftTransaction.asset.type === AssetType.NFT
? GAS_LIMITS.BASE_TOKEN_ESTIMATE
: GAS_LIMITS.SIMPLE;
// Run our estimateGasLimit logic to get a more accurate estimation of
// required gas. If this value isn't nullish, set it as the new gasLimit
const estimatedGasLimit = await estimateGasLimitForSend({
...
});
gasLimit = estimatedGasLimit || gasLimit;
}
This leads to the main culprit estimateGasLimitForSend
There are some potential places that can cause estimateGasLimitForSend
to return with BASE_TOKEN_ESTIMATE
Why I ruled out this function...
- ansatz - failed gas simulation causes this to return
null
or BASE_TOKEN_ESTIMATE
if (simulationFailed) {
const estimateWithBuffer = addGasBuffer(
paramsForGasEstimate?.gas ?? gasLimit,
blockGasLimit,
bufferMultiplier,
);
return addHexPrefix(estimateWithBuffer);
}
This function always returns a value so it must return BASE_TOKEN_ESTIMATE
if it is the source of error.
- We can't exit under the first:
paramsForGasEstimate.gas
> blockGasLimit
(Ruled Out) and paramsForGasEstimate.gas ?? gasLimit == BASE_TOKEN_ESTIMATE
as the input arg to estimateGasLimitForSend
sets blockGasLimit=metamask.currentBlockGasLimit
which is defined as getBlockByNumber(blockNumber, false).gasLimit
and was 30,000,000
when I checked block # 17891191
- We can't exit under the final return because we would get 30M as above
- The middle return seems impossible as
bufferMultiplier=1.5
for mainnet
so we would have to have exactly 66667
and it seems that for this case paramsForGasEstimate.gas = 30,000,000 * 0.95
anyway
Finally, I don't consider the simulation passing (the estimate is passed into addGasBuffer
before return) as otherwise we would not see a consistent behaviour - as per above it would return the estimatedGasLimit * 1.5
.
!to == true
This seems really bizarre and wasn't the first place I'd look as to=draftTransaction.recipient.address.toLowerCase()
in the input args to estimateGasLimitForSend
but by process of elimination it's another place that can cause a return value of BASE_TOKEN_ESTIMATE
since
if (sendToken) {
if (!to) {
// If no to address is provided, we cannot generate the token transfer
// hexData. hexData in a transaction largely dictates how much gas will
// be consumed by a transaction. We must use our best guess, which is
// represented in the gas shared constants.
return GAS_LIMITS.BASE_TOKEN_ESTIMATE;
}
and I'm assuming they put this in for a reason...
Unhandled Error
This seems the most likely area - I'm not expert in js
but I assume an error would return null
from estimateGasLimitForSend
and only some errors are handled
'Transaction execution error.'
'gas required exceeds allowance or always failing transaction'
} catch (error) {
const simulationFailed =
error.message.includes('Transaction execution error.') ||
error.message.includes(
'gas required exceeds allowance or always failing transaction',
) ||
(CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP[chainId] &&
error.message.includes('gas required exceeds allowance'));
if (simulationFailed) {
...
}
throw error;
}
note that last error isn't handled for mainnet
The error message received in many cases, when calling eth_estimateGas
is just "execution reverted"
- this is notably missing from metamask's error handling.
This would therefore trigger the fallback onto gasLimit
when estimateGasLimitForSend
returned null
gasLimit = estimatedGasLimit || gasLimit;
where gasLimit is set as BASE_TOKEN_ESTIMATE