SlotBolt / Developer Docs

The fast path, documented.

Set up Shred Stream, submit transactions through regional Landing Service endpoints, or request a managed Dedicated Node. Every access flow starts in SlotBolt Discord.

01 / Landing Service

Send straight into the action.

Use one API key across Frankfurt, Amsterdam, New York, and Tokyo. Create it in Discord.

Pay as you go · 3 TPS Start · 40 TPS · 700 USDC/30 days Advanced · 100 TPS · 1,750 USDC/30 days Enterprise · Custom · contact us on Discord

Your free API key starts at 3 TPS in each region. There is no plan payment for this tier; each submitted transaction still needs the required Tip.

Manage your API key and TPS plan

01Create your API key

Create your API key in SlotBolt Discord.

02Choose a paid tier

Select 40 or 100 TPS. A private order shows the exact USDC amount, wallet, unique payment reference, and quote expiry.

03Submit the TXID

Pay before the quote expires, then submit the transaction ID in Enter TXID. The same key gains the new TPS across all four regions.

04Renew or upgrade

Return to the same panel to renew your current tier or upgrade from 40 to 100 TPS. The order quotes unused prepaid credit.

Required: include a top-level System Program transfer of at least 0.1 SOL to one approved SlotBolt Tip Account in every signed transaction.
Pay Only on Landing
The tip lives inside your transaction.
SystemProgram.transfer({
  fromPubkey: payer.publicKey,
  toPubkey: new PublicKey("5kw4XdUjk7k9rPH8cTNFqgtpnwhWekVAXEKB2medmoq"), // example; randomly select from the approved Tip Accounts below
  lamports: 100_000_000 // 0.1 SOL
})

Priority Fee and Tip guidance

Suggested: Priority Fee ≥ 0.01 SOL and Tip ≥ 0.1 SOL may improve inclusion chances; results depend on validators and network conditions.

Configure Priority Fee and Tip for current network conditions. Actual inclusion depends on validators and network conditions.

Approved Tip Accounts

Tip selection: Choose one approved Tip Account at random or in round-robin order for each transaction. Spreading writes across the accounts reduces write lock conflicts and can improve landing rates.
20 approved Tip Accounts
  1. 5kw4XdUjk7k9rPH8cTNFqgtpnwhWekVAXEKB2medmoq
  2. 4tgpD2mKc6ojaGdJhxvjvo4fRDRJCQUimcvt9SX842Qt
  3. GN1BZUWcoGLStou1M9RD98c9b7JdoXZoTTZR9a6WkdaT
  4. 3uANArQUJDsaBqFVNCP9C5HZV9YSU7b2upRH3d9pNHtM
  5. 4A68gTsn6pExwmEAn9CVdJxFvVxLdYertG83SR2BYmtD
  6. CinsfRDHjvyfKJjwp8R9TJRCkoP6SGM2eHKJiWyehpP7
  7. EKb54KnRaXxQaaDLqDSvGvRL76whuf5CKtum1qSCSMnk
  8. AsMLHiUCZoojNF1b6sDRPKrbc6RkeDHPZyGPa5hK5ToY
  9. GnPxqoD63TXYrTsXZNsxyt2iZaperxTUz344KUHgBJLJ
  10. HiKfsAEYBL21cZSmhLjV5VEcgjFH5yzGGXXDQcVTztKS
  11. 4TTTLWoJNMAHWyFVUDfXasZLfXTt54h4zVpE6wYXa2fH
  12. J8PJG3HQfwxw9yG5w7DzuwnfadAfaikyGyDSLkt2cpGi
  13. 8qKJzmNeckLTEHnNdmN198quQ1omU2FshgSLYQeB3oww
  14. 7yQ4me6yFn6LDX9jAZ4Sy3jdg6m15uJM15tB1EBC3Jbz
  15. 9qc8oYmvSyxaruvnqReY8WXk1ua9r4gRvqKQpEGEVaWc
  16. HPbzYkQ8pkC9UyBEWMtLopQGLxUzpzboXRbrqn9Ue9kg
  17. GQVb9JkQGzQfkXZAbMS66p7dUs7BKbJRD5soSX8m66MW
  18. 3jShf4bRzuXPsHtkAAwwvAPCt4H4sEcncKPjo1jZGdWg
  19. F2378NNuCQMng3kDU4exgDoZ6LCSfHS2o2pRH2jXYWjE
  20. 82yeCQBs34DJDfj3ybwrbfUDmpdHQpxMNvoRdnhRmw7E

