What preflight actually gates

A dry run before live is the last point at which a mistake is still free. Preflight is the sequence of checks that stands between a configuration you believe in and a set of signed transactions you cannot recall, and its purpose is narrow: prove the path works end to end at the smallest size that still counts as real, then decide deliberately whether to scale it.

Most bad runs are not caused by exotic failures. They are caused by ordinary ones that nobody looked for: a configuration file edited but never reloaded, an endpoint pointed at the wrong cluster, a wallet funded to a number that looked right in a spreadsheet, a monitoring channel that has been silently disconnected for a week. Each of those is trivially detectable before the first signature and expensive afterwards, which is the entire argument for a gate.

Treat preflight as a decision procedure rather than a ritual. Each gate has a question, an observable answer and a defined action when the answer is wrong. If a gate cannot fail, it is not a gate; delete it and stop pretending it provides assurance. The rest of the operations runbooks assume you arrive at a live run having cleared this one.

Dry run, simulation and canary are three different things

These three words get used interchangeably, and the confusion has a cost: operators think they have tested something they have not. Separating them tells you exactly which failures each mode can catch, and more usefully, which it cannot.

Three modes, three scopes

A dry run builds everything and submits nothing. No node sees the transaction, no fee is charged, and the only thing under test is your own code path: does the configuration parse, do the accounts resolve, does the tool assemble the instructions it claims to. A simulation hands the assembled transaction to an RPC node, which executes it against current state and returns logs and compute usage without committing anything. That catches missing accounts, failing program logic and compute limits, but it still costs nothing and proves nothing about landing. A canary is a real transaction, signed with a real key, paying a real fee, at the smallest size that still exercises the whole path. Only the canary tests the parts that exist solely in production.

Ordering follows scope. Run the dry run until it is boring, simulate until the node stops objecting, then run one canary and reconcile it by hand. Skipping straight from a passing simulation to a full fan-out is the common shortcut, and the reason it hurts is that simulation never touches signing keys, fee payment, account creation, or the rate limits of the endpoint under real load.

The go and no-go gate table

Write the gates down before the run rather than during it. The value of a table like this is that it converts a judgement call made under time pressure into a lookup made in advance, and it gives whoever is on call a defensible reason to stop something that somebody else started.

Gate What is checked Pass looks like No-go action
Configuration review The file the process will actually load, read line by line against intent Every value is either explicitly set or knowingly left at a default you can state Stop and re-read; do not adjust values while the process is running
Endpoint and credentials Cluster, endpoint URL, key or token, and where that credential is stored The endpoint answers, reports the intended cluster, and the credential is not in the repository Correct the endpoint or rotate the credential before any further gate
Wallet funding floors Balance of every wallet in the run against the stated per-wallet floor No wallet is below its floor and the fee payer has headroom for the whole run Top up and re-check; never start a run intending to fund it midway
Single transaction simulation One assembled transaction evaluated by a node without submission The node returns success, and the compute used sits inside the limit you request Fix the instruction set or the account list; do not raise limits to hide an error
One-wallet canary A real signed transaction at the smallest usable size, from one wallet The signature confirms and the closing balance matches the predicted balance Halt, reconcile the difference, and treat the gap as unexplained until it is explained
Monitoring reachable Log output, metrics and the alert channel, tested with a deliberate signal The canary appears in the log, the metric moves, and a test alert arrives Do not scale up; a run you cannot observe cannot be stopped on evidence
Abort path tested The exact command or action that stops the run, executed once for real The process stops, leaves no orphaned work, and the state afterwards is known Do not run; build the stop path first, because it cannot be written mid-incident

Note the shape of the last column. No-go never means proceed carefully. It means the run does not start, or does not widen, until that specific gate is cleared, and the person who clears it says so in writing. A gate that can be waived verbally by whoever is keenest to start is decoration.

The preflight checklist

Below is the working list, grouped so it can be worked through in order by someone who did not write the configuration. Keep it in the repository next to the runbook, and tick it in a fresh copy each time rather than reusing a marked-up one, because a pre-ticked list is how a check gets skipped.

Keys

  • The signer path in the configuration is explicit, and it points at the key you intend, not a default location.
  • The address derived from that key matches the address recorded in the account map for this role.
  • No key material appears in the configuration file, the environment dump, the log output or the console history.
  • The key controls only the accounts this run needs, and you can state what else it can reach.

