Simulate Before You Trade: Dry-Run APIs for High-Stakes DeFi Bots
Your DeFi Bot's Worst Bug Isn't in the Code - It's in the Deployment Strategy
There's a category of bug that doesn't show up in unit tests, doesn't trigger in staging, and doesn't announce itself with a stack trace. It shows up as a drained wallet and a transaction that executed perfectly, just not the way you intended.
Automated trading bots operating on mainnet have no margin for "let's see what happens." A single malformed transaction on an Aave liquidation bot can burn your gas budget, miss the liquidation window, and leave you worse off than if the bot had never run at all. The cost of learning on live infrastructure in DeFi isn't an error log. It's wei.
The Real Problem With Live Testing
Most developers who've built bots outside of DeFi are used to environments where a failed API call costs you... a failed API call. You log it, you fix it, you retry. The feedback loop is cheap. On-chain, the feedback loop has a gas bill attached. And gas is just the beginning.
- If your Jupiter arbitrage bot submits a transaction with an underestimated gas limit, it fails mid-execution and you still pay for the computation that ran.
- If your price oracle is stale by 400ms in a high-volatility window, you're not getting a bad fill - you're getting sandwiched.
- If your cross-chain bridge bot doesn't correctly account for the destination chain's asset balance state, you're not triggering a warning. You're triggering a stuck transfer that requires manual intervention to unwind.
These aren't hypothetical edge cases. They're Tuesday.
What Dry-Run APIs Actually Give You
The core idea behind dry-run APIs is that you pipe a transaction through the full execution stack - gas estimation, policy checks, balance validation, event emission - without committing anything to state. You get back a complete simulation of what would have happened.
That sounds simple. The value is in the details of what "complete simulation" actually means. A well-implemented dry-run doesn't just tell you whether the transaction would succeed or fail. It returns the state delta: what balances changed, which events would have been emitted, what internal calls were made and in what order.
For a liquidation bot, that means you can verify the collateral math before the transaction competes for block inclusion. For a bridge bot, you can confirm the destination-side receive logic resolves correctly before the source-side lock is committed.
Here's a stripped-down example of what that simulation call might look like in practice:
const result = await simulateTransaction({
from: botWallet,
to: AAVE_POOL_ADDRESS,
data: encodeLiquidationCall({
collateralAsset: WETH,
debtAsset: USDC,
user: targetAddress,
debtToCover: debtAmount,
receiveAToken: false,
}),
blockTag: "pending",
});
if (!result.success) {
console.error("Simulation failed:", result.revertReason);
return;
}
console.log("Gas estimate:", result.gasUsed);
console.log("Asset deltas:", result.stateDiff.balances);
console.log("Events:", result.logs);
The blockTag: "pending" detail matters more than it looks. You want to simulate against the pending block state, not the last confirmed block, because liquidation opportunities and arbitrage windows exist in the mempool. Simulating against stale state is only marginally better than not simulating at all.
Latency Is a First-Class Concern
Here's where a lot of teams build the right architecture and then quietly break it: they treat simulation as a correctness check and forget it also has to be fast.
For a Jupiter arb loop running on Solana, the opportunity window can be measured in slot times - roughly 400ms. If your simulation layer adds 300ms of latency, you've consumed most of that window before you've decided whether to trade. You don't just miss the arb. You've built an elaborate system for arriving late.
This means simulation infrastructure has to be colocated or near-colocated with your execution path. It means you're not calling a generic RPC endpoint that's shared across thousands of other requests. It means the simulation response has to come back fast enough that it fits inside your decision loop without becoming the bottleneck.
The failure mode here is subtle because the bot appears to work. Simulations complete. Transactions are validated. But profitability craters because the timing slips and you're consistently executing at the wrong moment. Simulation latency is its own category of bug, and it doesn't show up in your correctness tests.
The Operational Shift This Requires
Adopting simulation-first architecture isn't just a technical decision. It changes how you think about bot deployment. Instead of asking "did this transaction succeed?" after the fact, you're asking "what will this transaction do?" before submission. That inversion forces you to define expected behavior explicitly - what state changes are acceptable, what events must be present, what gas envelope is within policy. The simulation becomes a contract the transaction has to satisfy before it gets near mainnet.
For teams managing multiple bots across chains, this also creates a natural place to centralize policy logic. Rather than encoding limits and guards in each bot independently, the simulation layer enforces them uniformly. A new bot inherits the same validation stack automatically.
The concrete takeaway: if you're running any bot that executes transactions with real value, simulation before submission isn't an optimization - it's the baseline. The alternative is paying mainnet gas rates to discover bugs that a dry-run would have caught in milliseconds.
Top comments (2)
How reliable is it Pretty reliable for catching the obvious stuff (bad gas estimates, revert reasons, malformed calldata) but it's not a guarantee. Two gaps worth knowing: State can shift between simulation and actual submission, especially on Solana where slot times are ~400ms. You're simulating against a moving target. Second, it won't catch MEV/frontrunning risk. The simulation can succeed cleanly and you can still get sandwiched once your tx hits the mempool for real. So think of it as removing the dumb failures, not as a full safety net. You still want position limits and circuit breakers as backup.
Comments
No comments yet. Start the discussion.