Repository navigation
fix(solana): send with preflight and rebroadcast so QuickNode sends land - #733
Closed
fengtality wants to merge 4 commits into
Closed
fengtality wants to merge 4 commits into
fengtality wants to merge 4 commits into
Conversation
Through the QuickNode Solana RPC, a transaction sent with skipPreflight is accepted (the RPC answers its signature) but never reaches a leader, so every send timed out at blockhash expiry. Rebroadcasting the same bytes every 2 s (15 resends) did not help; the same send with preflight on landed in about a second, and the same code through the public RPC lands either way. Every broadcast now uses one SEND_OPTIONS with preflight on at 'confirmed'. A transaction that preflight refuses was not broadcast, so the send now throws its parsed program error (slippage, balance, or simulation failed) instead of reporting an unconfirmed send with no signature.
Gateway sends with maxRetries 0, so the RPC hands the transaction to the leader once and a dropped send is never retried. While the send waits to confirm (WebSocket or polling), the same signed bytes are now sent again every REBROADCAST_INTERVAL_MS (2 s), with the same send options. The loop stops when the confirmation ends, when the block height passes the lastValidBlockHeight the confirmation uses, or when the RPC says the transaction was already processed. Nothing is re-signed and the blockhash is not changed, so the transaction can land only once. POST /chains/solana/submit (#721) sends through the same _sendAndConfirmRawTransaction, so it gets the rebroadcast too.
[Medium impact] The PR appears safe to merge; no new actionable issues were found. SummaryThe PR enables preflight checks for Solana sends and rebroadcasts the same signed bytes while awaiting confirmation.
Reviews (3) · Last reviewed commit: "fix(solana): check the stop flag before ..." · Reviewed by Greptile |
…ight Stopping the rebroadcast awaited the loop, so a slow getBlockHeight or resend held back a send that had already confirmed. Stopping now only sets the flag and wakes the timer; the loop catches its own errors and schedules nothing after it is stopped.
A resend that returns after the stop scheduled one more timer before the loop saw the flag. The loop condition now checks it first.
Contributor
Author
|
Bundled into #735 with the other fix PRs. |
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.
Problem
Solana sends through a QuickNode RPC time out and do not land. Gateway sent with
skipPreflight: trueandmaxRetries: 0. QuickNode accepts such a transaction (it answers the signature) but does not forward it to the leader. The same swap through the public RPC lands.Cause, measured on mainnet
A 0.5 USDC → SOL Jupiter swap through a Gateway on a spare port, using the QuickNode RPC:
skipPreflight: true,maxRetries: 0(currentdevelopment)5ipnwb…,2YgyaV…)skipPreflight: trueskipPreflight: falseskipPreflight: true, public RPCSo the cause is
skipPreflight, not the missing rebroadcast.Changes
fix(solana): send with preflight so QuickNode forwards the transaction: every broadcast now uses oneSEND_OPTIONS(skipPreflight: false,preflightCommitment: 'confirmed',maxRetries: 0). Preflight refusal means the transaction was not broadcast. The send now throws the parsed program error (slippage, balance, or simulation failed) and no longer returns an unconfirmed result with an empty signature.fix(solana): rebroadcast a sent transaction until it confirms or expires: while the send waits to confirm (WebSocket or polling), Gateway sends the same signed bytes again everyREBROADCAST_INTERVAL_MS(2 s). It stops when the confirmation ends, when the block height passeslastValidBlockHeight, or when the RPC reports the transaction as already processed. Gateway does not re-sign the transaction or change its blockhash. WithmaxRetries: 0, a dropped send otherwise gets no retry.Expiry uses the same
lastValidBlockHeightthat polling already uses: the latest blockhash at send time. Connector-built versioned transactions (Jupiter) keep their own, older blockhash, so the real expiry can be earlier. The only cost is a few resends that cannot land.POST /chains/solana/submit(#721) sends through the same_sendAndConfirmRawTransaction, so it gets both fixes when it rebases.Verification
rtQL3f2iaYMKxy23x3bFYJbsmwuEeD1en6ysQCUZ1boSMFJMpxNfyTUkix8F8TRzeVtRudGPQDcSXP42o8bAt5C(PR tip) and5FVTZRKckqXQs3xxPK4pjK3nLw84Cz1DCExZhYmgWv4JRj6CMz1AQkgJJgFZVnDqgEStSGYMdbkvzev2hdR7z2MU. Both are confirmed on the public RPC.send-rebroadcast.test.tsshows identical bytes resent until delivery, a single blockhash fetch, a stop at blockhash expiry with no sends after it, and a confirmation that does not wait for a resend still in flight.send-preflight.test.tsshows preflight is requested and a preflight refusal surfaces asSLIPPAGE_EXCEEDED.pnpm build,pnpm lint(0 errors),pnpm test -w 3: 171 suites, 1511 tests passed.🤖 Generated with Claude Code