feat: add undici dependency + dynamic gas multiplier for tx retry

- Install undici package for fetch proxy support
- Add gas multiplier on retry to handle "replacement transaction underpriced"
  - Attempt 1: 1x gas
  - Attempt 2: 2x gas
  - Attempt 3: 4x gas

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
direkturcrypto
2026-03-01 21:23:37 +07:00
co-authored by Claude Opus 4.6
parent 56f59eabdf
commit d24c5f2e81
3 changed files with 31 additions and 4 deletions
+10
View File
@@ -14,6 +14,7 @@
"dotenv": "^16.4.7",
"ethers": "^5.8.0",
"https-proxy-agent": "^7.0.6",
"undici": "^7.22.0",
"ws": "^8.19.0"
},
"devDependencies": {
@@ -1828,6 +1829,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/undici": {
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
"integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+1
View File
@@ -36,6 +36,7 @@
"dotenv": "^16.4.7",
"ethers": "^5.8.0",
"https-proxy-agent": "^7.0.6",
"undici": "^7.22.0",
"ws": "^8.19.0"
},
"devDependencies": {
+20 -4
View File
@@ -118,6 +118,9 @@ async function _doExecSafeCall(to, data, description = '') {
if (description) logger.info(`MM: exec safe tx — ${description}`);
let lastErr;
// Track gas price multiplier for replacement transactions
let gasMultiplier = 1;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const provider = await getPolygonProvider();
@@ -149,9 +152,15 @@ async function _doExecSafeCall(to, data, description = '') {
// Polygon requires maxPriorityFeePerGas ≥ 25 Gwei.
// Some RPC nodes (e.g. lava.build) return a stale low estimate, so we enforce a floor.
const feeData = await provider.getFeeData();
const MIN_TIP = ethers.utils.parseUnits('30', 'gwei');
const gasTip = feeData.maxPriorityFeePerGas?.gt(MIN_TIP) ? feeData.maxPriorityFeePerGas : MIN_TIP;
const gasFeeCap = feeData.maxFeePerGas ?? ethers.utils.parseUnits('500', 'gwei');
// Increase gas price on retry to replace pending transaction
// Multiplier: 1x → 2x → 4x on subsequent attempts
const MIN_TIP = ethers.utils.parseUnits('30', 'gwei').mul(Math.ceil(gasMultiplier));
const gasTip = feeData.maxPriorityFeePerGas?.mul(Math.ceil(gasMultiplier)).gt(MIN_TIP)
? feeData.maxPriorityFeePerGas.mul(Math.ceil(gasMultiplier))
: MIN_TIP;
const gasFeeCap = (feeData.maxFeePerGas ?? ethers.utils.parseUnits('500', 'gwei'))
.mul(Math.ceil(gasMultiplier * 1.5));
const tx = await safe.execTransaction(
to, 0, data, 0, 0, 0, 0,
@@ -169,7 +178,14 @@ async function _doExecSafeCall(to, data, description = '') {
const friendly = parseOnchainError(err);
if (attempt < MAX_RETRIES) {
logger.warn(`MM: transaction failed (attempt ${attempt}/${MAX_RETRIES}): ${friendly} — retrying in ${RETRY_DELAY / 1000}s...`);
// Increase gas multiplier for replacement transaction
if (err?.message?.includes('replacement transaction underpriced') ||
err?.message?.includes('Gas price too low to replace')) {
gasMultiplier *= 2;
logger.warn(`MM: transaction failed (attempt ${attempt}/${MAX_RETRIES}): ${friendly} — increasing gas ${gasMultiplier}x and retrying...`);
} else {
logger.warn(`MM: transaction failed (attempt ${attempt}/${MAX_RETRIES}): ${friendly} — retrying in ${RETRY_DELAY / 1000}s...`);
}
await sleep(RETRY_DELAY);
}
}