Why Your Transaction Shows “Failed” But Your Money Is Already Gone
A deep dive into two silent bugs that combine to show a false failure while your transaction already landed on-chain.

The Scenario
You submit a transaction. Everything looks normal. You wait.
Then you see it — “Transaction Failed.”
But your wallet balance already dropped. The funds moved. Something happened on-chain. Yet the UI is telling you it failed.
You try again. Now you get a cryptic error you’ve never seen before:
InternalRpcError: An internal error was received
Details: validation reverted
Reason: AA25 invalid account nonce
This is not a one-off glitch. This is the result of two separate bugs hitting at exactly the same time — and neither one is obvious on its own.
What Actually Happened (Step by Step)
Here is the exact sequence of events at runtime:
Step 1 — Transaction submits successfully ✅ sendPreparedCalls executes. The UserOperation is sent to the bundler. It lands on-chain. Nonce 8 is consumed. Funds are moved. Everything at the blockchain level is complete.
Step 2 — On-chain state is updated ✅ The transaction is confirmed. From the blockchain’s perspective, everything worked perfectly.
Step 3 — Status check throws a CORS error ❌ getCallsStatus() is called directly from the browser to check the transaction status. But the deployment domain is not whitelisted in the Alchemy dashboard. The browser blocks the request entirely — CORS policy violation.
Step 4 — UI displays “Transaction Failed” ❌ The catch block re-throws the CORS error. The frontend has no way to distinguish a CORS failure from a transaction failure. It treats them the same. The user sees a false error message.
Step 5 — User retries → AA25 rejected ❌ prepareCalls is called again. It returns nonce 8 — the same stale nonce. The bundler rejects it immediately with AA25 invalid account nonce because that nonce is already used on-chain. Every retry fails. There is no recovery path.
End result: Transaction succeeded ✅ — Money moved ✅ — UI says Failed ❌ — Every retry also fails ❌
The Two Root Causes
Bug 01 — AA25 Invalid Account Nonce
In ERC-4337 Account Abstraction, every UserOperation must include a nonce that matches the account’s current on-chain nonce. When a transaction lands on-chain, that nonce is consumed and can never be reused.
The problem: there was no retry logic in the code. When sendPreparedCalls threw an error (even a CORS error unrelated to the transaction itself), the entire flow crashed. The next attempt called prepareCalls again — but Alchemy returned the same stale nonce 8, because the frontend had no mechanism to detect that this nonce was already used.
The bundler then correctly rejected the operation with AA25 invalid account nonce.
This can also happen due to:
- The same transaction function being called multiple times (duplicate API calls)
- A user double-clicking the submit button
- React useEffect or event listeners triggering multiple times
- Multiple browser tabs using the same wallet simultaneously
Bug 02 — CORS Misconfiguration
The Alchemy API was being called directly from the browser. But the deployment domain (your-app.amplifyapp.com) was not added to the Alchemy dashboard's Allowed Origins list.
When getCallsStatus() ran after a successful transaction, the browser blocked the request:
Access to fetch at 'https://api.g.alchemy.com/v2'
has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present
Notice the response code: net::ERR_FAILED 200 (OK) — Alchemy actually returned a successful response. But the browser blocked it before the JavaScript could read it. The UI had no way to know the transaction succeeded.
Why These Two Bugs Are Hard to Catch Separately
Individually, each bug gives you a clear signal:
- AA25 alone → you would immediately see a nonce error and investigate duplicate calls
- CORS alone → you would see the blocked request and whitelist your domain
But together, CORS masks the transaction success, which causes the user to retry, which triggers AA25. The AA25 error then dominates the logs, making developers chase the wrong root cause first.
The Fix
Fix 01 — Add Retry Logic for AA25 (Code Change)
Wrap the prepare → sign → send flow inside a retry helper function. When an AA25 error is detected, re-call prepareCalls to fetch a fresh nonce from Alchemy, then retry the full operation — up to 3 attempts.
async function prepareSignAndSend(buildCalls, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const preparedCalls = await smartAccountClient.prepareCalls(buildCalls());
const signedCalls = await signPreparedCalls(sessionKey, preparedCalls);
return await smartAccountClient.sendPreparedCalls(signedCalls);
} catch (error) {
const isNonceError = error?.message?.includes('AA25');
if (isNonceError && attempt < maxRetries - 1) {
await new Promise(r => setTimeout(r, 300));
continue; // retry with fresh nonce
}
throw error;
}
}
}This ensures a stale nonce never permanently kills a transaction flow.
Fix 02 — Whitelist Your Domain in Alchemy (Dashboard Config)
- Go to dashboard.alchemy.com
- Select your app / API key (the one used for your chain)
- Navigate to Security → Allowed Origins
- Add your deployment domain: https://your-app.amplifyapp.com
- Also add your production domain if different
Without this fix, getCallsStatus will keep failing with CORS after every successful transaction — meaning the UI will never correctly confirm a transaction from the browser.
Checklist Before You Ship
- [ ] Transaction submit function is called only once per user action
- [ ] Submit button is disabled immediately on first click
- [ ] Retry-on-AA25 logic added with fresh nonce re-fetch
- [ ] All deployment domains whitelisted in Alchemy Allowed Origins (staging + production)
- [ ] Status check failure is handled separately from transaction failure in catch block
- [ ] Nonce value is logged before every sendPreparedCalls call during debugging
Key Takeaway
These two bugs are easy to miss individually. Their real damage only becomes visible when they collide at runtime — one hides the success, the other punishes every retry.
If your users are reporting that their transaction failed but their balance dropped, check these two things first:
- Is your domain whitelisted in your RPC provider’s CORS settings?
- Does your transaction flow have nonce retry logic for AA25 errors?
Fix both. Neither one alone is enough.
Tags: #Web3 #Blockchain #Debugging #ERC4337 #AccountAbstraction #Alchemy #JavaScript #DeFi