ethers.js
ethers.js copied to clipboard
Polygon transactions are stuck/transaction underpriced error
Ethers Version
5.6.1
Search Terms
polygon maxPriorityFeePerGas maxFeePerGas
Describe the Problem
There is a harcoded (1.5 gwei) maxPriorityFeePerGas (and maxFeePerGas) in index.ts.
This value is used in populateTransaction.
In case of Polygon, this will either result in a transaction stuck in the mempool, or in case og e.g an Alchemy endpoint, "transaction underpriced" error.
Code Snippet
signer.populateTransaction(txParams)
signer.sendTransaction(txParams)
Where txParams don't contain maxPriorityFeePerGas/maxFeePerGas, and is a type 2 transaction.
(Legacy transactions pass as they use gasPrice only)
Contract ABI
No response
Errors
processing response error (body="{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"transaction underpriced\"},\"id\":64}", error={"code":-32000}, requestBody="{\"method\":\"eth_sendRawTransaction\",\"params\":[\"0x02f873818981c98459682f0084596898e882520894c54c244200d657650087455869f1ad168537d3b387038d7ea4c6800080c080a07cb6e05c60a2cb7ffc83349bc52ade79afaf9fdb911c64d57aed421caa1ecbcfa05f30023a4d21dd2eab6ea619c8dbb4820ce40c71841baacf8e82cbde7e87602a\"],\"id\":64,\"jsonrpc\":\"2.0\"}", requestMethod="POST",
Environment
No response
Environment (Other)
No response
I have the exact same issue for the exact same use.
However I am using "ethers": "^5.4.6",
. so the value for me is BigNumber.from("2500000000");
For reference: https://medium.com/stakingbits/polygon-minimum-gas-fee-is-now-30-gwei-to-curb-spam-8bd4313c83a2
My current workaround:
// get max fees from gas station
let maxFeePerGas = ethers.BigNumber.from(40000000000) // fallback to 40 gwei
let maxPriorityFeePerGas = ethers.BigNumber.from(40000000000) // fallback to 40 gwei
try {
const { data } = await axios({
method: 'get',
url: isProd
? 'https://gasstation-mainnet.matic.network/v2'
: 'https://gasstation-mumbai.matic.today/v2',
})
maxFeePerGas = ethers.utils.parseUnits(
Math.ceil(data.fast.maxFee) + '',
'gwei'
)
maxPriorityFeePerGas = ethers.utils.parseUnits(
Math.ceil(data.fast.maxPriorityFee) + '',
'gwei'
)
} catch {
// ignore
}
// send tx with custom gas
const tx = await contract.multicall(calldata, {
maxFeePerGas,
maxPriorityFeePerGas,
})
And my current workaround:
const feeData = await maticProvider.getFeeData()
// Start transaction: populate the transaction
let populatedTransaction
try {
populatedTransaction = await contract.populateTransaction.mint(wallet, arg, { gasPrice: feeData.gasPrice })
} catch (e) {
return
}
I removed gasLimit
from the options as it never worked;
I have the same problem with the same version (5.6.1)
@Benjythebee seems like you could then just call provider.getGasPrice()
directly
@robertu7 what is this Matic.network site? is that officially supported by Polygon / how reliable is it? thanks for the code!
I've also had this issue with tx getting stuck. Hoping this will fix it.
@jschiarizzi It's official: https://docs.polygon.technology/docs/develop/tools/polygon-gas-station
For your interest @robertu7 I pulled what you did into a new wallet subclass that one day may or may not get sent back into the main lib. It's tuned for our use cases with Polygon but I can't see why it can't be generalized with the help of some interfaces:
import { TransactionRequest } from "@ethersproject/abstract-provider"
import { Provider } from "@ethersproject/abstract-provider"
import { ExternallyOwnedAccount } from "@ethersproject/abstract-signer"
import { BytesLike } from "@ethersproject/bytes"
import { Deferrable } from "@ethersproject/properties"
import { SigningKey } from "@ethersproject/signing-key"
import fetch from "cross-fetch"
import { Wallet as EthersWallet, ethers } from "ethers"
type GasData = {
fast: {
maxPriorityFee: number
maxFee: number
}
}
const DEFAULT_GAS_DATA: GasData = {
fast: {
maxPriorityFee: 40,
maxFee: 40,
},
}
class AutomaticGasWallet extends EthersWallet {
gasStationUrl: string
constructor(
privateKey: BytesLike | ExternallyOwnedAccount | SigningKey,
provider: Provider,
gasStationUrl: string
) {
super(privateKey, provider)
this.gasStationUrl = gasStationUrl
}
async populateTransaction(
transaction: Deferrable<TransactionRequest>
): Promise<TransactionRequest> {
const tx = await super.populateTransaction(transaction)
const data = await this.getGasData()
const maxFee = ethers.utils.parseUnits(
Math.ceil(data.fast.maxFee).toString(),
"gwei"
)
const maxPriorityFee = ethers.utils.parseUnits(
Math.ceil(data.fast.maxPriorityFee).toString(),
"gwei"
)
tx.maxFeePerGas = maxFee
tx.maxPriorityFeePerGas = maxPriorityFee
return tx
}
async getGasData(): Promise<GasData> {
if (!this.gasStationUrl) {
return DEFAULT_GAS_DATA
}
try {
const response = await fetch(this.gasStationUrl)
const data = (await response.json()) as GasData
return data
} catch (e) {
logger.error(
`Could not fetch gas data from ${this.gasStationUrl}: ${e.toString()}`
)
return DEFAULT_GAS_DATA
}
}
}
export default AutomaticGasWallet
Currently, we should do like @robertu7 's way.
provider. getFeeData()
return only maxPriorityFeePerGas: 1.5gwei
https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/src.ts/index.ts#L250
@Benjythebee seems like you could then just call
provider.getGasPrice()
directly
My solution above https://github.com/ethers-io/ethers.js/issues/2828#issuecomment-1073458126 seem to be unreliable now. The transaction fails even before asking the user for a transaction most of the time. With error "err: max fee per gas less than block base fee..."
metamask 10.14.1 ethers ^5.4.6
I just stumbled across this after pulling my hair out this past week with transaction errors UNDERPRICED, REPLACEMENT_UNDERPRICED and "tx fee exceeds the configured cap" on maticvigil, polygon-rpc and alchemy. Various multiples of the getFeeData results were used, all with low success rates. I will try @robertu7's workaround later tonight to see if there any improvement - i sure hope so. A clear and concise example in the ethers docs would be TRULY helpful for all of us trying to use Polygon. Note that Mumbai gave no indication that this would be an issue in production.
same issue here trasactions are pending for ever on mumbai polygon
same issue here on polygon mainnet
Interesting. I never had any issues with Mumbai. It was only after going live that the majority of transactions were rejected. And those that went thru the 2nd week of May had wildly fluctuating prices such that same tx went from cents to up to $3. So currently, I am very jaded with Polygon. Anyways, didn't get to trying the work-around. Doing it as shortly, will advise on my experience...
Thanks for the post @clemsos. I'm curious - is the data from the polygon gas station significantly different from the results from the getFeedata call?
Here's my results trying @clemsos code: results from getFeeData: { maxFeePerGas: '1500000082' } { maxPriorityFeePerGas: '1500000000' } results from @clemsos: const resp = await fetch('https://gasstation-mainnet.matic.network/v2') const data = await resp.json()
maxFeePerGas = ethers.utils.parseUnits(
`${Math.ceil(data.fast.maxFee)}`,
'gwei'
)
maxPriorityFeePerGas = ethers.utils.parseUnits(
`${Math.ceil(data.fast.maxPriorityFee)}`,
'gwei'
)
gasstation { maxFeePerGas: '48000000000' } { maxPriorityFeePerGas: '48000000000' } NOTE: gasstation value is 32x greater than getFeedata value, yet when I was submitting 3x getFeeData.maxFeePerGas values last week, i hit over cap errors
**************** calling secureToken **************** batchMint failed secureData Error: replacement fee too low [ See: https://links.ethers.org/v5-errors-REPLACEMENT_UNDERPRICED ]
reviewing @impguard / @robertu7 next... And that is pretty much the same code... hmm my method call with the gas values: await contract.connect(signer).secureToken( bnTokenId, bnCollectionId, bnChain, meta.metaCid, meta.properties.docroot, '', meta.exchange, owner, { maxFeePerGas, maxPriorityFeePerGas } )
so still dead in water...
and with @Benjythebee 's use of feeData.gasPrice: gasPrice 37172324995 await contract.connect(signer).secureToken( bnTokenId, bnCollectionId, bnChain, meta.metaCid, meta.properties.docroot, '', meta.exchange, owner, { gasPrice: feeData.gasPrice })
**************** calling secureToken **************** batchMint failed secureData Error: replacement fee too low [ See: https://links.ethers.org/v5-errors-REPLACEMENT_UNDERPRICED ] (error={"reason":"processing response error","code":"SERVER_ERROR","body":"{"jsonrpc":"2.0","id":56,"error":{"code":-32000,"message":"replacement transaction underpriced"}}","error":{"code":-32000},"requestBody":"{"method":"eth_sendRawTransaction"
requestMethod: 'POST',
url: 'https://rpc-mainnet.maticvigil.com/v1/xxxxx'
}, method: 'sendTransaction', transaction: { type: 2, chainId: 137, nonce: 3017, maxPriorityFeePerGas: BigNumber { _hex: '0x08a7a4aa83', _isBigNumber: true }, maxFeePerGas: BigNumber { _hex: '0x08a7a4aa83', _isBigNumber: true }, gasPrice: null, gasLimit: BigNumber { _hex: '0x099b26', _isBigNumber: true }, to: '0xxxxxx', value: BigNumber { _hex: '0x00', _isBigNumber: true },
INTERESTING - gasPrice shown as NULL in the actual transaction despite being submitted in call...
Am I doing something wrong? I imagine that these workarounds are working for those who posted these solutions. Is it the rpc provider? What rpc provider is used for the gasstation solution versus the gasprice one? I should note: I am making these calls from the BACKEND. I guess I'll check in the DAPP if this helps on that front...
Dang it. Just realized its REPLACEMENT_UNDERPRICED, meaning a previous TX has stuck my new TX. But doesn't the NEW tx with the CORRECT fee override the stuck on and push it through? Trying to figure out how to cancel these pending now. Maybe the code above will work once that's done. I have to say - this has been an awful experience.
Update. SO the nonce was pending with a previous accepted tx. So, the maxFeePerGas value from the gasstation is getting accepted. However, if I wait for the transaction to confirm, it never returns. If i submit another TX, it gets a REPLACEMENT_UNDERPRICED error. I then have to go to metamask, and send a 0Matic transaction with the SAME NONCE at the fast price to clear the pending tx. GREAT. YAY.
So as a part of all this pricing business, I have TX with an accepted price and a hash, that doesnt confirm within any reasonable timeframe, hence still blocking all other TX. Looking up the TX in polygonscan - i get this: Status: Pending This txn hash was found in our secondary node and should be picked up by our indexer in a short while. for a tx made 5 minutes ago.
trying a different RPC to see if there's any improvement.
I'm updating this info so maybe it helps others? But I sure could use some feedback from the more experienced Polygoner's out there who have figured this out. If there is such a state...
Meanwhile, more 0 matic transfers to reset Tx.
Update: after maybe 15min? explorer shows this: Sorry, We are unable to locate this TxnHash Sooo... the accepted fee wasn't good enough???
Update #2: so it seems now we're cooking with gas :) So a number of issues:
- getFeeData is apparently WAY UNDERPRICED compared to the gasstation values - USE the gasstation values for maxFeePerGas and maxPriorityFeePerGas (the tip is the same as the limit??? - whatever)
- I changed my RPC provider from maticvigil to polygon-rpc. It apparently was the difference between the await for confirmation timing out and the tx should as pending on a secondary node TO ACTUALLY being confirmed within 30s.
- If you see a REPLACEMENT_UNDERPRICED - that means you ALREADY have a pending TX and should ensure this is cancelled or processed before you create a NEW ONE. Your current NODE# is stuck and you will need to unstick it. Doing a zero matic transfer on Metamask for that node # at Aggressive gas seems to take care of that within 30s. And what do you know - Metamask is using polygon-rpc.
So... my batcher is finally chugging await and logging successful tx now. YAY but what a week from HELL. I cannot say I would recommend Polygon, or the support that is available to help. Thanks to the community here for the gas pricing info
NEXT STOP: anyone have any experience setting default gas for the provider, versus per tx. when using an sdk - direct access to setting the gas for a tx is sometimes not available - ie Rarible API or Opensea perhaps. I'm told Web3 has Web3Ethereum that can be supplied with gas option - does this work for maxFeeForGas? Is there an ETHERS equivalent?
Just completed minting 1000 nfts yesterday on polygon RELIABLY - finally. Here's my code to get around this very frustrating gas pricing issue on Polygon/Mumbai:
const gasEstimated = await contract.estimateGas.yourMethod(...methodParams)
const gas = await calcGas('fast', chain) // this is @robertu7 's code - thanks!
let response = await (
await contract.connect(signer).yourMethod(...methodParams,
{ gasLimit: mulDiv(gasEstimated, 110, 100),
maxFeePerGas: gas.maxFeePerGas,
maxPriorityFeePerGas: gas.maxPriorityFeePerGas }
)
).wait()
it irks me that the maxPriorityFeePerGas is so high. I know I saw random transactions I had with default gas paying 1.5gwei but I havent been able to re-achieve this reliably.
Note that I had to include a gasLimit at a 10% premium for improved reliability. the ethers getFeeData did NOT work. it was always underpriced.
But the code is demonstrably reliable in sending the tx and getting it included in the block within a few seconds each time. I added a check to retry the tx later if the maxFeePerGas exceeds 50gwei in production.
Hope that helps someone.
Just completed minting 1000 nfts yesterday on polygon RELIABLY - finally. Here's my code to get around this very frustrating gas pricing issue on Polygon/Mumbai:
const gasEstimated = await contract.estimateGas.yourMethod(...methodParams) const gas = await calcGas('fast', chain) // this is @robertu7 's code - thanks! let response = await ( await contract.connect(signer).yourMethod(...methodParams, { gasLimit: mulDiv(gasEstimated, 110, 100), maxFeePerGas: gas.maxFeePerGas, maxPriorityFeePerGas: gas.maxPriorityFeePerGas } ) ).wait()
it irks me that the maxPriorityFeePerGas is so high. I know I saw random transactions I had with default gas paying 1.5gwei but I havent been able to re-achieve this reliably.
Note that I had to include a gasLimit at a 10% premium for improved reliability. the ethers getFeeData did NOT work. it was always underpriced.
But the code is demonstrably reliable in sending the tx and getting it included in the block within a few seconds each time. I added a check to retry the tx later if the maxFeePerGas exceeds 50gwei in production.
Hope that helps someone.
What's the mulDiv function do? Can you kindly put out the complete code? I am trying to make it work on polygon too. But the transaction is always stuck at pending.
adds a 10% premium to the estimated fee
On Wed, Jul 27, 2022 at 10:32 PM Yong He @.***> wrote:
Just completed minting 1000 nfts yesterday on polygon RELIABLY - finally. Here's my code to get around this very frustrating gas pricing issue on Polygon/Mumbai:
const gasEstimated = await contract.estimateGas.yourMethod(...methodParams)
const gas = await calcGas('fast', chain) // this is @robertu7 's code - thanks!
let response = await ( await contract.connect(signer).yourMethod(...methodParams, { gasLimit: mulDiv(gasEstimated, 110, 100), maxFeePerGas: gas.maxFeePerGas, maxPriorityFeePerGas: gas.maxPriorityFeePerGas } ) ).wait()
it irks me that the maxPriorityFeePerGas is so high. I know I saw random transactions I had with default gas paying 1.5gwei but I havent been able to re-achieve this reliably.
Note that I had to include a gasLimit at a 10% premium for improved reliability. the ethers getFeeData did NOT work. it was always underpriced.
But the code is demonstrably reliable in sending the tx and getting it included in the block within a few seconds each time. I added a check to retry the tx later if the maxFeePerGas exceeds 50gwei in production.
Hope that helps someone.
What's the mulDiv function do?
— Reply to this email directly, view it on GitHub https://github.com/ethers-io/ethers.js/issues/2828#issuecomment-1197682780, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQKOKAZ4PVRIJAFRDVVOSDTVWILNPANCNFSM5REEG7RQ . You are receiving this because you commented.Message ID: <ethers-io/ethers .@.***>
What's the mulDiv function do? Can you kindly put out the complete code? I am trying to make it work on polygon too. But the transaction is always stuck at pending.
Yeah I was reading this thread like "oh yay a solution" and then "wtf is mulDiv?"
After some significant googling, I found that it's an old timey method that just means (a * b) / c. He's using it to calculate 10% higher than the given number.
But after getting it to work, I found that I didn't need to go 10% higher. I included the 10% higher code in a comment, in case anyone needs it.
I adapted and cleaned up the code, and here is what I ended up with.
function parse(data) {
return ethers.utils.parseUnits(Math.ceil(data) + '', 'gwei');
}
async function calcGas(gasEstimated) {
let gas = {
gasLimit: gasEstimated, //.mul(110).div(100)
maxFeePerGas: ethers.BigNumber.from(40000000000),
maxPriorityFeePerGas: ethers.BigNumber.from(40000000000)
};
try {
const {data} = await axios({
method: 'get',
url: 'https://gasstation-mainnet.matic.network/v2'
});
gas.maxFeePerGas = parse(data.fast.maxFee);
gas.maxPriorityFeePerGas = parse(data.fast.maxPriorityFee);
} catch (error) {
}
return gas;
};
const gasEstimated = await contract.estimateGas.yourMethod();
const gas = await calcGas(gasEstimated);
const tx = await contract.yourMethod(gas);
await tx.wait();
Provided I didn't make any mistakes in converting to ES, this should be a full working snippet. If not, let me know.
Honestly I don't know how I would have ever solved this if not for stumbling upon this particular thread. So thanks for your efforts, and your help.
Here is another example that works. Not the best practice to up the gas price like this, but it definitely solves the problem.
let txHash: any
const _gasPrice = await web3.eth.getGasPrice()
poolContract.methods
.doSomething(parseUnits(amount.toString(), tokenObject.decimals))
.send({
from: props.accounts,
gasLimit: '20000000',
gasPrice: Number(_gasPrice * 1.5)
.toFixed()
.toString(),
gas: '20000000',
})
.once('transactionHash', (hash: any) => (txHash = hash))
.then(async (res: any) => {
console.log('success', res)
setLoading(false)
})
.catch((e: any) => {
const message = e?.message
console.log(message)
setLoading(false)
if (e.toString().includes('not mined within')) {
const handle = setInterval(() => {
web3.eth
.getTransactionReceipt(txHash)
.then((resp: { blockNumber: number } | null) => {
if (resp != null && resp.blockNumber > 0) {
clearInterval(handle)
alert('network timeout')
}
})
})
}
})
I've opened up a separate issue ticket around this - I think we've identified the cause.
Here's the explanation, assuming we're right: there's a minimum 30 gwei priority fee on Polygon, the hardcoded 1.5 gwei priority in Ethers is likely where estimations are underpricing. If this is correct, if you are implementing a workaround, all you should need to do is raise the priority fee to 30 gwei.
If you have additional details, feel free to add there (or here).
And my current workaround:
const feeData = await maticProvider.getFeeData() // Start transaction: populate the transaction let populatedTransaction try { populatedTransaction = await contract.populateTransaction.mint(wallet, arg, { gasPrice: feeData.gasPrice }) } catch (e) { return }
I removed
gasLimit
from the options as it never worked;
Yes Exactly this issue got resolved by this
@SethTurin
Thanks for that cleaned code.
I have been using it for the past week. But since 2 days ago, my transactions are not going through .
await tx.wait();
Never resolve anymore . Transaction is not processed either and it stays like that forever.
Anyone has an idea about what could be the problem ? thanks
I'm so sick of this constantly changing. What worked 5 months ago didn't work a month ago and what worked 5 days ago doesn't work today. What is changing?
Ok so as much as I can tell maxFeePerGas and maxPriorityFeePerGas are estimated completely wrong by the ethers on mainnet polygon. gasPrice gets closer, but is still dramatically underpriced. So now I'm just back to using gasPrice again.
Fully agree. What worked months ago just broke last weekend amidst another network congestion. Had to clear the wallet several times due to underpriced transactions blocking others. Looking now for an automated solution to clear stuck wallets :P
On Mon, Oct 17, 2022 at 9:07 PM Nicholas Juntilla @.***> wrote:
I'm so sick of this constantly changing. What worked 5 months ago didn't work a month ago and what worked 5 days ago doesn't work today. What is changing?
— Reply to this email directly, view it on GitHub https://github.com/ethers-io/ethers.js/issues/2828#issuecomment-1281787172, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQKOKA3ZIINK3LLLKNTHRE3WDYO6TANCNFSM5REEG7RQ . You are receiving this because you commented.Message ID: <ethers-io/ethers .@.***>
Fully agree. What worked months ago just broke last weekend amidst another network congestion. Had to clear the wallet several times due to underpriced transactions blocking others. Looking now for an automated solution to clear stuck wallets :P … On Mon, Oct 17, 2022 at 9:07 PM Nicholas Juntilla @.> wrote: I'm so sick of this constantly changing. What worked 5 months ago didn't work a month ago and what worked 5 days ago doesn't work today. What is changing? — Reply to this email directly, view it on GitHub <#2828 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQKOKA3ZIINK3LLLKNTHRE3WDYO6TANCNFSM5REEG7RQ . You are receiving this because you commented.Message ID: <ethers-io/ethers .@.>
That's what my company Ownerfy does is queue transactions, but we don't let people import their own wallets because of liability.