Solana infrastructure, included with every server. LEARN MORE>

OpenInfra.shopeninfra.sh
Engineering11 min read

Submitting a Solana Transaction Is Not One Request

OpenInfra Team·August 17, 2026
Submitting a Solana Transaction Is Not One Request

The button says “Send.” The work behind it is longer—and its network path matters.

For a user, a Solana transaction can feel like one action: approve a swap, send a token, create an account, place an order. For an application, it is a sequence of decisions made across a changing network. It needs a recent blockhash. It may need to simulate. It must submit the signed transaction, observe the result, decide whether a retry is safe, and finally determine what actually happened on-chain.

That sequence crosses two boundaries. First, the application must reach its RPC endpoint. Then the endpoint must carry the transaction into the Solana network and return the resulting state. When application compute and RPC live in different places, the first boundary is another public-internet dependency inside every submit, status check, and confirmation read.

OpenInfra runs the application server and its dedicated Solana endpoint on the same private fabric. That does not make every transaction land automatically. It removes an unnecessary hop from the path the application controls, so transaction handling can be designed around the actual chain outcome rather than an extra public network boundary.

Reliable transaction handling begins by separating submission from confirmation.

A transaction has more than one outcome

There are several moments that can look like success:

  • The application built a valid transaction.
  • The wallet signed it.
  • The endpoint accepted the submission.
  • A validator processed it.
  • The transaction reached the commitment level the application requires.
  • The application fetched and recorded the result.

Those moments are related, but they are not identical. An endpoint response after submission usually gives the application a signature or an acknowledgement that the request was accepted. It is not, by itself, proof that the desired state change is now confirmed.

The practical rule is simple: submission tells you that a transaction was sent; confirmation tells you what became true.

The endpoint is part of the transaction path

Transaction code is often discussed as if the only important component is the wallet or signer. In production, the RPC endpoint is part of the path as well. The application uses it to obtain a blockhash, simulate a transaction, submit the signed payload, query the signature, and fetch confirmed state after the result is known.

Those operations are not a separate feature bolted onto compute. They are the control plane of a transaction flow.

Every OpenInfra server includes a dedicated JSON-RPC endpoint. The application can make those calls from the same private network where it runs, instead of treating transaction reads and writes as traffic to a separately purchased public API. This is useful because a transaction flow has several round trips by design. The goal is not to make a fragile flow look fast; it is to give the application a direct, predictable path for the reads and writes it must make.

The same principle applies as the product grows. A simple backend may run well on a VPS. A busy transaction service or execution system may need the more consistent CPU isolation of a VDS. A workload that needs complete host control can run on bare metal. The transaction lifecycle remains the same. The compute environment changes only when the workload’s own constraint changes.

Start with the user action

The transaction path should reflect the action’s consequences.

A user changing a display preference inside an application can tolerate a retry or a delayed update. A user sending funds, closing a position, or approving a trade cannot be treated the same way. The system needs to know whether an action is safe to submit again, whether it can be reconstructed after a refresh, and what confirmation level is required before the UI presents it as complete.

Write down the answer before choosing timeouts or retry counts:

  • What state change does the user expect?
  • Is repeating the same signed transaction safe?
  • Is creating a new transaction safe if the first one is uncertain?
  • What information can the user use to find the action again?
  • When is it appropriate to call the action complete?

These are product questions as much as infrastructure questions. The code should make them explicit.

A recent blockhash gives the transaction a lifetime

Before signing, a normal Solana transaction uses a recent blockhash. This ties the transaction to a recent part of the chain and prevents it from being valid indefinitely.

For an application, the important consequence is expiry. A transaction prepared too early can become stale before the user signs it or before the application submits it. A transaction retried much later may no longer be valid. The transaction lifecycle therefore starts close to the moment of signing and submission, not when a screen first opens.

Fetch the recent blockhash through RPC when the application is ready to construct the transaction. Keep the context that helps the application later decide whether a pending transaction has expired. Do not leave an old unsigned transaction sitting in a client and assume it will remain ready when the user returns.

This is especially important in flows involving wallet approval. A user may review a transaction immediately, or may leave it open long enough for the original blockhash to become unusable. The application should be able to rebuild the transaction cleanly rather than making the user interpret a cryptic network error.

Build the smallest transaction that expresses the action

Transaction reliability begins before the network sees anything.

Build only the instructions the action requires. Validate the accounts, token amounts, program parameters, and expected authority in application code where that is appropriate. Use the same inputs for the user-facing summary and the transaction itself so the interface does not describe one action while signing another.

For multi-step product flows, be clear about whether there is one atomic transaction or several independent actions. A single transaction can make a group of instructions succeed or fail together. Several transactions introduce intermediate states, separate signatures, and separate recovery needs. Neither model is always right, but the product should not hide the difference.

The goal is not to make every transaction clever. It is to make the signed payload understandable, minimal, and reproducible when debugging is required.

Simulate to learn before sending

Simulation is a useful preflight check performed through the same RPC path the application will use to submit and inspect the transaction. It can reveal obvious failures before a user is asked to sign or before an application pays to submit an action that cannot succeed as constructed.

It is particularly valuable when a transaction depends on account state, program rules, compute requirements, or a collection of instructions that are difficult to inspect by eye. Simulation logs can help identify an incorrect account, insufficient funds, a failed program condition, or an instruction sequence that does not do what the application intended.

But simulation is not a reservation. The chain can change between simulation and execution. Prices can move, an account can be updated, another transaction can consume the relevant state, or a program can take a different path based on conditions at execution time.

