Skip to content
A direct pathQUIC goes straight to the relay over UDP on port 4433: the shortest, most direct path to the slot.QUICNSLOT N
QUIC goes straight to the relay over UDP on port 4433: the shortest, most direct path to the slot.

Sending via QUIC

QUIC provides ultra-low latency through:

  • Persistent Connections: Connection established once, reused for all transactions
  • Connection Multiplexing: Multiple streams over a single connection
  • No Head-of-Line Blocking: Independent stream processing
  • Built-in TLS 1.3: Encrypted by default
  • 50-80% Latency Reduction compared to HTTP/2

QUIC Endpoint

Connect to the region host on port 4433. QUIC runs over UDP: your client resolves the name once when it opens the connection, then reuses that connection for every transaction.

fra.fastrelay.sh:4433   # Frankfurt (Primary)
ams.fastrelay.sh:4433   # Amsterdam
ny.fastrelay.sh:4433    # New York
tyo.fastrelay.sh:4433   # Tokyo

TLS Configuration

The QUIC server uses standard TLS 1.3. Clients should:

  • Skip server certificate verification (or use system root certs)
  • No ALPN required — do not set any ALPN protocols
  • No client certificate required — anonymous connections accepted
  • API key is optional — sent via the stream header, not TLS
rust
// Quinn / rustls 0.23 — minimal QUIC client config
let crypto = rustls::ClientConfig::builder()
    .dangerous()
    .with_custom_certificate_verifier(SkipServerVerification::new())
    .with_no_client_auth();

let client_config = quinn::ClientConfig::new(Arc::new(
    QuicClientConfig::try_from(crypto).unwrap()
));

Stream Protocol

Each transaction uses a bidirectional QUIC stream:

  1. Client sends API key header (optional — send \n if no key):

    api-key: YOUR_API_KEY\n
  2. Client sends JSON payload:

    json
    {"tx": "BASE64_TRANSACTION", "simulate": false}
  3. Client closes write side (signals end of request)

  4. Server responds:

    json
    {
      "status": "accepted",
      "request_id": "550e8400-e29b-41d4-a716-446655440000",
      "signature": "5wH...txSig"
    }

Bundle frames use the same stream protocol — send txs (an array) instead of tx in step 2:

json
{"txs": ["BASE64_TX_1", "BASE64_TX_2"], "id": "b1"}

The response carries the Jito bundle id instead of a single signature:

json
{"status": "accepted", "bundle_id": "jito-uuid", "signatures": ["sig1", "sig2"], "id": "b1"}

See Atomic Bundles (Jito) for tip requirements and landing semantics.

Rust Example (Quinn 0.11 + rustls 0.23)

rust
use quinn::{Endpoint, crypto::rustls::QuicClientConfig};
use std::sync::Arc;

// Skip server cert verification (relay uses Let's Encrypt)
struct SkipVerify;
impl rustls::client::danger::ServerCertVerifier for SkipVerify {
    fn verify_server_cert(&self, _: &rustls::pki_types::CertificateDer, _: &[rustls::pki_types::CertificateDer], _: &rustls::pki_types::ServerName, _: &[u8], _: rustls::pki_types::UnixTime) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> { Ok(rustls::client::danger::ServerCertVerified::assertion()) }
    fn verify_tls12_signature(&self, _: &[u8], _: &rustls::pki_types::CertificateDer, _: &rustls::DigitallySignedStruct) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> { Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) }
    fn verify_tls13_signature(&self, _: &[u8], _: &rustls::pki_types::CertificateDer, _: &rustls::DigitallySignedStruct) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> { Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) }
    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> { rustls::crypto::ring::default_provider().signature_verification_algorithms.supported_schemes() }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Configure QUIC client — no ALPN, no client cert, skip server verify
    let crypto = rustls::ClientConfig::builder()
        .dangerous()
        .with_custom_certificate_verifier(Arc::new(SkipVerify))
        .with_no_client_auth();

    let client_config = quinn::ClientConfig::new(Arc::new(
        QuicClientConfig::try_from(crypto)?
    ));

    let mut endpoint = Endpoint::client("0.0.0.0:0".parse()?)?;
    endpoint.set_default_client_config(client_config);

    // Resolve the region host, then connect (the connection persists — reuse it for all transactions)
    let addr = tokio::net::lookup_host("fra.fastrelay.sh:4433").await?.next().unwrap();
    let conn = endpoint
        .connect(addr, "fra.fastrelay.sh")?
        .await?;

    // Open bidirectional stream per transaction
    let (mut send, mut recv) = conn.open_bi().await?;

    // Send header (API key optional — just send newline if none)
    send.write_all(b"\n").await?;

    // Send transaction
    let payload = serde_json::json!({
        "tx": "BASE64_TRANSACTION",
        "simulate": false
    });
    send.write_all(&serde_json::to_vec(&payload)?).await?;
    send.finish()?;

    // Read response
    let response = recv.read_to_end(4096).await?;
    let result: serde_json::Value = serde_json::from_slice(&response)?;
    println!("Response: {}", result);

    Ok(())
}

Python Example (aioquic)

python
import asyncio
import json
from aioquic.asyncio import connect
from aioquic.quic.configuration import QuicConfiguration

async def send_transaction(api_key: str, tx_base64: str):
    configuration = QuicConfiguration(is_client=True)
    configuration.verify_mode = True

    async with connect(
        "fra.fastrelay.sh",  # Frankfurt
        4433,
        configuration=configuration,
    ) as client:
        # Open stream
        stream_id = client._quic.get_next_available_stream_id()

        # Send API key
        client._quic.send_stream_data(
            stream_id,
            f"api-key: {api_key}\n".encode()
        )

        # Send transaction
        payload = json.dumps({
            "tx": tx_base64,
            "simulate": False
        })
        client._quic.send_stream_data(stream_id, payload.encode(), end_stream=True)

        # Read response
        response_data = await client._quic.receive_stream_data(stream_id)
        response = json.loads(response_data.decode())
        print("Response:", response)

asyncio.run(send_transaction("YOUR_API_KEY", "BASE64_TX"))

Node.js Note

Node.js QUIC support is experimental. Rust is recommended for QUIC integration, or use the HTTPS endpoint for Node.js applications.