Las lecturas de transacciones de Solana JSON-RPC aceptan un parámetro encoding que controla si el nodo devuelve bytes serializados sin procesar (base64 o base64+zstd) o una interpretación estructurada de mejor esfuerzo (jsonParsed). jsonParsed solo decodifica las instrucciones cuyo programa reconoce el nodo; los programas desconocidos aparecen como partiallyDecoded con datos sin procesar, por lo que los consumidores que asumen una decodificación completa fallarán al primer contacto con un programa desconocido. base64 devuelve bytes deterministas que no dependen de la cobertura del parser del nodo ni de su versión, lo que lo convierte en la opción estable para indexadores y para la reproducibilidad entre proveedores, a costa de mantener un decodificador Borsh o bincode en el cliente. Las transacciones versionadas añaden una complicación: resolver las cuentas de una instrucción requiere fusionar las claves estáticas del mensaje con las direcciones cargadas desde las tablas de búsqueda en el orden documentado. Este artículo explica el vocabulario de codificación, las garantías que ofrece cada codificación, la interacción con maxSupportedTransactionVersion y proporciona ejemplos ejecutables en Node.js que comparan ambas codificaciones y reconstruyen instrucciones solo a partir de base64.
Vocabulario de codificación para lecturas de transacciones de Solana
Los métodos JSON-RPC de Solana que devuelven transacciones —getTransaction, getBlock y la familia transactionSubscribe— aceptan un parámetro encoding que determina la representación del payload de la transacción. Los valores documentados son base64 (bytes de transacción serializados sin procesar), base64+zstd (los mismos bytes comprimidos con zstd) y jsonParsed (una interpretación estructurada de mejor esfuerzo del mensaje, sus instrucciones y las direcciones cargadas). La envoltura de solicitud y respuesta sigue la especificación JSON-RPC 2.0, por lo que la elección de codificación afecta solo al payload del resultado, no al framing.
El parámetro encoding no es una preferencia de formato; selecciona entre dos contratos fundamentalmente diferentes. base64 y base64+zstd devuelven la transacción serializada canónica. jsonParsed devuelve una interpretación generada por el nodo que depende de la cobertura del parser y de la versión del software del nodo. La documentación de Solana para getTransaction enumera estos valores y la forma de la respuesta, y la página de estructuras JSON de RPC documenta las formas de instrucción jsonParsed y partiallyDecoded.
Para los equipos que construyen sobre endpoints gestionados, la decisión de codificación es independiente del proveedor. La página de la red Solana describe la superficie RPC, y la referencia de métodos RPC de Solana enumera los métodos que aceptan encoding. La elección es tuya; el nodo simplemente la respeta.
- base64: bytes de transacción serializados sin procesar, deterministas entre nodos y versiones.
- base64+zstd: los mismos bytes comprimidos; requiere un decodificador zstd antes de la decodificación base64.
- jsonParsed: mensaje e instrucciones estructurados, de mejor esfuerzo, sensible a la versión del nodo.
Qué garantiza realmente jsonParsed
jsonParsed no garantiza que todas las instrucciones estén decodificadas. El runtime analiza las instrucciones cuyo programa entiende en una forma estructurada y deja el resto como partiallyDecoded, llevando los datos de instrucción sin procesar y los índices de cuentas. Un consumidor que asume que cada instrucción tiene un campo parsed fallará en el primer programa desconocido. Esta es la causa más común de los informes de errores de 'jsonParsed es inconsistente': la misma transacción puede aparecer totalmente analizada en un nodo y parcialmente decodificada en otro si la cobertura del parser difiere.
La forma partiallyDecoded no es un error. Es la representación documentada para las instrucciones que el nodo no puede interpretar. Tu código debe ramificar según la presencia de parsed frente a partiallyDecoded y recurrir a los datos sin procesar cuando sea necesario. La página de estructuras JSON de RPC muestra ambas formas una al lado de la otra.
Debido a que jsonParsed es de mejor esfuerzo, no es adecuado como única fuente para indexadores que deben comparar registros campo por campo entre proveedores o a lo largo del tiempo. Los campos estructurados pueden cambiar cuando se actualiza el parser del nodo, incluso si los bytes de la transacción subyacente son idénticos.
- parsed: los datos de la instrucción y las cuentas están estructurados por el nodo.
- partiallyDecoded: se devuelven datos sin procesar e índices de cuentas; el cliente debe decodificar.
- La versión del nodo y la cobertura del parser determinan qué forma recibes.
Por qué base64 es la opción estable para los indexadores
base64 devuelve los bytes de la transacción serializados exactamente como se incluyeron en el ledger. Los bytes nunca dependen de la cobertura del parser del nodo ni de la versión del software, por lo que el cliente posee un diseño Borsh o bincode y los datos son comparables y reproducibles entre proveedores y a lo largo del tiempo. El coste es mantener el decodificador: debes rastrear los diseños de los programas y las estructuras de cuentas por tu cuenta.
Para los indexadores, el determinismo de base64 supera la comodidad de jsonParsed. Un registro construido a partir de base64 se puede comparar byte a byte con un registro de otro proveedor. Un registro construido a partir de jsonParsed no, porque los campos estructurados son una interpretación generada por el nodo. Si necesitas tanto comodidad como determinismo, obtén base64 y decodifica localmente, o solicita ambas codificaciones y concílialas.
El artículo sobre transacciones versionadas y análisis de getBlock cubre cómo getBlock devuelve transacciones versionadas y por qué los bytes sin procesar son la fuente canónica. El artículo sobre meta e instrucciones internas de getTransaction cubre el objeto meta posterior al envío, que es independiente de la elección de codificación.
- Determinista entre nodos, proveedores y tiempo.
- Requiere un decodificador Borsh o bincode en el cliente.
- Permite la comparación a nivel de byte y una indexación reproducible.
Resolución de transacciones versionadas y tablas de búsqueda de cuentas
Las claves estáticas de un mensaje v0 y las direcciones resueltas a partir de las tablas de búsqueda de direcciones deben estar presentes antes de que se puedan resolver las cuentas de una instrucción. Un decodificador debe fusionar las claves de cuenta del mensaje con las direcciones cargadas en el orden documentado en lugar de indexar solo la lista de claves estáticas. Si indexas solo las claves estáticas, las cuentas de instrucción que hacen referencia a direcciones cargadas se resolverán a pubkeys incorrectas o fuera de rango.
jsonParsed no elimina este requisito; el nodo realiza la fusión por ti cuando puede, pero las reglas de resolución subyacentes son las mismas. Cuando decodificas base64 tú mismo, debes implementar la fusión. El artículo sobre transacciones versionadas y análisis de getBlock detalla el orden y la mecánica de las tablas de búsqueda.
La codificación interactúa con el parámetro maxSupportedTransactionVersion de getTransaction. Una respuesta condicionada por la versión puede ser un error en lugar de un mensaje decodificado cuando la versión solicitada no es compatible, y jsonParsed no elimina esa restricción. Debes establecer maxSupportedTransactionVersion a la versión más alta que puedas manejar, o el nodo puede rechazar la solicitud.
- Fusiona las claves de cuenta estáticas con las direcciones cargadas en el orden documentado.
- jsonParsed realiza la fusión cuando puede, pero las reglas no cambian.
- maxSupportedTransactionVersion condiciona la respuesta independientemente de la codificación.
Comparación ejecutable: base64 vs jsonParsed para la misma firma
El siguiente script de Node.js obtiene la misma firma de transacción dos veces —una con base64 y otra con jsonParsed— e informa dónde discrepan las dos. Resalta las instrucciones partiallyDecoded y las instrucciones internas faltantes. Ejecútalo contra tu propio endpoint estableciendo la variable de entorno RPC_URL. El script utiliza la API fetch integrada disponible en Node.js 18+.
El script no afirma que una codificación sea mejor; expone las diferencias para que puedas decidir. La salida es un informe que puedes ampliar con tus propios diseños de programas.
const RPC_URL = process.env.RPC_URL || 'https://your-endpoint.example';
const SIGNATURE = process.env.SIGNATURE;
async function rpc(method, params) {
const res = await fetch(RPC_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
});
const json = await res.json();
if (json.error) throw new Error(JSON.stringify(json.error));
return json.result;
}
async function fetchBoth(signature) {
const base64 = await rpc('getTransaction', [
signature,
{ encoding: 'base64', maxSupportedTransactionVersion: 0 }
]);
const jsonParsed = await rpc('getTransaction', [
signature,
{ encoding: 'jsonParsed', maxSupportedTransactionVersion: 0 }
]);
return { base64, jsonParsed };
}
function reportDifferences(base64, jsonParsed) {
const b64Ix = base64.transaction.message.instructions;
const jpIx = jsonParsed.transaction.message.instructions;
console.log('base64 instruction count:', b64Ix.length);
console.log('jsonParsed instruction count:', jpIx.length);
jpIx.forEach((ix, i) => {
if (ix.parsed === undefined) {
console.log(`jsonParsed instruction ${i} is partiallyDecoded`);
}
});
const b64Inner = base64.meta?.innerInstructions || [];
const jpInner = jsonParsed.meta?.innerInstructions || [];
console.log('base64 inner instruction groups:', b64Inner.length);
console.log('jsonParsed inner instruction groups:', jpInner.length);
}
(async () => {
const { base64, jsonParsed } = await fetchBoth(SIGNATURE);
reportDifferences(base64, jsonParsed);
})();Reconstruir la lista de instrucciones solo a partir de base64
Para reconstruir instrucciones a partir de base64, debes deserializar la transacción, resolver las claves de cuenta (incluidas las direcciones cargadas para v0) y decodificar los datos de cada instrucción según el diseño de su programa. El siguiente ejemplo de Node.js utiliza @solana/web3.js para deserializar una transacción base64 e imprimir la lista de instrucciones con los IDs de programa y las claves de cuenta. No decodifica los datos de la instrucción; eso requiere un diseño específico del programa.
Este enfoque te proporciona una lista de instrucciones determinista que no depende del parser del nodo. Puedes ampliarla añadiendo decodificadores Borsh o bincode para los programas que te interesen. El artículo sobre meta e instrucciones internas de getTransaction cubre cómo combinarlo con el objeto meta para las instrucciones internas.
const { Connection, PublicKey, VersionedTransaction } = require('@solana/web3.js');
const RPC_URL = process.env.RPC_URL || 'https://your-endpoint.example';
const SIGNATURE = process.env.SIGNATURE;
(async () => {
const connection = new Connection(RPC_URL, 'confirmed');
const tx = await connection.getTransaction(SIGNATURE, {
encoding: 'base64',
maxSupportedTransactionVersion: 0
});
if (!tx) throw new Error('Transaction not found');
const raw = Buffer.from(tx.transaction[0], 'base64');
const decoded = VersionedTransaction.deserialize(raw);
const message = decoded.message;
const staticKeys = message.staticAccountKeys.map(k => k.toBase58());
const loaded = tx.meta?.loadedAddresses || { writable: [], readonly: [] };
const allKeys = [
...staticKeys,
...loaded.writable,
...loaded.readonly
];
message.compiledInstructions.forEach((ix, i) => {
const programId = allKeys[ix.programIdIndex];
const accounts = ix.accountKeyIndexes.map(idx => allKeys[idx]);
console.log(`Instruction ${i}: program=${programId}`);
console.log(' accounts:', accounts.join(', '));
console.log(' data length:', ix.data.length);
});
})();Tabla de decisión: codificación vs determinismo vs trabajo en el cliente vs sensibilidad a la versión del nodo
La siguiente tabla resume las ventajas y desventajas. Verifica cada fila contra tu propio endpoint obteniendo la misma firma con cada codificación y registrando los resultados. La tabla es una guía, no un benchmark; la cobertura del parser y la versión de tu nodo determinan el comportamiento real.
Usa la tabla para elegir una codificación por caso de uso. Para los indexadores, base64 es la opción estable. Para herramientas exploratorias, jsonParsed reduce el trabajo en el cliente. Para entornos con ancho de banda limitado, base64+zstd reduce el tamaño del payload a costa de un paso de descompresión.
- base64: determinismo alto; trabajo en el cliente alto (decodificador completo); sensibilidad a la versión del nodo baja.
- base64+zstd: determinismo alto; trabajo en el cliente alto más zstd; sensibilidad a la versión del nodo baja.
- jsonParsed: determinismo bajo; trabajo en el cliente bajo para programas conocidos; sensibilidad a la versión del nodo alta.
- jsonParsed con fallback a partiallyDecoded: determinismo medio; trabajo en el cliente medio; sensibilidad a la versión del nodo media.
Medir el comportamiento de la codificación contra tu propio endpoint
Debido a que la cobertura del parser y las versiones del nodo varían, deberías medir el comportamiento de tu endpoint en lugar de confiar en afirmaciones generales. El método a continuación es reproducible: obtén un conjunto de firmas con ambas codificaciones, cuenta las instrucciones partiallyDecoded y registra las diferencias. Rellena la tabla de resultados con tus propios números.
Ejecuta el script de comparación de la sección anterior sobre una muestra de firmas que incluya programas conocidos (System, Token, Associated Token) y al menos un programa desconocido. Registra los recuentos. Repite la medición después de cualquier actualización del nodo para detectar cambios en la cobertura del parser.
- Columnas de la tabla de resultados: firma, recuento de instrucciones base64, recuento de instrucciones jsonParsed, recuento de partiallyDecoded, recuento de grupos de instrucciones internas (base64), recuento de grupos de instrucciones internas (jsonParsed).
- Fila 1: [rellenar con tu medición]
- Fila 2: [rellenar con tu medición]
- Fila 3: [rellenar con tu medición]
Solución de problemas relacionados con la codificación
Sorpresas con partiallyDecoded: si tu código asume que cada instrucción tiene un campo parsed, lanzará una excepción con partiallyDecoded. Ramifica según la presencia de parsed y recurre a los datos sin procesar. Este es un comportamiento documentado, no un error.
Errores de maxSupportedTransactionVersion: si el nodo devuelve un error sobre una versión de transacción no compatible, establece maxSupportedTransactionVersion a la versión más alta que puedas manejar. jsonParsed no evita esta restricción. La guía de migración para métodos obsoletos cubre cambios de versionado relacionados.
Fallos de decodificación de base64+zstd: asegúrate de descomprimir con un decodificador zstd antes de la decodificación base64. Un error común es decodificar en base64 los bytes comprimidos directamente, lo que produce datos basura. El artículo sobre la codificación de accountSubscribe cubre el mismo vocabulario de codificación para las suscripciones.
Diferencias de campos entre codificaciones: si comparas un registro derivado de base64 con un registro derivado de jsonParsed, espera diferencias en la representación de instrucciones, el orden de las claves de cuenta y la agrupación de instrucciones internas. No los compares campo por campo sin normalizarlos a una representación común.
- Ramifica según parsed vs partiallyDecoded.
- Establece maxSupportedTransactionVersion explícitamente.
- Descomprime zstd antes de la decodificación base64.
- Normaliza antes de comparar entre codificaciones.
Limitaciones y ventajas y desventajas
jsonParsed es de mejor esfuerzo y sensible a la versión del nodo. Es cómodo para programas conocidos pero inadecuado como única fuente para una indexación determinista. base64 es determinista pero requiere un decodificador en el cliente que debes mantener a medida que evolucionan los diseños de los programas. base64+zstd añade una dependencia de descompresión.
Las transacciones versionadas requieren fusionar las claves estáticas con las direcciones cargadas; no hacerlo produce una resolución de cuentas incorrecta. La elección de codificación no elimina este requisito. Las páginas de la red Solana y de precios de RPC describen la superficie del endpoint y el modelo de costes; la página del servicio API describe el acceso gestionado.
Ninguna codificación elimina la necesidad de entender el formato de la transacción. Elige según si valoras el determinismo (base64) o un menor trabajo en el cliente (jsonParsed), y mide contra tu propio endpoint.
- jsonParsed: poco trabajo en el cliente, bajo determinismo.
- base64: mucho trabajo en el cliente, alto determinismo.
- Transacciones versionadas: fusiona las claves independientemente de la codificación.
Próximos pasos para la integración
Empieza obteniendo una firma conocida con ambas codificaciones y ejecutando el script de comparación. Registra las diferencias en la tabla de resultados. Luego decide qué codificación se adapta a tu caso de uso. Para los indexadores, crea un decodificador base64 para los programas que te interesen. Para las herramientas, usa jsonParsed con un fallback a partiallyDecoded.
Revisa el centro de aprendizaje de OnFinality para artículos relacionados sobre la integración de RPC de Solana, y la referencia de métodos RPC de Solana para la lista completa de métodos. El artículo sobre la codificación de accountSubscribe cubre las codificaciones de suscripción, y el artículo sobre transacciones versionadas cubre el análisis de getBlock.
Mide, decide y documenta tu elección de codificación en tus notas de integración para que los futuros mantenedores entiendan por qué el cliente decodifica base64 o se apoya en jsonParsed.
- Ejecuta el script de comparación en tu endpoint.
- Elige base64 para determinismo, jsonParsed para comodidad.
- Documenta la elección y el comportamiento de fallback.