> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbitflare.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Apex Best Practices

> Land more Solana transactions with OrbitFlare Apex: keep connections warm, size the tip and compute budget, confirm every send, and retry the right way.

## Keep Connections Warm

The slowest part of a cold send is the connection setup, not Apex. Open the connection before you need it and keep it open.

<Tabs>
  <Tab title="QUIC">
    The [Rust client](/apex/rust-client) handles this for you. It pings every second against the Apex endpoint's 30 second idle timeout, resumes with 0-RTT, and re-handshakes in the background as soon as a drop is noticed.

    * Create **one client per process and Apex endpoint** at startup, not one per send. Streams multiplex on the single connection.
    * The client is cheap to `Clone` and clones share the connection. Hand a clone to each task.
    * Export `health()`, `reconnects_total()`, and `zero_rtt_resumptions_total()` to your metrics so you can see drops.

    If you implement QUIC yourself, send a QUIC PING well inside the 30 second idle timeout and keep a session ticket for 0-RTT.
  </Tab>

  <Tab title="HTTP">
    * **Reuse one connection.** Use a client that keeps connections alive: a `requests.Session` in Python, a shared `reqwest::Client` in Rust, or a keep-alive agent in Node.js. A new TCP connection per send adds a full round trip.
    * **Call `GET /ping` when idle.** It needs no key, returns `pong`, and keeps the connection from being closed between bursts. Every few seconds is enough.
    * **Warm up at startup.** Call `/ping` once before your first real send so the handshake is already done.

    ```python theme={null}
    import threading

    import requests

    session = requests.Session()  # reuse this session for every send

    def keep_warm(stop: threading.Event, url="http://fra.apex.orbitflare.com/ping"):
        while not stop.wait(5):
            session.get(url, timeout=2)

    stop = threading.Event()
    threading.Thread(target=keep_warm, args=(stop,), daemon=True).start()
    ```
  </Tab>
</Tabs>

## Send from Close to the Apex Endpoint

Pick the [Apex endpoint](/apex/endpoints) with the lowest round trip from your sender, and measure it with `/ping` rather than guessing from a map. Every Apex endpoint routes to the validator clients nearest the upcoming leaders, so your job is only to reach the endpoint quickly.

If you run in several regions, send from each region to its own nearest endpoint. Sending the **same signed transaction** to more than one Apex endpoint is safe, since the network executes a signature once, but each copy counts against your rate limit.

## Size the Tip to the Trade

The floor (0.001 SOL on the standard tier) is the minimum to be accepted, not a recommendation for every transaction.

* The tip is what funds your **Jito bid**: the tip minus the 5,000 lamport base fee. In a contested block, a floor-level bid may lose the auction. The stake-weighted and TPU paths still run, but you give up one of your three paths.
* You pay the tip **only when the transaction lands**, so a larger tip on a transaction that matters costs nothing when it misses.
* Scale the tip with the value of landing. A routine transfer can sit at the floor. A competitive trade should tip in proportion to what landing first is worth to you.

Read the floor from the rejection message if you are unsure of your tier: a below-floor error states it.

## Set a Compute Budget on Every Transaction

Apex gets you to the leader. The leader's scheduler then orders by priority fee. Without a compute budget you compete at the bottom of the queue.

* **Compute unit limit:** simulate the transaction on your regular RPC, take the units consumed, and add a margin of around 10 to 20 percent. The default limit when you set none is far higher than most transactions need, which wastes your priority.
* **Compute unit price:** follow the market for the accounts you write to. `getRecentPrioritizationFees` with your writable accounts is a reasonable starting signal.
* Put the ComputeBudget instructions **first** in the transaction.
* This applies to legacy and v0 transactions. A [transaction v1](/apex/transaction-v1) sets its budget in the message's `TransactionConfig` instead, and ComputeBudget instructions do not take effect there.

The memo transactions in these docs use a 100,000 unit limit because the memo program is unusually expensive. It is a poor guide for your own limit. Size it to your own instructions.

## Confirm Every Send

A signature in the reply means **accepted**, not landed.

* Poll `getSignatureStatuses` on a Solana RPC, or subscribe with `signatureSubscribe`, until the status is `confirmed` or `finalized`.
* Check the `err` field. A transaction can land and still fail on chain. The tip is part of the transaction, so a transaction that fails during execution rolls back the tip transfer too, and you pay only the network fee.
* Stop waiting when the blockhash expires. Track `lastValidBlockHeight` from `getLatestBlockhash`. Once the chain passes it, the transaction can never land.

In Rust, `rpc::SolanaRpc::confirm(signature, timeout)` does the polling and returns the slot, `None` on timeout, or an error if the transaction failed on chain.

## Let Apex Retry, Then Rebuild

Apex resends an accepted transaction until it lands or its blockhash expires. Work with that instead of around it:

* **Do not resend an accepted transaction in a loop.** It adds nothing and spends your rate limit.
* **Use a fresh blockhash.** Fetch it at `confirmed` commitment right before you sign. A blockhash that is already a minute old leaves Apex very little time to work with.
* **When the blockhash expires, rebuild.** Fetch a new blockhash, re-sign, and send the new transaction. Make sure the first one can no longer land before you do, or use logic that is safe if both execute.
* **Retry transport failures.** If a send fails before you get a reply, send the same bytes again. The endpoint deduplicates by signature.
* **Back off on `rate limited` and `busy`.** See [which errors to retry](/apex/errors-and-rate-limits#which-errors-to-retry).

`maxRetries` caps how many times Apex re-sends a transaction. Leave it at the default unless you want a transaction to give up early, for example a quote that is stale after a second or two.

## Integrate with Feedback, Run without It

While you integrate, use a transport that tells you why a transaction was rejected: JSON-RPC, the HTTP routes, or a bidirectional QUIC stream. Once your transactions are accepted reliably, move hot paths to unidirectional QUIC streams, and keep a bidirectional or HTTP send available for debugging.

## Make Every Transaction Unique

Apex deduplicates by signature. Two transactions with identical instructions, signers, and blockhash have the same signature and count as one. If you intend to send the same action twice, change something, such as a memo or the compute unit price by one micro-lamport.

## Checklist

<AccordionGroup>
  <Accordion title="Before you go live">
    * One tip per transaction, top level, funded by a signer, tip account in the static keys
    * Tip account chosen at random per transaction from a cached `getTipAccounts` list
    * Compute unit limit and price on every transaction
    * Blockhash fetched right before signing
    * Connection opened and warmed at startup
    * Every send confirmed, with the `err` field checked
    * Backoff on rate limited and busy, rebuild on invalid
    * API key in an environment variable or secret store, not in code
  </Accordion>
</AccordionGroup>
