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.- QUIC
- HTTP
The 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
Cloneand clones share the connection. Hand a clone to each task. - Export
health(),reconnects_total(), andzero_rtt_resumptions_total()to your metrics so you can see drops.
Send from Close to the Apex Endpoint
Pick the Apex endpoint 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.
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.
getRecentPrioritizationFeeswith 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 sets its budget in the message’s
TransactionConfiginstead, and ComputeBudget instructions do not take effect there.
Confirm Every Send
A signature in the reply means accepted, not landed.- Poll
getSignatureStatuseson a Solana RPC, or subscribe withsignatureSubscribe, until the status isconfirmedorfinalized. - Check the
errfield. 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
lastValidBlockHeightfromgetLatestBlockhash. Once the chain passes it, the transaction can never land.
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
confirmedcommitment 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 limitedandbusy. See 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
Before you go live
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
getTipAccountslist - Compute unit limit and price on every transaction
- Blockhash fetched right before signing
- Connection opened and warmed at startup
- Every send confirmed, with the
errfield checked - Backoff on rate limited and busy, rebuild on invalid
- API key in an environment variable or secret store, not in code