Funding

  • Every wallet in the run is at or above its stated per-wallet floor, checked against the chain rather than a spreadsheet.
  • The fee payer holds enough for the whole run plus a margin, not enough for the first few cycles.
  • Any account the run will create has its reserve accounted for, and you know whether that reserve comes back.
  • The destination of any sweep at the end of the run is set now, not decided later.

Configuration

  • The file on disk is the file the process will load, confirmed after the last edit and after any restart.
  • Sizes, counts, intervals and caps are stated explicitly, and every unset value is a default you can name.
  • The maximum spend the configuration permits in one hour has been calculated, not estimated.
  • The configuration is committed or copied somewhere immutable, so the run can be reproduced afterwards.

Endpoints

  • The endpoint answers, and it reports the cluster you intend rather than the one left over from testing.
  • The credential is supplied from a secret store or a restricted file, never from the repository or a chat message.
  • A fallback endpoint is configured and has been tried at least once, not merely written down.
  • You know the rate limit that applies and roughly what the run will demand against it.

Observability

  • Log output is written somewhere durable, not only to a terminal that will close with the session.
  • A deliberate test signal has arrived in the alert channel today, from this host.
  • The three numbers you would check first during an incident are visible without running a query you have not written yet.
  • Someone other than the operator can find the log location from the runbook alone.

Abort

  • The stop command exists, is written in the runbook verbatim, and has been executed once against this deployment.
  • The abort criteria are numeric or binary, so nobody has to interpret them at speed.
  • You know what a half-completed cycle leaves behind, and how to tell whether one is in progress.
  • The person on call has the access needed to abort without waking anyone else up.

Groups matter more than items here. Each group answers a different question, and a group with a single unchecked line is a group that has failed, not a group that is mostly fine. If the list feels long, remember that its length is fixed while the cost of the omissions it catches is not.

Endpoints and credentials

Endpoint problems are the quiet ones, because a wrong endpoint usually works. It responds, it accepts requests, and it produces plausible results right up until you notice the cluster is wrong, the node is behind, or the shared free tier drops requests once the run reaches its normal rate. Confirm three separate things: which cluster the endpoint serves, that it is current, and what it does under the request pattern the run will produce rather than the single request you are testing with.

Credential handling deserves the same discipline as key handling, if not the same paranoia. An RPC key is not a signing key and cannot move funds, but it can be spent by someone else until your quota is gone, which means the run stops at an unpredictable point. Keep it out of the repository, out of the container image, out of screenshots and out of the log line where the tool prints the full endpoint URL on startup. That last one leaks more keys than any attack.

Blockhash expiry is the reason latency belongs in the preflight rather than in the postmortem. A blockhash is valid for 150 slots at a target slot time of about 400 milliseconds, so a transaction built against a stale blockhash by a lagging node has a shrinking window in which it can land. If your endpoint is behind, transactions fail in a way that looks like your logic is broken. The protocol behaviour is documented in the Solana developer documentation.

Funding floors and the arithmetic behind them

A funding floor is the balance below which a wallet should not begin a cycle. It exists because a wallet that runs out midway does not stop cleanly: it produces failed transactions that still cost the base fee of 5,000 lamports per signature, charged whether the transaction succeeds or fails, and it leaves you reconciling partial work. Set the floor from the arithmetic of one cycle plus a margin, and check it against the chain immediately before the run rather than trusting a fan-out you performed yesterday.

Include account reserves in the floor. If a cycle can create a token account, that account locks 2,039,280 lamports as its rent exempt reserve, returned only when the account is closed with a zero token balance. A floor calculated from fees alone will be wrong by exactly the number of accounts your run creates, and that error compounds across a fleet. The fan-out order and reconciliation method are set out in the fleet funding runbook.

Simulating one transaction properly

Simulate the transaction the run will actually send, not a simplified version of it. A simulation of a stripped-down transfer proves that transfers work, which was never in doubt. Build the real instruction set, with the real accounts, at the real size, and hand that to the node. What you are looking for is a success result, a compute figure comfortably inside the limit you request, and a log that contains nothing you cannot account for.