Use simulation to catch mistakes early. Do not present it as a guarantee that the final transaction will land.

What to do with a simulation failure

Classify the result before showing it to the user. Some failures are actionable: an insufficient balance, an invalid input, a missing account, or a known program condition. Others are operational: a stale blockhash, a transient endpoint issue, or an application construction error.

The user should see an explanation that matches the action they can take. “Insufficient token balance” is useful. “Simulation failed” may be technically true but does not help someone decide what to do next.

The application should retain the detailed failure context for logs and support. This is where a short transaction identifier, request timing, program logs, and the construction inputs become invaluable.

Submission is a handoff, not the finish line

Once signed, submit the transaction through RPC. On OpenInfra, that request travels from the server to its dedicated endpoint over the private fabric. Store the signature immediately with the user action or backend job that created it. The signature is the durable handle for the rest of the transaction lifecycle.

Do not rely on a browser tab, one in-memory promise, or a single API response to remember that the action exists. If the client refreshes, a mobile app is suspended, or a server process restarts, the application needs to resume from the signature and inspect its state.

At this point, a thoughtful interface should show the action as pending. Not complete. Pending is an honest state: the application has handed the signed transaction to the network and is now waiting to learn its outcome.

That small distinction prevents a great deal of confusion. It gives the application space to confirm properly, and it gives the user a meaningful status if a network response takes longer than expected.

Confirmation is an application responsibility

Confirmation means the application checks whether the transaction reached the commitment level needed for the workflow and whether the executed transaction had the expected result.

The appropriate commitment depends on the action. A fast-moving interface may show an early pending update while continuing to monitor for a stronger confirmation. A balance history, settlement record, or irreversible product action should wait for the level of certainty its domain requires. The important part is consistency: the UI, database, and downstream workers should agree on what “complete” means.

When confirmation arrives, inspect more than the fact that a signature exists. Check for execution errors. Read the state change or relevant account data when the business action requires proof beyond a status. Store the completed result so a later refresh displays the same answer rather than beginning the search again.

Timeouts create uncertainty, not necessarily failure

A timeout is one of the most common sources of accidental duplicate actions.

If the application submits a transaction and does not receive a prompt response, it does not know whether the endpoint failed before receiving it, whether the endpoint accepted it but the response was lost, or whether the transaction is simply still pending. Retrying immediately with a newly constructed transaction can be safe in some flows and harmful in others.

The first response to uncertainty should be observation. Query the known signature. Check its status and the relevant confirmed state. Give the network a bounded period to resolve the existing action before creating another one.

If the original transaction is known to have expired without landing, a new transaction may be appropriate. If it is still possible that the original will land, the product must apply its own idempotency rule before sending a replacement.

Design retries around idempotency

Idempotency means that repeating an operation leads to the same intended final state rather than creating an extra action.

Some transactions are naturally safe to re-submit with the same signature while they remain valid. Others are not safe to replace with a new transaction because the action could execute twice. A payment or trade flow may need an application-level operation identifier that links the user intent, the transaction signature or signatures, and the final settled record.

This lets the backend ask a better question than “did the request time out?” It can ask “has this user operation already reached its intended result?” If yes, return that result. If not, determine whether the original transaction is still viable, expired, failed, or safe to replace.

The exact logic will differ by product. What should not differ is the principle: retries must be driven by the action’s semantics, not by a generic HTTP timer.

Keep the pending state durable

For any meaningful transaction, store a durable record before and after submission. At minimum, keep the user or job that initiated it, the intended action, the signature, the time submitted, the current status, and enough context to verify the outcome.

This record supports several important behaviours. A user can leave and return without losing the action. A backend worker can resume confirmation after a restart. Support can locate an action from a signature. Analytics can distinguish submitted, confirmed, failed, and expired operations. A retry worker can avoid accidentally creating a second action.

The transaction signature is not merely a link to an explorer. It is part of the application’s own state machine.

Separate early feedback from final state

Fast interfaces should not feel frozen while a transaction is pending. They can show that an action was submitted, display the signature, and update a temporary pending view. The key is to label the state honestly.

Do not write an irreversible “success” record merely because a signature was returned. Do not update a final balance from an optimistic assumption unless the product can reconcile it when execution differs. Do not erase a pending action after a short timeout if the transaction may still be discoverable and confirmable.

An interface can be responsive and accurate at the same time when it treats submitted, pending, confirmed, failed, and expired as real states rather than variations of one loading spinner.

The practical transaction path

For a typical application, the path looks like this:

  1. Validate the user intent and construct the needed instructions.
  2. Fetch a recent blockhash near the moment of signing.
  3. Simulate where a preflight check is useful.
  4. Ask the wallet or signer to sign.
  5. Store the pending operation and submit it through RPC.
  6. Track the signature until the required confirmation and execution result are known.
  7. Reconcile relevant state, record the outcome, and make retries safe if uncertainty remains.

This is more work than a single sendTransaction call. It is also what makes the action dependable when the client refreshes, the endpoint is slow, the network is busy, or the original request does not return a simple answer.

The transaction is complete when the application can explain what happened—not merely when it has sent bytes to an endpoint.

For an OpenInfra deployment, that means keeping the full path together: compute for the application, a dedicated RPC endpoint for blockhashes, simulation, submission, and confirmation, and a private network path between them. The application still needs good transaction logic. It simply does not need to introduce an extra public-internet boundary before it can learn what the chain decided.

Get Started

Stop paying for RPC.

Every server includes the full Solana stack. See how the economics work in practice.