Skip to content

Sending Transactions via QUIC

QUIC is a UDP-based transport protocol that eliminates TCP handshake overhead, enabling the fastest possible transaction delivery to Solana validators.

lucum.io endpoints accept QUIC connections directly — connect to any endpoint on port 8888.

When to Use QUIC

Use CaseRecommended Transport
General transactionsHTTP
Latency-sensitive tradingQUIC
Arbitrage / MEVQUIC
High-frequency sendingQUIC
Simple integrationsHTTP

Endpoints

Connect directly to any regional IP on port 8888. Using raw IPs skips DNS resolution for lowest latency.

RegionIP:Port
Frankfurt64.130.40.195:8888
Frankfurt84.32.98.249:8888
Frankfurt70.40.184.133:8888
Amsterdam64.130.42.36:8888
Amsterdam108.61.188.23:8888
New York206.223.233.127:8888
Tokyo198.13.42.17:8888
London64.34.86.109:8888

Tipping Requirement

All transactions must include a transfer of at least 0.001 SOL to one of the tip wallets:

Lucum3sDVsPmHnQVaRKGpLXVPQLhcUqJqmcN5Tn9xuR
Lucum2REE14nX1xBJee9RR24gMaM878icjigfuvWy7H
Lucum2g9HQeHdXEaENapK66C9bgprAMADsg1XijoW2m
Lucum3TJzgBRMZV5CgkmsH6jnE9YKa9ceAQDrUQQuA6
Lucum3TosrLyi8nwP9L9E6s9HWRTg8Y8kv67MjWpkKk
Lucum3yhZeqqXxW3yeTRheBRqwwXnr285HzTiyWKrgm
Lucum4XaQeeARcS4EwmJsGpjNWUNH75hAD2k7jsxSKD
Lucum4r22CCf5M5Zsj4PvhxYJ8CGz4QQMUCrL89Rupz
Lucum5FeurZkc7qrKadaWmzsZ6L1ig79EHGXJU65rPn
Lucum6s8rtKN5n7oWMm1h2Afm18DxWuA8Fgmraikxa3

How It Works

  • Authentication: Your API key is passed via the TLS client certificate's Common Name (CN). You generate a self-signed cert with CN = your_api_key and present it during the QUIC handshake.
  • ALPN protocol: Must be set to lucum-tpu or astralane-tpu.
  • Data format: Send raw bincode-serialized transaction bytes on a unidirectional stream (open_uni). Max 1232 bytes per stream (one transaction per stream).
  • Fire-and-forget: There is no server response. The transaction is forwarded to validators once the bytes are written.
  • Server certificate: The server uses a self-signed certificate — your client must skip server certificate verification.

Connection Error Codes

CodeMeaning
1Unknown API key
2Connection limit reached
3API key expired

Rust

The easiest way to send transactions via QUIC in Rust is to use the Astralane QUIC client, which is fully compatible with lucum.io endpoints.

toml
# Cargo.toml
[dependencies]
astralane-quic-client = { git = "https://github.com/Astralane/astralane-quic-client" }
solana-sdk = "2.0"
tokio = { version = "1", features = ["full"] }
rust
use astralane_quic_client::QuicClient;
use solana_sdk::{
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = "YOUR_API_KEY";
    let endpoint = "64.130.40.195:8888"; // fra.lucum.io

    let client = QuicClient::new(endpoint, api_key).await?;

    // Build transaction
    let sender = Keypair::new();
    let recipient = solana_sdk::pubkey!("RECIPIENT_PUBKEY");
    let tip_wallet = solana_sdk::pubkey!("Lucum3sDVsPmHnQVaRKGpLXVPQLhcUqJqmcN5Tn9xuR");

    let rpc = solana_client::rpc_client::RpcClient::new("https://api.mainnet-beta.solana.com");
    let blockhash = rpc.get_latest_blockhash()?;

    let tx = Transaction::new_signed_with_payer(
        &[
            system_instruction::transfer(&sender.pubkey(), &recipient, 10_000_000),
            system_instruction::transfer(&sender.pubkey(), &tip_wallet, 1_000_000), // 0.001 SOL tip
        ],
        Some(&sender.pubkey()),
        &[&sender],
        blockhash,
    );

    // Send via QUIC (fire-and-forget)
    client.send_transaction(&tx).await?;
    println!("Transaction sent via QUIC");

    Ok(())
}

