Send over QUIC

The low latency path. Open one QUIC connection to send.swqos.com:11000, hold it for the life of your process, and open a single bidirectional stream per submission. Identity is established once at handshake, so no send pays for authentication.


Why QUIC is faster here#

Over HTTPS, every submission pays for a connection setup unless your client is careful about pooling, and it re-presents your credential on every request. Over QUIC you connect once. The TLS handshake happens once, your identity is resolved once from the client certificate, and after that a submission is a stream write on a connection that is already open.

Measured relay overhead on this path is 6.29 ms at the median and 32.3 ms at the 99th percentile on production traffic.

The protocol#

protocol
1ALPN ultrasend/1 structured receipts and errors
2 solana-tpu raw compatibility mode, no per-stream reply
3Endpoint send.swqos.com:11000
4Stream one bidirectional stream per submission
5Write the serialized transaction, then finish the send half
6Read one JSON object from the receive half, capped at 4 KiB
7Payload at most 1232 bytes
8Keepalive hold the connection open; do not reconnect per send

Use ultrasend/1 whenever you want a structured receipt or a structured error. It is the branded protocol and it is what every first party client speaks.

Stream lifecycle#

  1. Open one bidirectional stream.
  2. Write the serialized transaction as raw bytes. No JSON wrapper, no base64.
  3. Finish the send half so we know the payload is complete.
  4. Read one JSON object from the receive half. Responses are capped at 4 KiB.

On success

receipt
1{
2 "receipt": {
3 "signature": "5Nx...",
4 "accepted": true,
5 "duplicate": false,
6 "charged_lamports": 200000,
7 "balance_remaining_lamports": 49800000
8 }
9}

On failure

error
1{
2 "error": {
3 "code": "INSUFFICIENT_BALANCE",
4 "message": "insufficient prepaid balance"
5 }
6}

The error codes are the same ones the HTTPS path returns, and they are all listed in errors. The send and stream timeout is 1000 ms.

A client in every language#

There is no bearer header on this path. The client derives an Ed25519 keypair from your API key and presents a self-signed certificate carrying that key, which is the same convention Solana’s own TPU clients use. Each program below connects once, then opens one stream per submission.

send.ts
1import { Agent } from "undici";
2
3// Node's QUIC support is still experimental, so the fastest reliable path from
4// Node today is HTTP/1.1 with a warm keep-alive pool rather than raw QUIC. This
5// removes the TCP and TLS handshake from every send, which is most of what the
6// QUIC path buys you.
7//
8// If you want true QUIC from a JS runtime, run the Rust or Go client above as a
9// sidecar and talk to it over a local socket.
10
11const agent = new Agent({
12 keepAliveTimeout: 60_000,
13 keepAliveMaxTimeout: 300_000,
14 connections: 4, // a small warm pool, mirroring the QUIC guidance
15 pipelining: 1,
16});
17
18const API_KEY = process.env.SWQOS_API_KEY!;
19
20export async function send(tx: Uint8Array) {
21 const res = await fetch("https://send.swqos.com/v1/transactions", {
22 method: "POST",
23 headers: {
24 Authorization: `Bearer ${API_KEY}`,
25 "Content-Type": "application/json",
26 },
27 body: JSON.stringify({
28 transaction: Buffer.from(tx).toString("base64"),
29 }),
30 // @ts-expect-error undici dispatcher is not in the DOM fetch types
31 dispatcher: agent,
32 });
33
34 if (!res.ok) {
35 const { error } = await res.json();
36 throw new Error(`${error.code}: ${error.message}`);
37 }
38 return res.json();
39}
Node’s QUIC support is still experimental, so the TypeScript sample uses a warm keep-alive connection pool over HTTPS instead. That removes the same per-send handshake cost, and it is the fastest reliable path from Node today. If you want true QUIC from a JavaScript runtime, run the Rust or Go client as a sidecar and talk to it over a local socket.

Compatibility mode#

We also accept solana-tpu, which takes raw unidirectional transaction streams for clients built against the standard Solana TPU protocol. There is no per-stream response in this mode, so you get no receipt and no error detail. Failures surface as connection close codes instead.

Close codeMeaning
0x100Unauthorized
0x101Account disabled
0x102Insufficient balance

Use compatibility mode only when you cannot change the client. If you can, ultrasend/1 gives you a receipt with the charge and your remaining balance, which is worth having.

The mistake that costs you everything#

Do not open a new connection per transaction. Reconnecting for every send pays the handshake every time and throws away the entire reason to use QUIC. Connect once at startup, hold the connection, and open a stream per submission.

Keepalive is 25 seconds and the maximum idle timeout is five minutes, so a connection survives quiet periods without any work on your side. If your process is long lived, so is the connection.

If your transactions are bursty and you want headroom, open a small pool of connections at startup and round robin across them. Even two is enough to keep one warm while another is in use.