Features Endpoints Pricing Staking Integration Docs Login Profile

SWQoS Documentation

High-speed Solana transaction landing service

Contact Us

Reach out to us on Telegram:

@speedlanding

Regional Endpoints

For QUIC connections, append :17778 to the regional domain. HTTP endpoints use port 80.

πŸ‡©πŸ‡ͺ Frankfurt (FRA)

fra.speedlanding.trade

πŸ‡³πŸ‡± Amsterdam (AMS)

ams.speedlanding.trade

πŸ‡ΊπŸ‡Έ New York (NY)

ny.speedlanding.trade

πŸ‡―πŸ‡΅ Tokyo (TYO)

tyo.speedlanding.trade

πŸ‡¬πŸ‡§ London (LON)

lon.speedlanding.trade

πŸ‡ΈπŸ‡¬ Singapore (SGP)

sgp.speedlanding.trade

Tip Accounts

Support the following Solana addresses for tip payments:

Minimum Tip

Minimum tip amount required for transactions:

0.001 SOL (1_000_000 Lamports)

HTTP API

Use JSON-RPC transaction submission over HTTP/1.1 on port 80. Replace tyo with fra, ams, ny, or lon to select a region.

Routes
POST http://tyo.speedlanding.trade/sendTx  JSON-RPC sendTransaction
GET  http://tyo.speedlanding.trade/ping    Health check and keepalive

Authentication

Pass a base58-encoded Solana keypair secret as ?apikey=<BASE58_KEYPAIR_SECRET>. The legacy api_key spelling is also supported. This is not a public key.

JSON-RPC submission

send-transaction.sh
curl --http1.1 -X POST \
  "http://tyo.speedlanding.trade/sendTx?apikey=$APIKEY_BASE58" \
  -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":["<BASE64_TRANSACTION>",{"encoding":"base64"}]}'

Keepalive and /ping

Reuse one HTTP/1.1 client with Connection: keep-alive. The public Nginx proxy closes an idle client connection after 75 seconds; the backend timeout is 90 seconds. Send GET /ping every 30 seconds (and no less frequently than every 60 seconds) to keep the connection active.

keepalive.js
const http = require("http");
const agent = new http.Agent({ keepAlive: true, keepAliveMsecs: 30_000, maxSockets: 1 });
setInterval(() => {
  http.get("http://tyo.speedlanding.trade/ping", {agent}, (res) => res.resume()).on("error", console.error);
}, 30_000);

Binary API

Submit raw signed Solana transaction bytes directly. The request body is binary data, not JSON or base64.

Route
POST http://tyo.speedlanding.trade/v1/sendTx

Authentication

Use the apikey header with a base58-encoded Solana keypair secret. Query parameters are supported for compatibility, but headers avoid exposing keys in URLs and logs.

send-binary.sh
curl --http1.1 -X POST \
  "http://tyo.speedlanding.trade/v1/sendTx" \
  -H "Content-Type: application/octet-stream" \
  -H "apikey: $APIKEY_BASE58" \
  --data-binary "@transaction.bin"

A successful submission returns a JSON-RPC-shaped response when the transaction enters the sending path:

{"jsonrpc":"2.0","id":null,"result":"<transaction_signature>"}

Common responses are 400 for invalid transaction data, 401 for missing or invalid API keys, 413 for oversized payloads, and 503 when submission is unavailable.

QUIC API - Rust Client

speed_client.rs
use anyhow::Context as _;
use arc_swap::ArcSwap;
use quinn::{
    crypto::rustls::QuicClientConfig, ClientConfig, Connection, Endpoint, IdleTimeout,
    TransportConfig,
};
use solana_sdk::{signature::Keypair, transaction::VersionedTransaction};
use solana_tls_utils::{new_dummy_x509_certificate, SkipServerVerification};
use std::{
    net::{SocketAddr, ToSocketAddrs as _},
    sync::Arc,
    time::Duration,
};
use tokio::sync::Mutex;
use tracing::warn;

const ALPN_TPU_PROTOCOL_ID: &[u8] = b"solana-tpu";
const SPEED_SERVER: &str = "speed-landing";
const KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(25);
const MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(5 * 60);

pub struct SpeedClient {
    endpoint: Endpoint,
    client_config: ClientConfig,
    addr: SocketAddr,
    connection: ArcSwap<Connection>,
    reconnect: Mutex<()>,
}

impl SpeedClient {
    pub async fn connect(endpoint_addr: &str, api_key: &str) -> anyhow::Result<Self> {
        let keypair = Keypair::from_base58_string(api_key);
        let (cert, key) = new_dummy_x509_certificate(&keypair);
        let mut crypto = rustls::ClientConfig::builder()
            .dangerous()
            .with_custom_certificate_verifier(SkipServerVerification::new())
            .with_client_auth_cert(vec![cert], key)
            .context("failed to configure client certificate")?;

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

        let client_crypto = QuicClientConfig::try_from(crypto)
            .context("failed to convert rustls config into quinn crypto config")?;
        let mut client_config = ClientConfig::new(Arc::new(client_crypto));
        let mut transport = TransportConfig::default();
        transport.keep_alive_interval(Some(KEEP_ALIVE_INTERVAL));
        transport.max_idle_timeout(Some(IdleTimeout::try_from(MAX_IDLE_TIMEOUT)?));
        client_config.transport_config(Arc::new(transport));

        let mut endpoint = Endpoint::client("0.0.0.0:0".parse()?)?;
        endpoint.set_default_client_config(client_config.clone());
        let addr = endpoint_addr
            .to_socket_addrs()?
            .next()
            .ok_or_else(|| anyhow::anyhow!("Address not resolved"))?;
        let connection = endpoint.connect(addr, SPEED_SERVER)?.await?;

        Ok(Self {
            endpoint,
            client_config,
            addr,
            connection: ArcSwap::from_pointee(connection),
            reconnect: Mutex::new(()),
        })
    }

    pub async fn send_transaction(&self, transaction: &VersionedTransaction) -> anyhow::Result<()> {
        let serialized_tx = bincode::serialize(transaction)?;
        let connection = self.connection.load_full();
        if Self::try_send_bytes(&connection, &serialized_tx)
            .await
            .is_ok()
        {
            return Ok(());
        }
        warn!("Failed to send paylod to Speed QUIC; reconnecting",);
        self.reconnect().await?;
        let connection = self.connection.load_full();
        Self::try_send_bytes(&connection, &serialized_tx).await
    }

    async fn reconnect(&self) -> anyhow::Result<()> {
        let _guard = self.reconnect.try_lock()?;
        let connection = self
            .endpoint
            .connect_with(self.client_config.clone(), self.addr, SPEED_SERVER)?
            .await?;
        self.connection.store(Arc::new(connection));
        Ok(())
    }

    async fn try_send_bytes(connection: &Connection, payload: &[u8]) -> anyhow::Result<()> {
        let mut stream = connection.open_uni().await?;
        stream.write_all(payload).await?;
        stream.finish()?;
        Ok(())
    }
}
Back to Home