Regional HTTP endpoints

Frankfurthttp://fra.slotbolt.trade:1234/submit
Amsterdamhttp://ams.slotbolt.trade:1234/submit
New Yorkhttp://ny.slotbolt.trade:1234/submit
Tokyohttp://tokyo.slotbolt.trade:1234/submit

Append your API key as the api_key query parameter. Submit a signed, base64 encoded transaction with the JSON-RPC 2.0 sendTransaction method:

The current endpoint accepts legacy and v0 signed transactions. Version 1 is not supported.

POST http://fra.slotbolt.trade:1234/submit?api_key=<YOUR_API_KEY>
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "sendTransaction",
  "params": ["<SIGNED_TRANSACTION_BASE64>", {"encoding": "base64"}]
}

A valid submission returns HTTP 200 with the first signature from the signed transaction and your original request ID:

{
  "jsonrpc": "2.0",
  "result": "<FIRST_TRANSACTION_SIGNATURE>",
  "id": 1
}

QUIC Access

In development. Please use JSON-RPC for now.

Keep Alive

Request the matching regional /ping endpoint every 30 seconds with the same api_key. Once the key is accepted, each /submit or /ping request consumes one regional TPS slot, including submits rejected for invalid parameters, transaction parsing, or Tip. Each region counts missing or invalid keys on /ping, /submit, and legacy POST / together by connecting IP. Before an IP block, these requests return HTTP 401; the threshold request or an existing connection may receive HTTP 403. Requests over the TPS limit return HTTP 429.

GET http://fra.slotbolt.trade:1234/ping?api_key=<YOUR_API_KEY>

Error codes

Submit errors use a JSON-RPC 2.0 error object with the original request ID when available. /ping returns the HTTP status without a JSON body.

HTTPJSON-RPC codeMeaning
400-32700The request body is not valid JSON.
400-32600Missing or invalid JSON-RPC version, method field missing or not a string, or missing or invalid request ID.
400-32601Unsupported JSON-RPC method.
400-32602Invalid parameters or signed transaction; a missing approved Tip also fails here.
401-32001Missing, invalid, or revoked API key.
403-32004Access denied. Contact SlotBolt Support for help.
413-32600Request body is too large.
429-32002API key TPS limit exceeded. Keep Alive shares the same allowance.
503-32003Transaction logging is unavailable; retry later.
503-32005Access check in progress; retry shortly.

Code examples

Node.js 20+ · one Tip per transaction, sign, submit and keepalive
npm install @solana/web3.js@1

landing.mjs

// Save as landing.mjs. Send one JSON line per transaction on stdin.
import { readFileSync } from "node:fs";
import { createInterface } from "node:readline";
import {
  ComputeBudgetProgram, Keypair, PublicKey, SystemProgram,
  TransactionMessage, VersionedTransaction
} from "@solana/web3.js";

const region = process.env.SLOTBOLT_REGION;
if (!["fra", "ams", "ny", "tokyo"].includes(region)) throw new Error("Invalid region");
const key = process.env.SLOTBOLT_API_KEY;
const keypairFile = process.env.SLOTBOLT_KEYPAIR_FILE;
if (!key || !keypairFile) throw new Error("Set SLOTBOLT_API_KEY and SLOTBOLT_KEYPAIR_FILE");
const payer = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(readFileSync(keypairFile, "utf8"))));
const tipAccounts = [
  "5kw4XdUjk7k9rPH8cTNFqgtpnwhWekVAXEKB2medmoq",
  "4tgpD2mKc6ojaGdJhxvjvo4fRDRJCQUimcvt9SX842Qt",
  "GN1BZUWcoGLStou1M9RD98c9b7JdoXZoTTZR9a6WkdaT",
  "3uANArQUJDsaBqFVNCP9C5HZV9YSU7b2upRH3d9pNHtM",
  "4A68gTsn6pExwmEAn9CVdJxFvVxLdYertG83SR2BYmtD",
  "CinsfRDHjvyfKJjwp8R9TJRCkoP6SGM2eHKJiWyehpP7",
  "EKb54KnRaXxQaaDLqDSvGvRL76whuf5CKtum1qSCSMnk",
  "AsMLHiUCZoojNF1b6sDRPKrbc6RkeDHPZyGPa5hK5ToY",
  "GnPxqoD63TXYrTsXZNsxyt2iZaperxTUz344KUHgBJLJ",
  "HiKfsAEYBL21cZSmhLjV5VEcgjFH5yzGGXXDQcVTztKS",
  "4TTTLWoJNMAHWyFVUDfXasZLfXTt54h4zVpE6wYXa2fH",
  "J8PJG3HQfwxw9yG5w7DzuwnfadAfaikyGyDSLkt2cpGi",
  "8qKJzmNeckLTEHnNdmN198quQ1omU2FshgSLYQeB3oww",
  "7yQ4me6yFn6LDX9jAZ4Sy3jdg6m15uJM15tB1EBC3Jbz",
  "9qc8oYmvSyxaruvnqReY8WXk1ua9r4gRvqKQpEGEVaWc",
  "HPbzYkQ8pkC9UyBEWMtLopQGLxUzpzboXRbrqn9Ue9kg",
  "GQVb9JkQGzQfkXZAbMS66p7dUs7BKbJRD5soSX8m66MW",
  "3jShf4bRzuXPsHtkAAwwvAPCt4H4sEcncKPjo1jZGdWg",
  "F2378NNuCQMng3kDU4exgDoZ6LCSfHS2o2pRH2jXYWjE",
  "82yeCQBs34DJDfj3ybwrbfUDmpdHQpxMNvoRdnhRmw7E"
].map((address) => new PublicKey(address));
let nextTipIndex = payer.publicKey.toBytes()[0] % tipAccounts.length;
const base = "http://" + region + ".slotbolt.trade:1234";
const query = new URLSearchParams({ api_key: key });

