in-solana by Supanode

Sending Transactions Directly to the Leader

TL;DR

  • - Direct TPU sending skips the RPC node hop, reducing latency by 10–30ms
  • - Requires a QUIC connection to the leader validator's TPU port
  • - Send to both current and next leader for redundancy

When you call sendTransaction through an RPC node, the node forwards your transaction to the current leader validator via its TPU (Transaction Processing Unit). Direct TPU sending skips the RPC node and connects to the leader directly, cutting one network hop from the path.

Standard vs. direct sending

Standard:  Your App → RPC Node → Leader TPU → Processed
Direct:    Your App → Leader TPU → Processed

The RPC hop adds 10–30ms of latency. For time-sensitive applications (trading bots, liquidations), this matters.

How TPU sending works

1. Get the leader schedule

const currentSlot = await connection.getSlot();
const leaders = await connection.getSlotLeaders(currentSlot, 4);

The leader schedule tells you which validator produces blocks for upcoming slots.

2. Resolve leader TPU addresses

Use getClusterNodes to map validator identity keys to their TPU addresses:

const clusterNodes = await connection.getClusterNodes();
const nodeMap = new Map(
  clusterNodes.map((node) => [node.pubkey, node.tpuQuic]),
);

const leaderTPU = nodeMap.get(leaders[0].toBase58());
// Example: "123.45.67.89:8009"

The tpuQuic field is the QUIC endpoint. Older nodes may only expose tpu (UDP).

3. Send via QUIC

QUIC is Solana’s transport protocol for transaction submission. It provides:

  • Reliable delivery (unlike raw UDP).
  • Connection-level authentication (for SWQoS).
  • Multiplexed streams.

Sending via QUIC requires a QUIC client library. In Rust:

use solana_quic_client::QuicClient;

let client = QuicClient::new(leader_tpu_addr);
let wire_tx = bincode::serialize(&transaction)?;
client.send_transaction(&wire_tx).await?;

In practice, most developers use the Solana SDK’s built-in TPU client or a library that handles QUIC connection management.

4. Send to multiple leaders

For highest landing probability, send to both the current and next leader:

const [currentLeader, nextLeader] = leaders;
await Promise.all([
  sendToTPU(nodeMap.get(currentLeader), signedTx),
  sendToTPU(nodeMap.get(nextLeader), signedTx),
]);

If the current leader’s slot ends before processing your transaction, the next leader already has it.

Using the TPU client

The Solana SDK provides a TpuClient that handles leader resolution and multi-leader sending:

use solana_tpu_client::tpu_client::TpuClient;

let tpu_client = TpuClient::new(
    rpc_client.clone(),
    websocket_url,
    TpuClientConfig::default(),
).await?;

tpu_client.send_transaction(&transaction);

The TPU client:

  • Monitors the leader schedule automatically.
  • Maintains QUIC connections to upcoming leaders.
  • Sends to multiple leaders for redundancy.
  • Handles connection lifecycle (reconnection, cleanup).

SWQoS and direct sending

Direct TPU sending benefits from Stake-Weighted QoS only if you connect with a staked identity. An unstaked QUIC connection uses the 20% unstaked pool, same as going through an unstaked RPC node.

To benefit from both direct sending and SWQoS:

  • Run a staked RPC node and send through its TPU client.
  • Or use a provider that offers staked TPU forwarding.

Limitations

  1. Leader TPU addresses change. The leader schedule rotates every ~1.6 seconds (4 slots). You must track the schedule and update connections.
  2. QUIC connection overhead. Establishing a new QUIC connection takes 1–2 round trips. Keep connections warm to avoid this cost.
  3. No response. TPU sends are fire-and-forget. You do not get a success/failure response. Use signatureSubscribe or polling to track confirmation.
  4. Validator firewall. Some validators may rate-limit or block unknown QUIC connections.

When to use direct TPU sending

Use caseDirect TPUsendTransaction RPC
Trading botsYesBackup
Standard dAppsUsually not neededYes
High-frequency operationsYesNo
Development/testingNoYes

For most applications, sendTransaction through an RPC node with staked connections is sufficient. Direct TPU sending is an optimization for latency-critical applications.

FAQ

Can I use direct TPU sending from a browser? No. Browser JavaScript cannot open raw QUIC connections. Use a backend service for TPU sending and expose it via an API to your frontend.

Do I need a staked identity for direct TPU sending? No, but without stake your connection uses the unstaked pool (20% capacity), which congests during peaks. A staked identity gets dedicated bandwidth proportional to stake.

How do I know if my transaction was received? You do not get an acknowledgment from the TPU. Monitor the transaction signature via signatureSubscribe or getSignatureStatuses polling.

Is UDP TPU still supported? QUIC is the default and required transport. UDP was deprecated. Use QUIC for all new implementations.

Updated: