Devnet and Simulation: What Each One Actually Proves
A test cluster and a simulated transaction answer different questions, and treating either as a substitute for the other is the most common structural mistake in testing Solana automation. This page separates them, gives a devnet checklist you can work through in order, and lists the defect classes neither one can reach.
- Applies to
- Any Solana automation before it signs a mainnet transaction
- Preconditions
- A test keypair created for this purpose only, and an RPC endpoint for each cluster
- Pass condition
- The full flow completes twice on a test cluster and every transaction simulates without error
- Out of scope
- Anything about price, liquidity depth or competition, none of which exist on a test cluster
Devnet proves that your code path completes end to end against a real validator. Simulation proves that one specific transaction would succeed against current state on whichever cluster you asked, including mainnet. Neither proves anything about liquidity, competition or fee markets, and running both is cheaper than running either badly.
Two tools, two questions
The confusion is worth naming precisely. A test cluster is a place; simulation is an operation. You can simulate a transaction against mainnet without spending anything, which is the single most useful fact in this article, because it means the safest pre-send check is available against the environment you actually care about.
Devnet answers a question about your program flow: does the sequence of instructions, account creations, retries and state transitions run to completion without a human intervening. Simulation answers a question about a single transaction: would this succeed right now. They are complementary, and teams that use only one usually use only devnet, which is the less informative half.
The clusters and what they are for
Solana runs three public clusters with different purposes, documented in the Anza cluster reference. Mainnet-beta carries real value. Devnet is the application developer's playground, where tokens are issued on request and have no value. Testnet is where validator software releases are exercised, and it is not intended as an application test environment.
The practical consequence is that application testing belongs on devnet or a local validator, not on testnet. Teams sometimes drift onto testnet because it sounds like the testing cluster, then find its behaviour unrepresentative in ways that have nothing to do with their code. Read the cluster's stated purpose before pointing anything at it.
Public endpoints for all three are rate limited and explicitly not for production traffic. If your test hammers a public endpoint and starts receiving HTTP 429 responses, you have discovered something about the endpoint policy rather than about your software, and the fix is a dedicated endpoint rather than a retry loop.
Funding a test wallet
Create a keypair that exists only for testing. Not a copy of anything, not a wallet that has ever held value, and not one you will later reuse on mainnet. This is a hygiene rule rather than a cryptographic one: separating them means a leaked test configuration cannot cost you anything, and test configurations leak constantly through logs, screenshots and shared repositories.
Key handling during testing
A test keypair is still a real key. Keep it out of version control, out of screenshots and out of chat. Never paste a seed phrase or a private key into a website, a support form or a tool that asks for it, even one that claims to be a testing utility, because the request itself is the defect. Nothing in this checklist requires exposing a key to any third party.
Devnet SOL comes from a faucet, either through the command line client or through the public web faucet. Requests are rate limited per address and per period, which is deliberate and which you should design around rather than fight: script the funding step, keep the required balance small, and reuse the same funded test address across runs rather than creating a new one each time.
What is not on devnet
The gap is larger than most test plans acknowledge. Many production programs are deployed only on mainnet; where a devnet deployment exists it often carries a different program ID, which means your configuration is not the configuration you will run in production. That difference alone has produced a long tail of defects that appear on the first mainnet transaction.
Liquidity is the second gap and the more serious one. A devnet pool holds whatever a developer put there for testing, so price impact, slippage behaviour and route selection all behave in ways that tell you nothing. A swap that completes on devnet has demonstrated instruction correctness, not execution quality.
The third gap is everyone else. There are no competing traders, no priority fee auction of any consequence, and no congestion of the kind that makes mainnet transactions expire. Any criterion about landing rate, fee levels or timing measured on devnet is measuring an empty road.
The devnet checklist
Work through these in order. Each item has a check you can perform and a reason it exists; the order matters because a failure early makes later items uninterpretable.
- Endpoint confirmed. The tool is pointed at a devnet endpoint and you have verified it by querying the cluster rather than by trusting the configuration file. Pointing a test at mainnet by accident is the most expensive configuration error in this category.
- Keypair isolated. The signing key was created for testing, has never held value, and is stored outside the repository.
- Funded and verified. Balance queried after funding, not assumed. Faucet requests can silently fail under rate limiting.
- Program IDs recorded. Every program the flow touches is listed with the ID actually used on devnet, alongside the mainnet ID it will be swapped for. This list is the diff that mainnet will test.
- Account creation exercised. The flow has been run at least once starting from a wallet with no token accounts, so account creation and its rent deposit are inside the tested path rather than outside it.
- Full flow completes. Start to finish, no manual intervention, no step performed by hand because it was easier.
- Second run agrees. The flow completes a second time with the same outcome. Disagreement between two runs is a nondeterminism and outranks every other item on the list.
- Restart tested. The process is killed mid-flow and restarted, and you have recorded what it did on restart rather than assuming it resumed correctly.
- Cleanup verified. Accounts the flow created are accounted for, and any that should be closed are closed, so the next run starts from a known state.
- Evidence written. Signatures, timestamps in UTC and outcomes exist in a file, not only in a terminal.
Item seven deserves its own note. Two identical runs on a test cluster cost almost nothing and catch a class of race condition that is otherwise found on mainnet at full price. A single clean run is compatible with a race that resolved favourably by chance, and chance is not a test result.
What simulation returns
The simulateTransaction method executes a transaction against recent cluster state without committing it. The response carries the pieces a tester needs: an error field that is null on success, the program log lines the execution produced, the compute units consumed, and optionally the post-execution state of accounts you asked about by address.
Two configuration options matter for testing. Signature verification can be turned on to check that the transaction is properly signed, and the recent blockhash can be replaced with a fresh one so a stale blockhash does not cause a spurious failure. Those two options are mutually exclusive, since replacing the blockhash invalidates the signature, and choosing between them is choosing what you are testing.
The log lines are the most useful output and the most ignored. They show which programs were invoked, in what order and at what depth, plus any message the program emitted. A route that quietly changed venue, an instruction that ran twice, or a program invoked that you did not expect are all visible there before any funds move.
Reading compute from a simulation
Every Solana transaction runs inside a compute budget. Instructions are allotted a default number of compute units unless the transaction sets an explicit limit, and there is a hard ceiling per transaction. A transaction that exceeds its budget fails at execution, having paid its fee, which makes compute a real testing concern rather than an optimisation detail.
Simulation reports units consumed, which lets you set the limit from measurement instead of from a guess. The arithmetic below is illustrative. Suppose simulation reports 118,400 units consumed for a swap. Setting a limit of 200,000 leaves generous headroom; setting it at 125,000 leaves roughly six percent, which is thin if the route can change or an extra account creation can appear.
The reason this matters for fees is direct: the priority fee is the compute unit price multiplied by the compute unit limit you requested, not by the units you actually used. At a price of 20,000 micro-lamports per unit, a limit of 200,000 units costs 4,000,000,000 micro-lamports, which is 4,000 lamports, while a limit of 1,400,000 units at the same price costs 28,000 lamports for the same work. Requesting far more than you need is a fee defect that no functional test will surface, and one that the SOL volume bot category has to get right on every transaction because the error is multiplied by the transaction count.
Preflight and when to skip it
When you submit a transaction, the RPC node normally runs a simulation first and rejects the submission if it would fail. That preflight check is why some errors arrive immediately and clearly instead of as a silent non-landing. It is also a round trip, so high-frequency senders sometimes disable it.
Skipping preflight is a legitimate engineering choice and a testing liability. With it disabled, a doomed transaction is broadcast rather than rejected, and you learn about the failure from the absence of a confirmation instead of from an error message. If your tool skips preflight, the acceptance criteria need to cover what happens to transactions that never confirm, because that path is now the normal error path.
The check worth adding either way
Whether or not preflight is enabled, simulate against mainnet before the first real send of any new configuration. It costs one RPC call, it uses the state you actually care about, and it catches the specific class of error where a configuration valid on devnet references something that does not exist on mainnet.
Local validator with cloned accounts
The middle ground between devnet and mainnet is a local validator running a copy of real accounts. The Solana command line tools can start a local test validator pointed at a mainnet endpoint and copy named accounts and programs into the local ledger, which lets you exercise the real program against a snapshot of real state, repeatedly, for nothing.
This is the right environment for testing anything that depends on a specific program's behaviour, because the program is the real one. Its limitation is that the snapshot is frozen at the moment you took it: balances do not move, other traders do not exist, and a pool cloned an hour ago has an hour-old state. It is a laboratory, not a market.
Setup is worth scripting rather than performing by hand, both because you will do it repeatedly and because the list of accounts you cloned is part of the test record. A result obtained against a snapshot nobody can reconstruct is not reproducible, and reproducibility is the only property that makes a laboratory useful.
Which defects each layer catches
| Defect class | Simulation | Devnet | Local with cloned state |
|---|---|---|---|
| Malformed instruction or wrong account order | Yes | Yes | Yes |
| Missing token account or rent shortfall | Yes | Yes | Yes |
| Compute limit set too low | Yes | Yes | Yes |
| Compute limit set wastefully high | Only if you compare against units consumed | No | Only by comparison |
| Wrong program ID for the target cluster | Yes, against the target cluster | No, devnet has its own IDs | Yes, if cloned from mainnet |
| Retry logic and restart behaviour | No | Yes | Yes |
| Blockhash expiry handling | No | Partly | Partly |
| Slippage and price impact behaviour | Only against real state | No | Against a frozen snapshot only |
| Landing rate under congestion | No | No | No |
| Fee level under a live priority market | No | No | No |
The last two rows are the honest ending of this article. Nothing in the cheap tier can tell you how the software behaves when block space is contested, which is precisely when trading automation matters most. That question belongs to a sized mainnet rehearsal, and no amount of test-cluster work substitutes for it.
How to write the verdict
The verdict from this layer should be narrow enough to be true. A usable form is: the flow completed twice on devnet against the program IDs listed, restart behaviour was observed and recorded, and every transaction in the flow simulated without error against mainnet state on the stated date. That sentence is defensible and it does not overclaim.
What it must not say is that the tool works. It says that the code path runs and that the transactions were well formed at a moment in time. The next sentence in any honest report is the list of things this layer could not see, which is the last column of the table above, carried forward into the design of the rehearsal that follows.
Questions the desk gets asked
Is testing on devnet enough before mainnet?
No. Devnet proves the code path completes: instructions build, accounts are created, signatures are produced, retries fire. It cannot prove anything about liquidity, price impact, competition for block space or fee markets, because none of those exist there in a comparable form. It removes one class of defect and leaves another untouched.
What does simulateTransaction actually check?
It executes the transaction against recent cluster state without committing the result, and returns any error, the program logs, the compute units consumed and optionally the post-execution data of accounts you name. It answers whether this exact transaction would have succeeded a moment ago against the cluster you asked.
Do devnet tokens have any value?
No. Devnet SOL is issued by a faucet on request and exists so developers can pay for transactions on a test cluster. It cannot be moved to mainnet, sold or exchanged, and anyone offering to buy it is running a scam that ends with you sending something real.
Why does a transaction succeed in simulation and fail when sent?
Because the state moved between the two. Simulation runs against a recent slot; by the time the transaction lands, a pool balance can have changed, an account can have been closed, the blockhash can have expired, or the compute budget requested can no longer be enough. A clean simulation is a necessary condition, not a guarantee.
Can you clone mainnet accounts into a local validator?
Yes. The local test validator can be started against a mainnet endpoint and told to copy specific accounts and programs into the local ledger, which lets you exercise a real program against a snapshot of real state. It is the closest thing to mainnet realism that costs nothing, and the snapshot is frozen at the moment you took it.
Should preflight checks be skipped for speed?
Only deliberately, and only where you have another guard. Preflight is a simulation run before submission that catches transactions doomed to fail, and skipping it removes that catch in exchange for latency. If you skip it, the error handling downstream has to be genuinely complete, because failures now arrive later and less clearly.
Does devnet get reset?
Test clusters can be reset and their state is explicitly not durable, so anything you rely on being there may not be next week. Treat devnet state as disposable, script the setup so it can be recreated from nothing, and never store a test result whose only evidence lives in an account on a test cluster.
Filed under Before you trust it by The QA Ground Desk. Behaviour described here comes from protocol documentation and from procedures the desk can run itself; any figure in an example is labelled as illustrative arithmetic and describes no real account. How the desk decides what to publish is set out in the method note.