Web3 backends bridge two execution models: one atomic, one async—and that gap is where vulnerabilities hide.
Smart contracts execute transactions atomically on-chain, even when they invoke external calls.
Meanwhile, off-chain code (Node.js APIs, tests, indexers) runs asynchronously. On-chain logic doesn’t.
That mismatch is where bugs hide.
These two models don't naturally align, and the mismatch creates security blind spots that most developers don't see coming.
Your backend shows a transaction as complete. The user's balance is updated. Then you check the blockchain explorer, it never landed. Or it landed in a different block with a different hash.
That's not a network issue. That's async assumptions from Web2 colliding with blockchain finality. When the blockchain is your source of truth, "complete" doesn't mean what you think it means.
This article breaks down how async functions, race conditions, re-entrancy, Node.js backends, development tools, and how message ordering create vulnerabilities across EVM and non EVM chains, and also what you can do to close those gaps.
1⃣ Re-Entrancy and Race Conditions in Ethereum Smart Contracts
Re-entrancy and race conditions are still some of the easiest ways Ethereum contracts get drained. From my experience, it's usually simple logic mistakes: contracts make external calls before updating state, and that small ordering issue gives attackers room to re-enter and repeat actions like withdrawals.
I've seen this pattern show up again and again in DeFi losses.
➡️ Example: The Omni NFT protocol lost $1.4M to re-entrancy because safeTransferFrom invoked onERC721Received before updating state. The attacker re-entered mid-execution and drained funds before the contract knew what happened.
Re-entrancy is by far the most notorious async-related flaw in Ethereum contracts.
The fix is conceptually simpleupdate state before making external calls but under pressure or in complex logic, developers still get it wrong.
2⃣ Asynchronous Patterns in Non-EVM Programs
Non-EVM programs avoid classic Ethereum-style re-entrancy, but that doesn’t mean they’re “safe by default.”
Programs are stateless, accounts are passed in explicitly, and CPIs (Cross-Program Invocations) in solana chain can’t secretly call back to you. That design removes a whole class of attacks. But logic mistakes still happen. If you make a CPI before updating account state, you’re opening yourself up to problems.
I’ve also seen teams get burned by account re-initialization, especially when the base unit of a native coin hit zero or ownership changes unexpectedly.
Non-EVM security is less about re-entrancy and more about account validation, permissions, and state ordering. If you get those wrong, an exploit would look simple in hindsight.
3⃣ Asynchronous Operations in Node.js Backends
Most off-chain bugs don't come from hackers being clever, they come from async code being careless.
You're usually running Node.js, which means everything is async and shared inside one process. If you use global state, two requests can step on each other and leak data.
I've seen user sessions overwrite each other exactly like this. Same thing with event listeners and database writes. If you don't await properly, things run out of order and your numbers go wrong. You have to assume nothing happens sequentially unless you force it.
➡️ The fix: Use local state, await everything, handle errors explicitly, and serialize anything that must stay consistent. Don't trust JavaScript's event loop to respect your intentions.
Framework-Specific Async Risks
1⃣ Hardhat: When writing tests, missing awaits and mining-mode timing differences in Hardhat can make tests pass locally but fail elsewhere; always await async calls and never assume block ordering.
2⃣ Foundry: Foundry feels safer because it's mostly synchronous, but don't get lazy. Anvil's block timing still matters, and any off-chain tooling around it is async.
3⃣ Anchor(Solana): Anchor in the solana chain helps a lot, but it won't save you from bad logic. init_if_needed, CPIs, and account loading can surprise you. Always lock ownership and track initialization explicitly.
4⃣ Node-based tools: Anything built on Node is async by nature frontends, event listeners, wallets, indexers. All of them need proper awaits, sequencing, and error handling, or the state will leak or break.
3⃣ Stellar SDK & Soroban: The SDK simplifies transaction building, but it won't catch logic flaws. Asset trustlines, sequence number mismatches, and authorization flags can surprise you. Always validate account states and handle transaction failures explicitly.
Best Practices and Mitigations
1⃣ Ethereum smart contracts: Make sure you update state before any external call. Use the Checks-Effects-Interactions (CEI) pattern, add reentrancy guards, and don't trust execution ordering. Test exploits deliberately and run static analysis before deploying anything.
2⃣ Non- EVM programs: In a blockchain like Solana, always verify accounts, ownership, and initialization status. Update balances before making CPIs, track initialization flags yourself, and don't assume Anchor macros cover every edge case.
3⃣ Node.js backends: Never use a shared global state. Await everything, sequence dependent actions explicitly, and handle errors loudly. If order matters, enforce it with queues or database transactions.
4⃣ Frameworks and testing: Await all calls in tests, use realistic mining settings, wait for transaction receipts, and simulate race conditions. If it can break in production, test it locally.
In backends, never assume that one async callback finishes before another unless explicitly ordered. The mantra is: "State before call, and await everything in between."
If you apply all this, it will save you from a future exploit, retweet it so other builders can catch these patterns early.
Top comments (0)