Skip to content

fix(bridge): estimate the Axelar fee locally when the Axelar API is unavailable - #2958

Merged
ermyas merged 2 commits into
mainfrom
fix/bridge-axelar-fee-fallback
Oct 6, 2026
Merged

ermyas merged 2 commits into
mainfrom
fix/bridge-axelar-fee-fallback

Conversation

@ermyas

@ermyas ermyas commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Mainnet requests to api.gmp.axelarscan.io intermittently get a Cloudflare challenge (403) or rate limit (429). In browsers the CORS preflight fails, getAxelarFee dereferenced an undefined error.response, and the bridge widget stalled on the fee skeleton. Testnet is unaffected.

getAxelarFee now tries the Axelar API on every call (5s timeout). When the API is unavailable (no response incl. CORS rejections, timeout, 403, 429, 5xx) it estimates the fee locally; Axelar error replies and other 4xx still throw AXELAR_GAS_ESTIMATE_FAILED.

(gas limit + 250k base gas) × max(destination gas price, 1 gwei) × max(gasMultiplier, 2)  →  converted at checkout-api USD prices  +  5¢
  • Base gas covers Axelar's base fee, which tracks destination gas (~185k gas for Ethereum, two mainnet samples on 2026-10-07).
  • Axelar refunds unused gas to msg.sender (RootERC20Bridge.sol:411, ChildERC20Bridge.sol:328), so the estimate errs high.
  • Returns to the Axelar quote on the next call once the API responds; no state is kept.
  • Each fallback logs [imtbl-bridge] Axelar fee API unavailable, using fallback estimate {reason, …}; getFee and getUnsignedBridgeBundledTx fee data carry bridgeFeeSource: 'axelar' | 'fallback'.
  • If the fallback also fails, throws AXELAR_GAS_ESTIMATE_FAILED.
  • The GMP status GET no longer sends Content-Type, so browsers skip the preflight.

Fallback vs Axelar

Mainnet Testnet
Deposit (ETH) 3.6× 3.7×
Withdraw (IMX) 2.3× ~50×

Testnet withdrawals overshoot because Sepolia gas sits below the 1 gwei floor and Axelar's testnet quotes use synthetic token prices. A fallback withdrawal on testnet needs ~15 test IMX for the fee.

Testing

  • Unit tests for each fallback trigger (network/CORS, timeout, 403, 429, 5xx), the cases that still throw (Axelar error body, other 4xx, unparseable body), the multiplier, recovery on the next call, double failure, and a 1.5×–4× bound against mainnet quotes.
  • Local sample app against mainnet: real Cloudflare 403s in the browser fall back with fees shown; Axelar quotes resume when the API responds; bundled tx value = amount + fallback fee.
  • Node against mainnet: real HTTP 429 falls back.
  • Pending: a testnet deposit on the fallback to confirm execution and gas refund on Axelarscan.

Follow-ups

  • BridgeReviewSummary never clears loading if the fee fetch throws (BridgeReviewSummary.tsx:287-296), so a double failure still shows a stuck skeleton.
  • Toolkit (checkout-playground) and partners need an SDK bump to pick this up.

🤖 Generated with Claude Code

…navailable

Mainnet requests to api.gmp.axelarscan.io intermittently receive a
Cloudflare challenge (403) or a rate limit (429). In browsers the CORS
preflight fails, getAxelarFee read `error.response.data` on an undefined
response, and the bridge widget stalled on the fee skeleton.

getAxelarFee now tries the Axelar API on every call with a 5s timeout and,
on any failure, estimates the fee from the destination gas price and USD
prices from the checkout API:

  (gas limit + 250k base gas) × max(gas price, 1 gwei) × 2, plus 5 cents

The 250k base gas covers Axelar's base fee, which tracks destination gas
(~185k gas observed for Ethereum on 2026-10-07). Axelar refunds unused gas
to the sender, so the estimate errs high. Against mainnet quotes on
2026-10-07 the fallback is ~2.3x for withdrawals and ~3.6x for deposits.
On testnet, withdrawals estimate ~50x Axelar's quote because Sepolia gas
sits below the 1 gwei floor and Axelar's testnet quotes use synthetic
token prices.

Each fallback logs a console warning prefixed `[imtbl-bridge]` with the
reason, and fee responses carry `bridgeFeeSource: 'axelar' | 'fallback'`.
If the fallback also fails, getAxelarFee throws AXELAR_GAS_ESTIMATE_FAILED.

Also drop the Content-Type header from the GMP status GET so browsers send
it without a preflight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ermyas
ermyas requested a review from a team as a code owner October 6, 2026 23:04
@nx-cloud

nx-cloud Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit eadb9ca

Command Status Duration Result
nx affected -t build,test ✅ Succeeded 1m 3s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-10-06 23:35:31 UTC

@ermyas
ermyas enabled auto-merge October 6, 2026 23:18
Axelar error replies ({error: true, message}), 4xx responses other than
403/429, and unparseable 2xx bodies now throw AXELAR_GAS_ESTIMATE_FAILED,
as before the fallback was added. The fallback estimate runs only for no
response (including CORS rejections), timeouts, 403 (Cloudflare
challenge), 429 and 5xx.

The fallback multiplier is now max(gasMultiplier, 2) for a numeric
gasMultiplier, and 2 for 'auto', so a caller asking for a larger buffer
keeps it during an outage. The gasMultiplier docs on the fee request
types describe this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@lfportal lfportal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both review comments are fixed in 230ad20. LGTM.

@ermyas
ermyas added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit 4ade71c Oct 6, 2026
8 checks passed
@ermyas
ermyas deleted the fix/bridge-axelar-fee-fallback branch October 6, 2026 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants