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_toreturns when the packet has been handed to the kernel — not when BlockSprint has received or accepted it. Track the signature client-side and pollgetSignatureStatusesyourself. - 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-message4.2+ andsolana-transaction4.x and serialize it withwincode::serialize, notbincode::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. bincodeversion. Solana's wire format for legacy and v0 transactions requiresbincode 1.x(bincode = "1").
