The bot passes every test on devnet. It ships. It loses races on day one.
★Devnet validates that your code is correct. It cannot validate that your code is competitive, because the thing you are competing against does not exist there.★
What Devnet Genuinely Proves
It is a real cluster with real consensus, so a passing devnet test is meaningful for a specific set of things:
Instruction correctness. Account metas, ordering, PDA derivation, CPI structure — all identical.
Program logic. Your on-chain code behaves the same way.
Serialization. The 1232-byte limit and account layouts are the same.
API shape. Every RPC method behaves as it will on mainnet.
★If it fails on devnet, it will fail on mainnet.★ A simulateTransaction that reverts there reverts in production too. The implication does not run the other way, and that asymmetry is the whole point.
What It Cannot Show You
| Mainnet reality | On devnet |
|---|---|
| Priority fee market | ★Essentially nonexistent★ |
| Competing bots | ★None★ |
| Block space contention | ★None★ |
| Account write contention | ★None★ |
| Slot distance under load | ★Always best case★ |
| Real liquidity and slippage | Meaningless |
★Every one of these is a submission-side property, and submission is where trading bots actually fail.★
A retry loop that never retries because nothing ever needs retrying is untested code. A fee calculation that always returns the floor because no one is competing is an untested calculation. The paths that matter most are the paths devnet never exercises.
If your code is correct and the gap is competitive, a free BoltTx key is one line to test the submission path on mainnet.
Testing Fee Logic Without a Fee Market
const fees = await connection.getRecentPrioritizationFees({
lockedWritableAccounts: writableAccounts,
});
★On devnet this returns near-nothing, every time.★ Your median calculation returns zero, your multiplier does nothing, and your ceiling never binds. The function runs, produces a number, and proves nothing.
Test it against recorded mainnet data instead:
// ★Feed real fee distributions through the pure function.★
const scenarios = [calmFees, busyFees, spikeFees];
for (const s of scenarios) {
const fee = computeFee(s);
assert(fee >= FLOOR && fee <= CEILING);
}
Keep the fee decision in a pure function so it can be tested against real distributions without a network. This is the single highest-value thing you can do to make devnet testing meaningful for the part it cannot reach.
Test Failure Paths Deliberately
Devnet will not produce these on its own, so induce them:
// ★Expiry: sign against a blockhash you let go stale.★
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
await waitBlocks(200); // ★past the window★
// Expect: expired, not a crash, and no rebuild without checking.
// ★Revert: build a transaction that must fail.★
// Insufficient balance, impossible slippage, missing account.
// ★Compute exhaustion: set the limit below measured usage.★
ComputeBudgetProgram.setComputeUnitLimit({ units: 1000 });
★A bot that has never seen an expiry in testing will handle its first one in production.★ These paths are cheap to induce and are exactly the ones that carry the double-execution and mis-accounting risks.
Where the Environments Differ Operationally
Airdrops. requestAirdrop works on devnet and is rate limited and unreliable. On mainnet it does not exist, so any funding path in your code is devnet-only by construction.
Program addresses differ. Token programs match, but DEX and launchpad programs generally do not have devnet deployments. ★A bot that trades a specific protocol often cannot be tested against it at all until mainnet.★
Devnet resets. State can be wiped. Anything you deployed or funded may simply be gone.
Reliability. Devnet has outages that mainnet does not, and diagnosing a devnet-only failure is usually wasted effort.
The Sequence That Works
★1. Devnet for correctness.★ Instructions, program logic, serialization, error handling. Cheap and repeatable.
★2. Induced failures on devnet.★ Expiry, revert, compute exhaustion, lost responses. Confirm every path resolves to a terminal state rather than crashing.
★3. Pure functions against recorded mainnet data.★ Fee calculation, retry timing, size checks. No network required.
★4. Mainnet with minimal size.★ The only way to measure slot distance, contention, and real fees. Small amounts, full logging, no strategy expectations.
★5. Scale after measuring, not before.★
Step 4 is not optional and cannot be simulated. Budget for it as a real cost — a period of live trading whose purpose is measurement rather than profit.
What to Measure in Step 4
metrics.record({
submittedSlot,
landedSlot,
outcome, // success | reverted | expired
priorityFeeLamports,
});
★Compare this distribution against the one you assumed while building★, checking computeUnitsConsumed against the limit you requested. Nearly every incorrect assumption a bot carries into production is visible in the first few hundred real transactions, and none of them were visible on devnet.
What Landing Looks Like
Real transactions through our delivery nodes: median confirmation 336ms — under one slot.
★On devnet you would measure something narrower and learn nothing from it.★ A distribution is only informative when it was produced under contention, which is the one condition devnet cannot supply.
Where BoltTx Fits
We handle submission on mainnet, which is where submission behaviour exists to be measured.
Submissions route through our own delivery nodes in four regions with stake-weighted routing and no public mempool exposure, so a transaction is not observable in transit before it lands.
You sign locally. We never hold funds, never sign, and never modify transaction contents. The tip travels inside the transaction, paid on chain from your own wallet, and reverts with the transaction if it fails, because that is how Solana handles atomic transactions. ★You pay only on transactions that reach the chain, which is what makes a small measurement run inexpensive★ — there is no monthly fee to justify before you have data.
const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");
FAQ
Is devnet testing useful for Solana bots? Yes, for correctness — instructions, program logic, serialization, and error handling. It cannot validate anything competitive, because there is no fee market or contention to compete against.
Why does my bot work on devnet but fail on mainnet? Because the failures are submission-side. Devnet has no priority fee market, no competing bots, and no block space contention, so the paths that fail in production are never exercised.
Can I test priority fee logic on devnet? Not meaningfully. Recent fee queries return near-nothing, so your calculation always produces the floor. Test the fee function against recorded mainnet distributions instead.
How do I test retry logic without congestion? Induce the failures. Sign against a blockhash you let expire, build a transaction that must revert, and set a compute limit below measured usage. Each should resolve cleanly rather than crash.
Should I test on testnet instead of devnet? Testnet is for validator release candidates and is less stable for application testing. Neither reproduces mainnet's fee market, so the choice does not affect the limitation that matters.
Do program addresses differ between devnet and mainnet? Token programs match, but most DEX and launchpad programs have no devnet deployment. A bot targeting a specific protocol often cannot be tested against it until mainnet.
Does devnet get reset? Yes, state can be wiped, so anything you deployed or funded may disappear. Treat devnet state as disposable and scriptable rather than as something to maintain.
Is requestAirdrop available on mainnet? No. It exists only on devnet and is rate limited even there, so any funding path built around it is devnet-only by construction.
How much should I risk in the first mainnet run? Small amounts, with the explicit purpose of measurement rather than profit. Treat it as a testing cost, since slot distance and real contention cannot be observed any other way.
What should I measure on the first mainnet run? Slot distance from submission to landing, the split between success, reverted and expired, and fee spend against outcome. Compare against the assumptions you built with.
Can I simulate mainnet congestion locally? Not in a way that predicts fill rate. A local validator has no competitors and no fee market, so it reproduces correctness conditions rather than competitive ones.
Why is my devnet slot distance better than mainnet? Because nothing is competing for block space. Devnet always shows close to the best case, which is why a narrow distribution there tells you nothing about production.
What is worth testing against a local validator? Program logic and integration tests, where speed and determinism help. It is the fastest environment for correctness and the least informative for competitiveness.
Should I keep testing on devnet after going live? Yes, for program changes and instruction correctness. It stays useful for the things it was always good at, provided you do not read competitive conclusions into it.
How do I know my fee logic is correct before mainnet? Keep it as a pure function and run recorded mainnet fee distributions through it, asserting the output stays within your floor and ceiling across calm, busy, and spike scenarios.
What is the biggest devnet blind spot? The retry loop. On devnet nothing needs retrying, so the code that handles expiry, resends, and terminal states is effectively untested until it runs in production.