📥 Recibir transacciones decodificadas

Un Decoded Shred Stream se envía por UDP a la IP y el puerto que configures en la página de detalle del stream — sin conexión, sin handshake, sin retransmisión. Cada datagrama tiene como máximo 1.408 bytes y transporta una transacción completa: un header binario de 16 bytes, el slot y la transacción en formato wire estándar de Solana.


📦 El header del sobre (16 bytes)

Todos los datagramas comienzan con el mismo header fijo. Todos los enteros son little-endian.

OffsetTamañoCampoValor / semántica
02magic0x5AE7 (u16 LE) — cualquier otro valor ⇒ rechaza el datagrama
21version2 — rechaza cualquier versión desconocida
31msg_type2 = DecodedTx (1 = reservado)
41flagsReservado, 0
51frag_indexÍndice del fragmento, desde 0 — siempre 0 para transacciones decodificadas
61frag_countNúmero de fragmentos — siempre 1 para transacciones decodificadas (nunca fragmentadas)
71_pad0
88sequ64 LE, monótonamente creciente por flujo de producto

🧾 El payload (msg_type = 2)

Inmediatamente después del header:

CampoTamañoSemántica
slot8u64 LE — el slot al que pertenece la transacción
transactionresto del datagramaFormato wire estándar de Solana (VersionedTransaction serializada con bincode)

Los bytes de la transacción son exactamente lo que espera cualquier SDK de Solana — pásalos a tu parser existente sin cambios. Las transacciones decodificadas nunca se fragmentan: un datagrama siempre es una transacción completa. Las transacciones de voto están excluidas por defecto.


📉 Detectar pérdidas con seq

UDP no retransmite. Cada datagrama lleva un seq que aumenta exactamente en 1 dentro de tu stream. Un salto en seq significa que se perdieron esos datagramas:

text
seq recibido = 1042, anterior = 1039 → 2 datagramas perdidos

Registra el último seq que hayas visto y alerta ante los saltos — una pérdida sostenida suele indicar que tu buffer de recepción es demasiado pequeño (abajo) o un cuello de botella en la ruta.


🔧 Dimensionar el buffer

El flujo llega en ráfagas — un slot cargado puede entregar muchas transacciones seguidas. Dale al socket un buffer de recepción generoso (SO_RCVBUF, varios megabytes) para que las ráfagas cortas se absorban en lugar de que el kernel las descarte, y vacía el socket desde un bucle dedicado y rápido.


💻 Decodificador mínimo (Python)

python
import socket, struct
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8 * 1024 * 1024)
sock.bind(("0.0.0.0", 5000)) # el puerto que configuraste en el dashboard
expected = None
while True:
dgram = sock.recv(2048)
if len(dgram) < 24:
continue
magic, version, msg_type = struct.unpack_from("<HBB", dgram, 0)
if magic != 0x5AE7 or version != 2:
continue # rechaza magic/version desconocidos
if msg_type != 2:
continue # no es un datagrama DecodedTx
(seq,) = struct.unpack_from("<Q", dgram, 8)
if expected is not None and seq != expected:
print(f"salto: {seq - expected} datagrama(s) perdido(s)")
expected = seq + 1
(slot,) = struct.unpack_from("<Q", dgram, 16)
tx_bytes = dgram[24:] # formato wire estándar — pásalo a cualquier parser de tx Solana

🦀 Clientes oficiales y el módulo codec

No hace falta que escribas el decodificador anterior: los clientes oficiales decoded-shredstream (Rust, Go, JavaScript/TypeScript, Python) escuchan en el puerto, parsean el sobre, detectan los saltos de seq y te entregan cada transacción con su slot y sus firmas — por UDP y por gRPC, con la misma API.

rust
// cargo add decoded-shredstream tokio --features tokio/macros,tokio/rt-multi-thread
use decoded_shredstream::{UdpClient, UdpConfig};
#[tokio::main]
async fn main() -> std::io::Result<()> {
let mut client = UdpClient::bind(UdpConfig { port: 8002, ..Default::default() })?;
while let Some(update) = client.next_update().await {
println!("slot={} sig={} {}B", update.slot(), update.signature(), update.bytes().len());
}
Ok(())
}

El crate Rust también expone el framing como módulo codec (constantes FRAME_MAGIC = 0x5AE7, FRAME_VERSION = 2, FRAME_HEADER_LEN = 16, MAX_DATAGRAM = 1408, parse_header, StreamDecoder), para que puedas decodificar datagramas leídos desde un socket que gestiones tú mismo — el cliente Go expone lo mismo a través de ParseHeader / StreamDecoder:

rust
use std::net::UdpSocket;
use decoded_shredstream::codec::{Push, StreamDecoder, MAX_DATAGRAM};
fn decode_loop(socket: &UdpSocket) -> std::io::Result<()> {
let mut decoder = StreamDecoder::new();
let mut buf = [0u8; MAX_DATAGRAM + 64];
loop {
let n = socket.recv(&mut buf)?;
if let Push::Update(update) = decoder.push(&buf[..n]) {
println!("slot={} sig={}", update.slot(), update.signature());
}
}
}

Instalación: npm install decoded-shredstream · pip install decoded-shredstream · cargo add decoded-shredstream · go get github.com/shredstream/decoded-shredstream-go.


➡️ Próximos pasos

  • Decoded Shred Stream — posicionamiento, latencia y modos de entrega.
  • Entrega gRPC — las mismas transacciones en un flujo ordenado, con filtrado de cuentas en el servidor.
Recibir transacciones decodificadas — Docs | ShredStream.com