async function ping() {
  const response = await fetch(base + "/ping?" + query);
  if (!response.ok) throw new Error("Ping failed: HTTP " + response.status);
}
setInterval(() => { ping().catch(console.error); }, 30_000);

const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
for await (const line of lines) {
  if (!line.trim()) continue;
  const { recipient, lamports, recentBlockhash } = JSON.parse(line);
  if (!Number.isSafeInteger(lamports) || lamports <= 0) throw new Error("Invalid lamports");
  // Select one Tip Account for this input line; submit one transaction below.
  const tipAccount = tipAccounts[nextTipIndex];
  nextTipIndex = (nextTipIndex + 1) % tipAccounts.length;
  const message = new TransactionMessage({
    payerKey: payer.publicKey,
    recentBlockhash,
    instructions: [
      ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
      ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 60_000_000 }),
      SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: new PublicKey(recipient), lamports }),
      SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: tipAccount, lamports: 110_000_000 })
    ]
  }).compileToV0Message();
  const tx = new VersionedTransaction(message);
  tx.sign([payer]);
  const signedTx = Buffer.from(tx.serialize()).toString("base64");
  const response = await fetch(base + "/submit?" + query, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      jsonrpc: "2.0", id: 1, method: "sendTransaction",
      params: [signedTx, { encoding: "base64" }]
    })
  });
  const reply = await response.json().catch(() => null);
  if (reply?.error) throw new Error("SlotBolt JSON-RPC " + reply.error.code + ": " + reply.error.message);
  if (!response.ok) throw new Error("Submit failed: HTTP " + response.status);
  if (!reply) throw new Error("Submit returned no JSON-RPC body");
  console.log("Accepted by SlotBolt API:", reply.result);
}
Rust · one Tip per transaction, sign, submit and keepalive

Cargo.toml

[dependencies]
tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread", "time", "io-std", "io-util"] }
reqwest = { version = "0.11.27", default-features = false, features = ["json", "rustls-tls"] }
serde_json = "1.0.140"
solana-sdk = "=2.2.1"
base64 = "0.22.1"
bincode = "1.3.3"

src/main.rs

use std::{env, str::FromStr, time::Duration};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use serde_json::{json, Value};
use solana_sdk::{
    compute_budget::ComputeBudgetInstruction,
    hash::Hash,
    message::{v0, VersionedMessage},
    pubkey::Pubkey,
    signature::{read_keypair_file, Signer},
    system_instruction,
    transaction::VersionedTransaction,
};
use tokio::io::{self, AsyncBufReadExt, BufReader};