Compute is worth reading carefully. Each instruction receives a default allocation of 200,000 compute units unless you raise it, with a ceiling of 1,400,000 units for the whole transaction. A simulation that succeeds while sitting close to your requested limit is a warning, because live state is heavier than test state and the margin is where the failures live. Raising the limit to make a simulation pass is not a fix; it converts a visible failure into an intermittent one.

preflight commands
solana config get
solana config set --url <RPC_ENDPOINT>
solana address -k <path/to/signer.json>
solana balance <WALLET_PUBKEY> --url <RPC_ENDPOINT>
solana transfer <DESTINATION_PUBKEY> <AMOUNT_SOL> --from <path/to/signer.json> --dry-run
solana confirm -v <TRANSACTION_SIGNATURE>

Check the size of the serialized transaction as well. The limit is 1,232 bytes, and a transaction that touches many accounts can approach it without any single change looking significant. If you are close, an address lookup table reduces the space that account list consumes. Discovering the limit during a live run is worse than discovering it here, because in a live run it presents as a subset of your work silently never being attempted.

The one-wallet canary

The canary is the first real transaction, and it is the gate that most runs skip. Its purpose is to exercise everything that only exists in production: the actual signing key, the actual fee payment, account creation, the endpoint under authentication, and your own monitoring. One wallet, one cycle, the smallest usable size, watched by a person from start to finish.

The reason to keep the canary formally separate from the dry run is that the two answer different questions and one of them cannot be undone. A professional Solana volume bot presents the dry run and the first small live run as two explicit steps with a decision between them rather than one button, and if the tool you are using collapses them, you build the gap yourself by running the canary manually before you let the scheduler near it.

  1. Isolate one wallet. Pick a single wallet, confirm no other process is configured to use it, and write down its address and starting balance. Reconciliation later depends on knowing the starting number exactly rather than approximately.
  2. Fund it to the smallest usable size. Cover one real action, the fees it will incur and any reserve it will lock, and nothing more. The canary should be sized so that a total loss is an annoyance you would not need to report.
  3. Set the smallest parameters the configuration allows. Reduce sizes, counts and intervals to their minimum. You are testing whether the path works, not whether the approach is profitable, and mixing those questions makes both answers unreliable.
  4. Run one cycle in the foreground. Start it with an operator watching and stop after a single completed cycle. Do not start it under a supervisor that will restart it, and do not walk away from the first one.
  5. Confirm the signature independently. Look the signature up through an explorer or a second endpoint rather than accepting the report of the tool that produced it. Independent confirmation is the only kind that catches a tool reporting success it did not achieve.
  6. Reconcile the balance against prediction. Compare the closing balance with the starting balance minus the fees and reserves you predicted before the run. Any difference is an open item, including a difference in your favour.
  7. Check monitoring recorded it. The log line, the metric and the alert channel should all show the canary. If any of the three missed it, fix that before scaling, because the full run will be invisible in exactly the same way.
  8. Write down go or no-go. Record the decision, the time, the parameters used and the abort criteria for the full run. The written form is what lets someone else act on your behalf later without guessing what you intended.

Sizing the canary

A canary that is too small tests nothing, because it avoids the account creation and the fee accounting that make the real run interesting. A canary that is too large stops being a test and becomes the run you were trying to gate. Size it from the fees and reserves it will genuinely incur, then compare that figure to the value the full run puts at risk so the ratio is visible before anyone argues about it.

Worked example: canary cost against fleet exposure, illustrative

Suppose an illustrative per-wallet floor of 0.02 SOL, which is 20,000,000 lamports; pick your own floor, since it depends entirely on what one cycle does. Assume the canary cycle signs 4 transactions, each with one signature, at the base fee of 5,000 lamports per signature: 4 multiplied by 5,000 gives 20,000 lamports, or 0.00002 SOL, charged whether those transactions succeed or fail. If the cycle also opens one SPL token account, that account locks 2,039,280 lamports as its rent exempt reserve, returned when the account is later closed with a zero token balance. The minimum viable canary balance is therefore 2,039,280 plus 20,000, which is 2,059,280 lamports, or 0.00205928 SOL. Now compare that with the full run: a fleet of 25 wallets each held at the floor represents 25 multiplied by 20,000,000, which is 500,000,000 lamports, or 0.5 SOL. The canary exposes 2,059,280 against 500,000,000, roughly four tenths of one percent of the value the full run puts in motion, and it answers the only question that matters before that fan-out happens.

