fix(solana): fail fast when the payer has no USDC token account#23
Merged
Conversation
added 2 commits
July 15, 2026 11:32
Companion to blockrun-sol's invalidMessage classification. The gateway now returns the x402 facilitator's `invalidMessage` next to the coarse `invalidReason`; read it and stop retrying payments that can never pass. A wallet whose USDC token account was never created fails simulation with InvalidAccountData, which invalidReason collapses to transaction_simulation_failed. That pattern is deliberately absent from _UNRECOVERABLE_PAYMENT_PATTERNS because it usually IS recoverable under concurrent load — so the retry meant for load spikes fired on a wallet with no funds, burning all 5 attempts. Each of those cost the gateway its own 4 verify retries: one request became up to 20 facilitator calls, and one broke wallet looked like a ~260-attempt client-side storm. build_payment_rejected_error folds invalidMessage into the error message (the classifiers only ever see str(exc)) and keeps it on .response. Bounded to 256 chars, matching the existing `details` handling — same provenance, a facilitator error string rather than upstream text. Matching normalizes away non-alphanumerics so InvalidAccountData, "invalid account data" and invalid_account_data all hit one pattern; the facilitator's exact spelling is not yet confirmed. Blockhash messages stay OUT of the unrecoverable list on purpose. The gateway fails fast on them because retrying the SAME dead header is futile; here the opposite holds — re-signing with a fresh blockhash is what this retry does, and it fixes them. With both halves, verify calls per doomed request go 20 → 1.
VickyXAI
pushed a commit
that referenced
this pull request
Jul 15, 2026
VickyXAI
force-pushed
the
fix/fail-fast-on-invalid-account-data
branch
from
July 15, 2026 19:01
dfb1c89 to
ccd6fc0
Compare
VickyXAI
pushed a commit
that referenced
this pull request
Jul 15, 2026
VickyXAI
added a commit
to BlockRunAI/blockrun-litellm
that referenced
this pull request
Jul 15, 2026
) Picks up BlockRunAI/blockrun-llm#23, which stops the SDK retrying payments a wallet can never make. Below this floor an unfunded payer burns all 5 payment attempts, each costing the gateway 4 verify retries — 20 facilitator calls per doomed request, which is how one wallet with no USDC token account surfaced as a ~260-call storm. A floor rather than a preference: a pinned older SDK reintroduces the amplification in full. Base-chain users are unaffected, so the base blockrun-llm floor stays put. The fix also needs the gateway half (BlockRunAI/blockrun-sol#48, deployed) to return the invalidMessage the SDK classifies on; against an older gateway the SDK degrades to the previous retry behaviour rather than misbehaving. Unblocked: blockrun-llm 1.6.1 published to PyPI, so blockrun-llm[solana]>=1.6.1 now resolves (verified: blockrun-llm-1.6.1 with solana-0.39.0, x402-2.15.0).
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.
What
Client half of the x402 verify retry-storm fix. Gateway half: BlockRunAI/blockrun-sol#48 — that one must land and deploy first, since it's what starts returning the field this PR reads.
Why
A wallet whose USDC token account was never created fails simulation with
InvalidAccountData, which the gateway's coarseinvalidReasoncollapses totransaction_simulation_failed. That pattern is deliberately absent from_UNRECOVERABLE_PAYMENT_PATTERNSbecause it usually is recoverable under concurrent load — so the retry built for load spikes fired on a wallet that could never pay, burning all 5 attempts.Each of those cost the gateway its own 4 verify retries: one request became up to 20 facilitator calls, and one broke wallet looked like a ~260-attempt client-side storm to our facilitator partner. It was ~13 real requests.
How
build_payment_rejected_errorfoldsinvalidMessageinto the error message (the classifiers only ever seestr(exc)) and keeps it on.response. Bounded to 256 chars, matching the existingdetailshandling — same provenance: a facilitator error string, not upstream text._is_unrecoverable_payment_erroralso matches normalizedinvalidMessagepatterns, soInvalidAccountData,invalid account dataandinvalid_account_dataall hit one pattern.The asymmetry is load-bearing
Blockhash messages stay OUT of the unrecoverable list on purpose. The gateway fails fast on
BlockhashNotFoundbecause retrying the same dead header is futile. Here the opposite holds: re-signing with a fresh blockhash is exactly what this retry does, and it fixes it.The two repos classify blockhash errors oppositely and both are correct. Anyone later "harmonizing" the two lists will either reintroduce the storm or break recovery — hence the comment pointing at the other repo.
Not yet confirmed
The facilitator's exact spelling of
invalidMessageis unverified — normalization covers the plausible variants, and the gateway's retry log (#48) now printsmessage=even when unclassified so the real string surfaces in prod. Worth confirming from logs before trusting the patterns.Impact
With both halves, verify calls per doomed request: 20 → 1.
Test
tests/unit/test_invalid_message_fail_fast.py, written first — 3 failed for exactly the reasons being fixed; the 7 that passed pin the asymmetry and back-compat that already held.ruffclean.Follow-up
Releasing this needs a
blockrun-litellmdependency floor bump.