const TIP_ACCOUNTS: [&str; 20] = [
    "5kw4XdUjk7k9rPH8cTNFqgtpnwhWekVAXEKB2medmoq",
    "4tgpD2mKc6ojaGdJhxvjvo4fRDRJCQUimcvt9SX842Qt",
    "GN1BZUWcoGLStou1M9RD98c9b7JdoXZoTTZR9a6WkdaT",
    "3uANArQUJDsaBqFVNCP9C5HZV9YSU7b2upRH3d9pNHtM",
    "4A68gTsn6pExwmEAn9CVdJxFvVxLdYertG83SR2BYmtD",
    "CinsfRDHjvyfKJjwp8R9TJRCkoP6SGM2eHKJiWyehpP7",
    "EKb54KnRaXxQaaDLqDSvGvRL76whuf5CKtum1qSCSMnk",
    "AsMLHiUCZoojNF1b6sDRPKrbc6RkeDHPZyGPa5hK5ToY",
    "GnPxqoD63TXYrTsXZNsxyt2iZaperxTUz344KUHgBJLJ",
    "HiKfsAEYBL21cZSmhLjV5VEcgjFH5yzGGXXDQcVTztKS",
    "4TTTLWoJNMAHWyFVUDfXasZLfXTt54h4zVpE6wYXa2fH",
    "J8PJG3HQfwxw9yG5w7DzuwnfadAfaikyGyDSLkt2cpGi",
    "8qKJzmNeckLTEHnNdmN198quQ1omU2FshgSLYQeB3oww",
    "7yQ4me6yFn6LDX9jAZ4Sy3jdg6m15uJM15tB1EBC3Jbz",
    "9qc8oYmvSyxaruvnqReY8WXk1ua9r4gRvqKQpEGEVaWc",
    "HPbzYkQ8pkC9UyBEWMtLopQGLxUzpzboXRbrqn9Ue9kg",
    "GQVb9JkQGzQfkXZAbMS66p7dUs7BKbJRD5soSX8m66MW",
    "3jShf4bRzuXPsHtkAAwwvAPCt4H4sEcncKPjo1jZGdWg",
    "F2378NNuCQMng3kDU4exgDoZ6LCSfHS2o2pRH2jXYWjE",
    "82yeCQBs34DJDfj3ybwrbfUDmpdHQpxMNvoRdnhRmw7E"
];

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let region = env::var("SLOTBOLT_REGION")?;
    if !matches!(region.as_str(), "fra" | "ams" | "ny" | "tokyo") {
        return Err("invalid region".into());
    }
    let key = env::var("SLOTBOLT_API_KEY")?;
    let payer = read_keypair_file(env::var("SLOTBOLT_KEYPAIR_FILE")?)?;
    let mut next_tip_index = payer.pubkey().to_bytes()[0] as usize % TIP_ACCOUNTS.len();
    let base = format!("http://{region}.slotbolt.trade:1234");
    let client = reqwest::Client::builder().timeout(Duration::from_secs(5)).build()?;
    let ping_client = client.clone();
    let ping_key = key.clone();
    let ping_url = format!("{base}/ping");
    tokio::spawn(async move {
        let mut cadence = tokio::time::interval(Duration::from_secs(30));
        cadence.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
        loop {
            cadence.tick().await;
            match ping_client.get(&ping_url).query(&[("api_key", &ping_key)]).send().await {
                Ok(response) if response.status().is_success() => {}
                Ok(response) => eprintln!("Ping failed: HTTP {}", response.status()),
                Err(error) => eprintln!("Ping failed: {error}"),
            }
        }
    });

    let mut lines = BufReader::new(io::stdin()).lines();
    while let Some(line) = lines.next_line().await? {
        if line.trim().is_empty() { continue; }
        let trade: Value = serde_json::from_str(&line)?;
        let recipient = Pubkey::from_str(trade["recipient"].as_str().ok_or("missing recipient")?)?;
        let lamports = trade["lamports"].as_u64().filter(|value| *value > 0).ok_or("invalid lamports")?;
        let blockhash = Hash::from_str(trade["recentBlockhash"].as_str().ok_or("missing recentBlockhash")?)?;
        // Select one Tip Account for this input line; submit one transaction below.
        let tip = Pubkey::from_str(TIP_ACCOUNTS[next_tip_index])?;
        next_tip_index = (next_tip_index + 1) % TIP_ACCOUNTS.len();
        let instructions = vec![
            ComputeBudgetInstruction::set_compute_unit_limit(200_000),
            ComputeBudgetInstruction::set_compute_unit_price(60_000_000),
            system_instruction::transfer(&payer.pubkey(), &recipient, lamports),
            system_instruction::transfer(&payer.pubkey(), &tip, 110_000_000),
        ];
        let message = v0::Message::try_compile(&payer.pubkey(), &instructions, &[], blockhash)?;
        let tx = VersionedTransaction::try_new(VersionedMessage::V0(message), &[&payer])?;
        let signed_tx = STANDARD.encode(bincode::serialize(&tx)?);
        let response = client.post(format!("{base}/submit"))
            .query(&[("api_key", &key)])
            .json(&json!({
                "jsonrpc": "2.0", "id": 1, "method": "sendTransaction",
                "params": [signed_tx, { "encoding": "base64" }]
            }))
            .send().await?;
        let status = response.status();
        let reply: Value = response.json().await?;
        if let Some(error) = reply.get("error") {
            return Err(format!("SlotBolt JSON-RPC error: {error}").into());
        }
        if !status.is_success() { return Err(format!("Submit failed: HTTP {status}").into()); }
        println!("Accepted by SlotBolt API: {}", reply["result"]);
    }
    Ok(())
}
HTTP 200 means the SlotBolt API accepted the request. Check Solana transaction status separately for landing and finality.
02 / Shred Stream