Two things fall out of that arithmetic. First, the canary is cheap enough that cost is never a real argument for skipping it, so when someone skips it the reason is time rather than money. Second, the reserve dominates the fee by two orders of magnitude, which means your funding floors are driven by the accounts a cycle creates rather than by the transactions it signs.

The abort path you tested first

Every gate above assumes there is a way to stop. That assumption is worth checking rather than making, because a stop path that has never been executed is a hypothesis, and the moment you need it is the worst possible time to test it. Run the abort once, deliberately, against the real deployment before the canary, and observe what it leaves behind.

A live run cannot be recalled

Once a transaction is signed and submitted there is no cancellation, no support channel and no reversal. Value that has moved has moved, to an address that owes you nothing, and the only remaining questions are how much moved and how quickly you noticed. The corresponding rule is that the only abort you can rely on is the one you executed before you needed it. An untested stop command, an unreachable dashboard, a credential only one person holds, and a process that restarts itself after being killed are all ways of discovering during an incident that you have no abort path at all.

Test the abort for its aftermath as well as its effect. Stopping the process is easy; knowing whether a cycle was half complete when it stopped is the part that determines what you do next. Establish in advance how you tell the difference between work that finished, work that was never attempted, and work that was submitted but not yet confirmed. The ordered wind-down, including drain order and account closure, is covered in the clean shutdown runbook.

Scheduling the run and recording the decision

Scheduling turns a supervised action into an unsupervised one, so treat the first scheduled execution as a second preflight rather than a continuation of the first. A run started by hand has an operator present by definition. A run started by a timer at four in the morning has whatever monitoring you built and nothing else, and the failure modes it introduces are its own: an environment the scheduler does not inherit, a working directory that differs, a credential available in your shell but not in the service, an overlapping execution when one cycle runs longer than the interval.

Decide explicitly what happens when a scheduled run overlaps its predecessor, because the default is usually to start anyway. Two instances signing with one key produces conflicting expectations about balances and duplicated work that is difficult to unpick afterwards. A lock file, a single-instance flag or a supervisor that refuses to start a second copy all work; assuming it will not happen does not. Pair the schedule with the alerting rules described in the monitoring runbook so that a silent scheduler failure is itself an alert.

Finally, record the decision. Preflight produces an artefact: the checklist as completed, the gate results, the canary signature, the parameters, the abort criteria and the name of the person who said go. That record is what makes a later question answerable, and it is what allows the next run to start from evidence rather than from memory. Without it you repeat the entire sequence from scratch every time, which in practice means you stop repeating it at all.

Questions the desk gets asked

How is a dry run different from a simulation?

A dry run builds the transaction and stops, so nothing is submitted and no node evaluates it. A simulation sends the built transaction to an RPC node, which executes it against current state and returns the result without committing it. The dry run tests your own code path; the simulation tests whether the network would accept the transaction.

Does a successful simulation guarantee the live transaction will succeed?

No. Simulation reflects the state at the moment it ran, and state moves. Balances change, accounts are closed, blockhashes expire, and priority conditions shift between simulation and submission. Treat a passing simulation as evidence that the instruction set is well formed and the accounts exist, not as a promise about the outcome.

Why use one wallet rather than a small percentage of the fleet?

A single wallet gives you one signature, one balance and one log line to reconcile, which makes any discrepancy obvious. A percentage of the fleet produces several partial outcomes at once and forces you to guess which one is representative. Widen only after the single case reconciles exactly as predicted.

What belongs in the abort criteria?

Concrete conditions that can be checked without judgement: a balance falling below a stated floor, a failure count over a stated window, an endpoint error rate, a transaction landing at a size you did not authorise, or any result you cannot explain. Vague criteria are ignored under pressure because nobody can say whether they were met.

Should preflight be repeated for every run?

Repeat the full sequence after any change to configuration, keys, endpoints, dependencies or the machine itself. For an unchanged repeat run, the short form is enough: confirm balances, confirm the endpoint answers, confirm monitoring is receiving data, and confirm the abort path is still in place.

Can the canary be skipped if the simulation passed?

It can be skipped, and skipping it is the usual reason the first live run surprises someone. Simulation never pays a fee, never creates an account and never proves your key can actually sign in production. The canary is the smallest experiment that exercises the whole path with real value.