Manual Implementation

If you prefer to implement the QUIC client yourself:

rust
use quinn::{ClientConfig, Endpoint, TransportConfig};
use rustls::ClientConfig as TlsConfig;
use std::sync::Arc;
use std::time::Duration;
use solana_sdk::{
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};

const LUCUM_QUIC: &str = "64.130.40.195:8888"; // fra.lucum.io
const ALPN: &[u8] = b"lucum-tpu";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = "YOUR_API_KEY";

    // Generate self-signed cert with CN = api_key
    let mut cert_params = rcgen::CertificateParams::new(vec![]);
    cert_params.distinguished_name.push(
        rcgen::DnType::CommonName,
        rcgen::DnValue::Utf8String(api_key.to_string()),
    );
    let cert = rcgen::Certificate::from_params(cert_params)?;
    let cert_der = cert.serialize_der()?;
    let key_der = cert.serialize_private_key_der();

    let client_cert = rustls::Certificate(cert_der);
    let client_key = rustls::PrivateKey(key_der);

    // Build TLS config — skip server verification (self-signed server cert)
    let mut crypto = TlsConfig::builder()
        .with_safe_defaults()
        .with_custom_certificate_verifier(Arc::new(SkipServerVerification))
        .with_client_auth_cert(vec![client_cert], client_key)?;

    crypto.alpn_protocols = vec![ALPN.to_vec()];

    // Transport config with keep-alive
    let mut transport = TransportConfig::default();
    transport.max_idle_timeout(Some(Duration::from_secs(30).try_into()?));
    transport.keep_alive_interval(Some(Duration::from_secs(25)));

    let mut client_config = ClientConfig::new(Arc::new(crypto));
    client_config.transport_config(Arc::new(transport));

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

    let connection = endpoint
        .connect(LUCUM_QUIC.parse()?, "lucum")?
        .await?;

    // Build transaction
    let sender = Keypair::new();
    let recipient = solana_sdk::pubkey!("RECIPIENT_PUBKEY");
    let tip_wallet = solana_sdk::pubkey!("Lucum3sDVsPmHnQVaRKGpLXVPQLhcUqJqmcN5Tn9xuR");

    let rpc = solana_client::rpc_client::RpcClient::new("https://api.mainnet-beta.solana.com");
    let blockhash = rpc.get_latest_blockhash()?;

    let tx = Transaction::new_signed_with_payer(
        &[
            system_instruction::transfer(&sender.pubkey(), &recipient, 10_000_000),
            system_instruction::transfer(&sender.pubkey(), &tip_wallet, 1_000_000),
        ],
        Some(&sender.pubkey()),
        &[&sender],
        blockhash,
    );

    let serialized = bincode::serialize(&tx)?;

    // Send via unidirectional stream (fire-and-forget)
    let mut send_stream = connection.open_uni().await?;
    send_stream.write_all(&serialized).await?;
    send_stream.finish().await?;

    println!("Transaction sent via QUIC");
    Ok(())
}

// Skip server certificate verification (server uses self-signed cert)
struct SkipServerVerification;

impl rustls::client::ServerCertVerifier for SkipServerVerification {
    fn verify_server_cert(
        &self, _: &rustls::Certificate, _: &[rustls::Certificate],
        _: &rustls::ServerName, _: &mut dyn Iterator<Item = &[u8]>,
        _: &[u8], _: std::time::SystemTime,
    ) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
        Ok(rustls::client::ServerCertVerified::assertion())
    }
}

Cargo.toml for manual implementation:

toml
[dependencies]
quinn = "0.11"
rustls = "0.21"
rcgen = "0.11"
tokio = { version = "1", features = ["full"] }
solana-sdk = "2.0"
solana-client = "2.0"
bincode = "1.3"

Go

go
package main

import (
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"fmt"
	"math/big"
	"time"

	"github.com/quic-go/quic-go"
)

const (
	lucumEndpoint = "64.130.40.195:8888" // fra.lucum.io
	apiKey        = "YOUR_API_KEY"
	alpn          = "lucum-tpu"
)

