Repository navigation
[sdk, simplex, docs]: retry bundler gas estimation on transient errors - #1406
Merged
Merged
Conversation
A single failed eth_estimateUserOperationGas made the Simplex filler skip an order, since it requires the bundler's own estimate. On 2026-10-15 a BSC->Base order was skipped and refunded because rundler's upstream RPC dropped one connection and rundler answered -32603 "internal error: rpc provider error". GasEstimator.estimateFillOrder and estimateBidPreVerificationGas now send the estimate through retryTransientBundlerErrors: at most 3 attempts, 250 ms then 750 ms apart. CryptoUtils.sendBundler throws a BundlerRpcError carrying the JSON-RPC code, HTTP status and a transport flag (message unchanged for JSON-RPC errors). Network failures, JSON-RPC -32603 and HTTP 429/5xx are retried; AA.. reverts, the ERC-4337 -325xx range and invalid params (-32602) fail on the first attempt as before. eth_sendUserOperation is not retried. Both params types take onBundlerRetry (default console.warn); Simplex's ContractInteractionService logs each retry as a warning with the order id, attempt and error.
seunlanlege
force-pushed
the
simplex/retry-bundler-gas-estimation
branch
from
October 15, 2026 15:29
a8b38ff to
3a4fe3e
Compare
… API Address review feedback on the transient bundler gas-estimate retry: - Bound each sendBundler request with AbortSignal.timeout(BUNDLER_REQUEST_TIMEOUT_MS) so a stalled connection cannot hold up a fill bid past the retries' budget. - Also retry HTTP 408 as transient. - Move onBundlerRetry out of BidPreVerificationGasParams into a second argument of estimateBidPreVerificationGas, keeping the bid-fields type pure. - Re-export BundlerRpcError, isTransientBundlerError, retryTransientBundlerErrors and the retry/timeout constants from @hyperbridge/sdk. - Document the sendBundler throw change (HTTP error with no JSON-RPC body now throws) and the timeout.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
On 2026-10-15 at 13:02:34 UTC the Simplex filler skipped a $298 BSC to Base order. One
eth_estimateUserOperationGascall to our rundler bundler failed because rundler's upstream RPC (dRPC) dropped a connection, and rundler answered JSON-RPC-32603 internal error: rpc provider error. The filler requires the bundler's own estimate (requireBundlerEstimate), so it loggedError estimating gas, not bidding on this orderand never asked again. Nobody filled the order and the user was refunded. A second request a moment later would almost certainly have succeeded.What changes
GasEstimator.estimateFillOrderandGasEstimator.estimateBidPreVerificationGas(@hyperbridge/sdk) now sendeth_estimateUserOperationGasthroughretryTransientBundlerErrors(newsdk/packages/sdk/src/protocols/intents/bundlerRetry.ts). It makes at most 3 attempts, waiting 250 ms and then 750 ms, so a failing estimate waits at most 1 s longer before the order is skipped as before.CryptoUtils.sendBundlernow throws aBundlerRpcErrorcarrying the JSON-RPCcode/data, the HTTP status and atransportflag. The message is stillBundler error: <message>for JSON-RPC errors. It also stops returningundefinedon an HTTP error with no JSON-RPC error body: that case now throwsBundler error: HTTP <status>.isTransientBundlerError): a request or body that failed in transit (fetch failed, connection reset/refused, timeouts), JSON-RPC-32603, and HTTP 429/5xx.AA..EntryPoint revert, the ERC-4337 range-32500..-32599(-32500,-32501,-32502,-32521, ...),-32700and-32600..-32602(invalid params, including rundler'sUnsupported entry point), whatever the HTTP status. Also not retried: any other JSON-RPC code on a non-429/5xx response, and any error that did not come fromsendBundler(e.g.No EntryPoint configured).eth_sendUserOperationgoes throughsendBundleronly inBid.execute, which sends once. Simplex'sUserOpSenderuses its own client, and its contract is that a rejected send was never submitted, so the caller may fall back to a native transaction. A send whose response was lost may already be in the mempool, so neither path treats a resend as idempotent, and this PR leaves both alone.EstimateFillOrderParamsandBidPreVerificationGasParamstake an optionalonBundlerRetry(event: BundlerRetryEvent), withattempt,maxAttempts,delayMsanderror. Without it the SDK writes aconsole.warn. Simplex'sContractInteractionServicepasses a hook that logsWARN contract-service Bundler gas estimate failed transiently, retrying { orderId, attempt, maxAttempts, delayMs, err }, so blips show up in the filler log and on the dashboard Logs page.sdk/packages/simplex/docs/ai/changelog/2026-10-15-retry-transient-bundler-gas-estimates.md, plus theonBundlerRetryrow and the retry behaviour underestimateFillOrderindocs/content/developers/sdk/api/intent-gateway.mdx. No version bumps: releases here are tag-driven. The bundledsimplexbinary and Docker image pick this up from the workspace SDK, and library embedders need the next@hyperbridge/sdkrelease.Validation
pnpm --filter @hyperbridge/sdk typecheck: passes.vitest --watch=false src/tests/bundlerRetry.test.ts src/tests/fillEstimateBundlerFailure.test.ts src/tests/bidPreVerificationGas.test.ts src/tests/fillEstimateGasPrice.test.ts: 34/34 pass. These include, over a stubbedfetchthrough the realsendBundler: a transient error then success succeeds; a transient error on every attempt fails after 3 attempts with the bundler's error; anAA23validation revert is not retried. The two retry cases fail when the retry is removed.test:concurrent: 336 passed, 6 skipped. One file fails,stateCalls.test.ts, which needs.env.localRPC URLs that this machine does not have. It is unrelated to this change.lintexits 0 (87 warnings, same asmain);test:unit261/261,test:data146/146,test:filler59 passed / 5 skipped,vitest src/tests/services189 passed / 3 skipped (including the new retry-logging test inbid-gas-estimate.test.ts);pnpm --filter @hyperbridge/simplex buildsucceeds (tsup + DTS + UI). The bundleddist/bin/simplex.jscontains the retry.biome lint .already fails onmainand is not part of CI.