fix(app): resubmit signed tx until confirmed — closes #291#296
Merged
Conversation
Public Solana RPCs (api.devnet.solana.com, api.mainnet-beta.solana.com)
drop transactions silently under load. The current broadcast →
confirmTransaction flow doesn't resubmit if the first send was lost,
so we get spurious "block height exceeded" failures even when the user
signed promptly.
This adds an aggressive resubmit loop that re-broadcasts the signed
bytes every 2s (idempotent on Solana RPCs — duplicate sends return the
same signature) while confirmation polls in parallel. First confirmation
wins; the loop stops in a finally{}.
- new app/src/lib/sendWithRetry.ts (helper + 5 unit tests)
- app/src/hooks/useTransactionSigner.ts wired to use it
- existing SignTxCard tests still green (hook external API unchanged)
- app test suite: 572 → 577
- full workspace typecheck clean
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced May 23, 2026
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.
Summary
Closes #291.
Public Solana RPCs (
api.devnet.solana.com,api.mainnet-beta.solana.com) drop transactions silently under load. The current broadcast →confirmTransactionflow doesn't resubmit if the first send was lost, so we get spurious "block height exceeded" failures even when the user signed promptly.Fix
Adds an aggressive resubmit loop that re-broadcasts the signed bytes every 2s (idempotent on Solana RPCs — duplicate sends return the same signature) while confirmation polls in parallel. First confirmation wins; the loop stops in a
finally{}.Pattern is standard in Solana DeFi (Jupiter, Phantom, Drift all do variants of this).
Why this fixes the failure mode
#291traced the race in session frontier_sip_17 to the FE's broadcast path. The FE does refresh the blockhash before signing (useTransactionSigner.ts:46-54), so the blockhash itself is fresh. The failure mode is that the broadcast itself doesn't always land:sendRawTransaction(signed)— but public devnet RPC silently drops it (rate-limit / congestion)confirmTransactionpolls for 60-90s, never sees the tx, returns "block height exceeded"getTransactionfor the signature:null(tx never landed)Background resubmit defends against the silent-drop case. As long as one resubmit lands before
lastValidBlockHeight, the tx confirms.Files changed
app/src/lib/sendWithRetry.ts— helper + JSDoc citing fix(send): chat send broken on devnet — blockhash expires before broadcast #291app/src/lib/__tests__/sendWithRetry.test.ts— 5 unit tests (happy path, resubmit-while-pending, swallow-rate-limit errors, first-send error propagates, block-height-exceeded propagates + stops resubmits)app/src/hooks/useTransactionSigner.ts— replace inlinesendRawTransaction+confirmTransactionwithsendAndConfirmWithRetrycallTest plan
pnpm exec vitest run src/lib/__tests__/sendWithRetry.test.ts— 5/5 passpnpm exec vitest run src/components/__tests__/SignTxCard.test.tsx— 15/15 pass (hook external API unchanged)pnpm --filter @sipher/app test -- --run— 577/577 pass (was 572 baseline, +5 new)pnpm typecheck(full workspace: root + sdk + app + agent) — cleanVerification post-deploy
Re-run the frontier_sip_17 chat send → scan → claim cycle (sipher#288's prod verification). Sign card → Phantom sign → cluster confirmation should now land within the blockhash window even when the first broadcast is rate-limited.
Scope notes / non-goals
/api/chat/stream) — separate transport-layer issuedeps.resubmitIntervalMs(tests use 5ms)