// generateClientCert creates a self-signed cert with CN = apiKey
func generateClientCert(apiKey string) (tls.Certificate, error) {
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return tls.Certificate{}, err
	}

	template := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject:      pkix.Name{CommonName: apiKey},
		NotBefore:    time.Now(),
		NotAfter:     time.Now().Add(365 * 24 * time.Hour),
	}

	certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
	if err != nil {
		return tls.Certificate{}, err
	}

	return tls.Certificate{
		Certificate: [][]byte{certDER},
		PrivateKey:  key,
	}, nil
}

func sendTransaction(serializedTx []byte) error {
	cert, err := generateClientCert(apiKey)
	if err != nil {
		return fmt.Errorf("generate cert: %w", err)
	}

	tlsConfig := &tls.Config{
		Certificates:       []tls.Certificate{cert},
		InsecureSkipVerify: true, // server uses self-signed cert
		NextProtos:         []string{alpn},
	}

	quicConfig := &quic.Config{
		MaxIdleTimeout:  30 * time.Second,
		KeepAlivePeriod: 25 * time.Second,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	conn, err := quic.DialAddr(ctx, lucumEndpoint, tlsConfig, quicConfig)
	if err != nil {
		return fmt.Errorf("dial: %w", err)
	}
	defer conn.CloseWithError(0, "done")

	// Open unidirectional stream and send raw transaction bytes
	stream, err := conn.OpenUniStream()
	if err != nil {
		return fmt.Errorf("open stream: %w", err)
	}

	_, err = stream.Write(serializedTx)
	if err != nil {
		return fmt.Errorf("write: %w", err)
	}

	stream.Close()
	fmt.Println("Transaction sent via QUIC")
	return nil
}

func main() {
	// serializedTx should be your bincode-serialized Solana transaction (max 1232 bytes)
	serializedTx := []byte{ /* your transaction bytes */ }
	if err := sendTransaction(serializedTx); err != nil {
		fmt.Printf("Error: %v\n", err)
	}
}

go.mod dependencies:

require github.com/quic-go/quic-go v0.48.0

Python

python
import asyncio
import ssl
from aioquic.asyncio import connect
from aioquic.quic.configuration import QuicConfiguration
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
import datetime

LUCUM_QUIC_HOST = "64.130.40.195"  # fra.lucum.io
LUCUM_QUIC_PORT = 8888
API_KEY = "YOUR_API_KEY"


def generate_client_cert(api_key: str):
    """Generate a self-signed cert with CN = api_key."""
    key = ec.generate_private_key(ec.SECP256R1())
    subject = x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, api_key),
    ])
    cert = (
        x509.CertificateBuilder()
        .subject_name(subject)
        .issuer_name(subject)
        .public_key(key.public_key())
        .serial_number(x509.random_serial_number())
        .not_valid_before(datetime.datetime.utcnow())
        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
        .sign(key, hashes.SHA256())
    )
    return cert, key


async def send_transaction_quic(serialized_tx: bytes):
    cert, key = generate_client_cert(API_KEY)

    config = QuicConfiguration(
        is_client=True,
        alpn_protocols=["lucum-tpu"],
    )
    config.verify_mode = ssl.CERT_NONE  # server uses self-signed cert

    # Load client cert for authentication
    config.certificate = cert
    config.private_key = key

    async with connect(
        LUCUM_QUIC_HOST,
        LUCUM_QUIC_PORT,
        configuration=config,
    ) as protocol:
        stream_id = protocol._quic.get_next_available_stream_id(is_unidirectional=True)
        protocol._quic.send_stream_data(stream_id, serialized_tx, end_stream=True)
        print("Transaction sent via QUIC")


# Usage:
# asyncio.run(send_transaction_quic(serialized_transaction_bytes))

Tips

  • Use raw IPs to skip DNS resolution for lowest latency.
  • Reuse connections — QUIC connections are persistent. Open one connection and send many transactions on separate streams.
  • Set keep-alive — configure max_idle_timeout(30s) and keep_alive_interval(25s) to prevent the connection from being dropped.
  • Send to multiple endpoints in parallel for higher landing probability.
  • Use the Astralane QUIC client for Rust — it handles all the TLS, ALPN, reconnection, and connection management for you.

Built with VitePress