Skip to content

rust ​

Send a transaction over UDP from Rust using the standard library UdpSocket. No extra dependencies needed beyond what you already use to sign Solana transactions (the examples use solana-sdk 2.x and bincode 1.x).


Minimal Example ​

rust
use std::net::UdpSocket;
use solana_sdk::transaction::Transaction;

const ENDPOINT: &str = "fra.blocksprint.io:5000";
const API_KEY: &str = "YOUR_API_KEY";

fn send_udp(signed_tx: &Transaction) -> std::io::Result<()> {
    // Serialize the transaction to wire bytes (same as base64-decoding for HTTP)
    let tx_bytes = bincode::serialize(signed_tx)
        .expect("transaction serialization should not fail");

    // Build the packet: [key_len: u8][api_key bytes][tx wire bytes]
    let mut pkt = Vec::with_capacity(1 + API_KEY.len() + tx_bytes.len());
    pkt.push(API_KEY.len() as u8);
    pkt.extend_from_slice(API_KEY.as_bytes());
    pkt.extend_from_slice(&tx_bytes);

    // Fire and forget
    let sock = UdpSocket::bind("0.0.0.0:0")?;
    sock.send_to(&pkt, ENDPOINT)?;
    Ok(())
}

Full Example: Sign + Send ​

rust
use solana_sdk::{
    hash::Hash,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use std::net::UdpSocket;
use std::str::FromStr;

const ENDPOINT: &str = "fra.blocksprint.io:5000";
const API_KEY: &str = "YOUR_API_KEY";

// One of your tip recipient addresses — see the Tipping doc for the full list
const TIP_ADDRESS: &str = "YOUR_TIP_ADDRESS";
const TIP_LAMPORTS: u64 = 1_000_000; // 0.001 SOL (minimum)

fn build_and_send(
    payer: &Keypair,
    recipient: &Pubkey,
    amount_lamports: u64,
    recent_blockhash: Hash,
) -> std::io::Result<()> {
    let tip_pubkey = Pubkey::from_str(TIP_ADDRESS).expect("valid tip address");

    let ixs = vec![
        system_instruction::transfer(&payer.pubkey(), recipient, amount_lamports),
        system_instruction::transfer(&payer.pubkey(), &tip_pubkey, TIP_LAMPORTS),
    ];

    let tx = Transaction::new_signed_with_payer(
        &ixs,
        Some(&payer.pubkey()),
        &[payer],
        recent_blockhash,
    );

    let tx_bytes = bincode::serialize(&tx).expect("serialize tx");

    let mut pkt = Vec::with_capacity(1 + API_KEY.len() + tx_bytes.len());
    pkt.push(API_KEY.len() as u8);
    pkt.extend_from_slice(API_KEY.as_bytes());
    pkt.extend_from_slice(&tx_bytes);

    let sock = UdpSocket::bind("0.0.0.0:0")?;
    sock.send_to(&pkt, ENDPOINT)?;

    println!("Sent tx with signature: {}", tx.signatures[0]);
    Ok(())
}

Reusing the Socket (High Throughput) ​

For latency-sensitive bots submitting many transactions, bind the socket once and reuse it:

rust
use std::net::UdpSocket;
use std::sync::Arc;

pub struct UdpSender {
    sock: UdpSocket,
    endpoint: String,
    api_key: String,
}

impl UdpSender {
    pub fn new(endpoint: impl Into<String>, api_key: impl Into<String>) -> std::io::Result<Self> {
        let sock = UdpSocket::bind("0.0.0.0:0")?;
        Ok(Self {
            sock,
            endpoint: endpoint.into(),
            api_key: api_key.into(),
        })
    }

    pub fn send(&self, tx_bytes: &[u8]) -> std::io::Result<usize> {
        let mut pkt = Vec::with_capacity(1 + self.api_key.len() + tx_bytes.len());
        pkt.push(self.api_key.len() as u8);
        pkt.extend_from_slice(self.api_key.as_bytes());
        pkt.extend_from_slice(tx_bytes);
        self.sock.send_to(&pkt, &self.endpoint)
    }
}

// Usage
fn main() -> std::io::Result<()> {
    let sender = Arc::new(UdpSender::new(
        "fra.blocksprint.io:5000",
        "YOUR_API_KEY",
    )?);

    // Send many transactions through the same socket
    for tx in pending_transactions() {
        let bytes = bincode::serialize(&tx).unwrap();
        sender.send(&bytes)?;
    }
    Ok(())
}

// Your signed transactions
fn pending_transactions() -> Vec<solana_sdk::transaction::Transaction> { vec![] }

Notes ​

  • No response. send_to returns when the packet has been handed to the kernel — not when BlockSprint has received or accepted it. Track the signature client-side and poll getSignatureStatuses yourself.
  • Silent drops. If the API key is invalid, the tip check fails, or you're rate-limited, the packet is dropped with no error.
  • MTU. Keep pkt.len() ≤ 1232 + 1 + api_key.len() to stay under typical path MTUs.
  • Transaction V1. Build it with solana-message 4.2+ and solana-transaction 4.x and serialize it with wincode::serialize, not bincode::serialize (bincode writes V1 in the wrong layout). Set the compute-unit limit and the loaded-accounts data size limit in its transaction config. Keep V1 datagrams within about 1,400 bytes, and send larger V1 transactions (up to 4,096 bytes) via QUIC or sendTransaction.
  • bincode version. Solana's wire format for legacy and v0 transactions requires bincode 1.x (bincode = "1").