Solana → EVM transfer fails with 'Signature has expired: block height exceeded' + Solana balance deducted on failed retry #128
Labels
No labels
agent:implement
agent:ready
backend
bug
cannot-reproduce
confirmed
desktop
docs
documentation
duplicate
enhancement
feature
frontend
good first issue
help wanted
high-risk
in-review
invalid
mobile
needs-triage
P0-critical
P1-high
P2-medium
P3-low
qa
QA
question
ready
report
responsive
security
security-escalate
smart-contract
solana
tablet
test-pass
ux
wallet-issue
wallet:keplr
wallet:metamask
wallet:station
wallet:walletconnect
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-bridge-monorepo#128
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Solana → MegaETH transfers fail repeatedly with expired blockhash. Frontend appears to reuse stale blockhash on retry instead of refreshing. Worse: balance is deducted on the failed retry attempt — Solana TDEC went 0.99 → deducted post-fail with no destination receipt.
Repro 2026-05-01, wallet Backpack Cu6Q...aDBy, dest 0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650, dest chain MegaETH:
Solana RPC was responsive same session (balance reads worked, prior outbound rows from EVM → Solana also worked). Looks like a frontend retry path issue.
P1-high — silent loss on failed retry is more than a UX issue. cc @PlasticDigits
mentioned in commit
b6c5b5afdcGL-128 fix merged to main (commit
b6c5b5a)What changed
sendSolanaTransaction(packages/frontend/src/services/solana/transaction.ts) now copies the instruction list once, then each wallet path (signAndSendTransactionvssignTransaction→ simulate →sendRawTransaction) builds a new legacyTransactionand fetches a freshgetLatestBlockhashimmediately before that path runs. Previously the samerecentBlockhash/lastValidBlockHeightpair was applied once and reused when falling back between paths after wallet or RPC delay, which matches the repro (Signature has expired: block height exceeded on retries).looksLikeSolanaExpiredBlockhashError— shared classifier for logs/support.docs/FRONTEND_BRIDGE_INVARIANTS.md, cross-links indocs/SOLANA_BRIDGE_INVARIANTS.md,solanaRpcUrls.tsheader, newskills/agent-solana-tx-blockhash.md, link fromskills/agent-frontend-bridge-chains.md.transaction.test.tscases for the classifier.What this does not automatically solve
fetchDepositNonceagain and could create another deposit. Operators should confirm via explorer before retrying; the new skill doc calls this out.Verification checklist for @brouie
signAndSendand falls back (or vice versa), confirm the flow completes without block height exceeded where it previously failed on retry.confirmTransactionstill reaches confirmed on first success path.withdraw_submit/ Solana withdraw execute / faucet panel paths that callsendSolanaTransactionstill sign and confirm.VITE_SOLANA_PREFER_SIGN_AND_SEND_FIRST=true— exercise both orderings; each path should show a new blockhash (behavioral; no extra UI).Leaving this issue open until you sign off on mainnet QA.
mentioned in issue #123
Source-level pre-walkthrough on
b6c5b5a. Architecture is correct — stale-blockhash class of bug cannot recur on this code path.Implementation review
services/solana/transaction.ts:651—instructions = transaction.instructions.slice()copies the instruction list once at entry.prepareFreshTransactionclosure: each call rebuilds a newTransactionfrom the copied instructions and fetches a freshgetLatestBlockhashsnapshot. Returns{ tx, blockhash, lastValidBlockHeight }.signAndSendTransactionandsignTransaction+sendRawTransaction) callprepareFreshTransaction()independently, so each path gets its own blockhash. Reuse architecturally not possible.(GL-128)reference.VITE_SOLANA_PREFER_SIGN_AND_SEND_FIRSTenv flag (line 675) controls path ordering — exposes both orderings for testing item 5.Classifier review
looksLikeSolanaExpiredBlockhashError(line 309) covers 7 error-message variants:block height exceeded(matches my repro literally)blockheightexceeded(no-space variant)transactionexpiredsignature has expired(also in my repro)transaction expiredexpired blockhashblockhash not foundDefensive type handling:
Error/string/ arbitrary objects (lazyJSON.stringifyIIFE).Tests
npx vitest run solana/transaction.test: 8/8 PASS in 6ms (1 dedicated GL-128 classifier test at line 52).eaa3d0aandb6c5b5aboth ship in27a5e42HEAD).Re: the silent-loss caveat
Your comment notes: "If a deposit actually confirmed on-chain but the UI errored afterward, a second form submit still runs
fetchDepositNonceagain and could create another deposit. Operators should confirm via explorer before retrying." That maps exactly to my repro's tdec balance loss (0.99 → deducted with no destination receipt). Acknowledging the operator-process mitigation is acceptable for now via the newskills/agent-solana-tx-blockhash.md. Will follow your guidance and confirm via explorer before any retry on Solana → EVM going forward.cc @PlasticDigits
mentioned in issue #131
mentioned in issue #127
Production deploy gap — sign-off blocked
Verified prod bundle on bridge.cl8y.com today: footer reads
v0.1.345 · 27a5e42. RepomainHEAD is currently at95f8fd5, which is the merge commit containing all four fixes (#127eaa3d0a, #128b6c5b5a, #130 Terra||clause drop, #13167a48e4). Prod is therefore still on the pre-fix bundle and none of the four fixes are live yet.Confirming repro on prod (proves bundle is pre-fix):
ETHtext fallback while MetaMask is on MegaETH (chain 4326). This is the clean #131-D pre-fix behavior —getChainLogoPathhas no branch for 4326, falls through togetGasSymboldefaultETH.Cannot run mainnet sign-off on the verification checklist for #127 / #128 / #130 / #131 until prod cuts to current HEAD (or whichever release commit contains the four fixes).
Will re-run the combined live walk on bridge.cl8y.com once deploy is confirmed:
Plus the 4 #123 blocked rows pick-up that rides on #128 / #130 fixes.
cc @PlasticDigits
mentioned in issue #130
@Brouie bridge.cl8y.com frontend is now deployed at latest commit.
@PlasticDigits — ack on the prod deploy. queueing the bundled bridge live-walk for the next mainnet session (need real Backpack + MetaMask + small mainnet balances). will cover the full #127 / #128 / #130 / #131 verification matrix in one pass + pick up the #123 blocked rows that ride on the fixes. apologies for the delay — yieldomega QA + MR work has been priority this week.
reasonable target: by Monday 2026-05-11. flagging if anything blocks earlier.
mentioned in issue #118