Fast Shred Stream from Frankfurt.

Receive Shred data from our validator partners at your Solana RPC node's public TVU UDP IP and port. Frankfurt delivery is available; Tokyo and New York are at capacity. The Frankfurt distributor forwards incoming Shreds to your configured TVU UDP destination.

01Join & Verify

Open the SlotBolt invite and complete Discord Verify to unlock service controls.

02Choose a term

Select Subscribe Shred Stream and choose a term. Frankfurt is available; Tokyo and New York are at capacity. No region selection is needed.

03Submit your TXID

Use the order's private Solana USDC payment details. Select Enter TXID on the payment request message, paste the transaction signature in the dialog, then select Submit to start payment verification.

04Manage delivery

In Manage Shred Stream, view the expiry, renew the term, and set your node's TVU UDP IP:port.

Frankfurt has 30 subscription places. If full, open a Discord ticket.
Your paid term starts at the USDC transaction's block time, even before you configure a TVU target. Set the target promptly and confirm UDP reception on your node.

Find your node's TVU port

On the machine running your Solana RPC node, inspect the validator's advertised contact info. Use its TVU UDP address and port, not its RPC or TPU port. Replace the ledger path with your own.

agave-validator --ledger /path/to/ledger contact-info | grep 'TVU:'

Set your delivery target to that node's reachable public IP:TVU-port. Once configured, Manage Shred Stream privately shows the sender IP to allow through your UDP firewall; ask SlotBolt Support only if it is absent. For older Solana validators, replace agave-validator with solana-validator.

TVU lookup reference ↗

Shred Stream plans

New subscriptions and renewals use the same six terms. In Manage Shred Stream, choose Renew Shred Stream, select a term, and submit the TXID from the private payment request. An active term extends its expiry; an expired term restarts from the new payment's block time. The first purchase timestamp remains on record.

1 hour · 10 USDC 3 days · 100 USDC 30 days · 400 USDC 90 days · 1,100 USDC 180 days · 2,100 USDC 360 days · 4,000 USDC

Payment verification and delivery target updates are handled in the private Shred Stream control. Need a custom setup? Talk to us on Discord ↗

03 / Dedicated Node

Your own high performance edge.

Managed Frankfurt RPC and gRPC on AMD EPYC Turin hardware. Our engineers tune the Jito-Solana client and Yellowstone stack for high frequency quantitative trading. Frankfurt Shred Stream is included.

We manage the RPC node and provide you with RPC and gRPC access. We can also build and run an RPC node optimized for high-frequency trading with the server provider you specify. You can provision and fully manage your own server with a third-party provider and connect it to the same router as our hosted RPC node; ping latency can be below 0.050 ms. We cannot access your server; your assets remain under your control.

9375FAMD EPYC Turin · 32 cores1,800 USDC /30 daysNode 1,400 + Frankfurt Shred Stream 400
9475FAMD EPYC Turin · 48 cores2,000 USDC /30 daysNode 1,600 + Frankfurt Shred Stream 400
9575FAMD EPYC Turin · 64 cores2,400 USDC /30 daysNode 2,000 + Frankfurt Shred Stream 400

The node uses Jito-Solana by default and exposes RPC and gRPC. Heavy Account Indexing and account enumeration RPC APIs are disabled.

All provisioning and payment arrangements take place in a private ticket. An administrator confirms the Frankfurt Shred destination, any case-by-case credit for an existing Shred subscription, and the 30-day service start date. You can also request a gRPC speed test. Join SlotBolt Discord, open Dedicated Node, and choose Create Ticket ↗