Un recibo de transacción es el registro posterior a la ejecución de una transacción blockchain: confirma si la ejecución tuvo éxito, informa el gas consumido y contiene los registros de eventos emitidos durante la ejecución. A diferencia de un objeto de transacción, que describe lo que se solicitó, un recibo describe lo que realmente ocurrió una vez que la transacción se incluyó en un bloque. Este artículo explica el modelo de datos del recibo, muestra cómo obtener y verificar recibos mediante JSON-RPC en curl, JavaScript y Python, y cubre los modos de fallo comunes: recibos nulos, estado revertido, registros faltantes y desajustes de cadena o proveedor. También describe un procedimiento de medición reproducible para la latencia de recibos, de modo que puedas evaluar tu propia ruta RPC en lugar de depender de las afirmaciones de los proveedores.
Qué representa un recibo de transacción
Un recibo de transacción es el registro canónico de que una transacción se incluyó en un bloque y de lo que produjo su ejecución. Se identifica por el hash de la transacción y solo está disponible después de que la transacción se haya minado o finalizado de otro modo en un bloque. Antes de la inclusión, el recibo no existe y las consultas de recibos devuelven null.
La distinción que más importa en la práctica es entre la transacción y su recibo. El objeto de transacción describe la intención: remitente, destinatario, valor, límite de gas, campos de precio de gas, nonce y calldata. El recibo describe el resultado: si la ejecución tuvo éxito o se revirtió, cuánto gas se consumió realmente, el precio efectivo de gas pagado, la dirección del contrato creado por un despliegue y los registros emitidos. Las aplicaciones que necesitan saber si algo funcionó deben leer el recibo, no la transacción.
Los recibos también son la base para la indexación basada en eventos. Cada evento Transfer, Swap o personalizado al que reacciona tu backend es una entrada de registro dentro de un recibo. Si implementas lógica de confirmación, en la práctica estás esperando un recibo con un número suficiente de bloques descendientes.
- Transacción: lo que se solicitó, firmó y transmitió.
- Recibo: lo que se ejecutó, cuánto costó y qué emitió.
- Los recibos existen solo después de la inclusión; las consultas previas a la inclusión devuelven null.
Supuestos y requisitos previos
Los ejemplos a continuación asumen un endpoint JSON-RPC compatible con EVM y un hash de transacción que controles o puedas observar. Necesitas una URL RPC para la red correcta; sustituye tu propio marcador RPC_URL en lugar de codificar un endpoint público, ya que los endpoints públicos varían en límites de velocidad, profundidad de archivo y métodos admitidos. Si estás eligiendo un proveedor para cargas de trabajo intensivas en recibos, como indexadores o servicios de confirmación, la guía de endpoints RPC y la guía de endpoints RPC multicadena cubren los criterios de selección, y el servicio API de OnFinality describe el acceso gestionado en las redes admitidas.
También debes saber a qué cadena te diriges. Un recibo obtenido de la cadena incorrecta para un hash dado será null o, en raras colisiones de hash entre cadenas, engañoso. Fija siempre el ID de cadena junto con el hash en el estado de tu aplicación.
Campos del recibo y su significado
El conjunto exacto de campos lo define la especificación JSON-RPC y es ampliamente consistente entre cadenas EVM, aunque algunas cadenas añaden campos como datos de tarifas L1 en rollups. Los campos principales son lo suficientemente estables para construir sobre ellos.
transactionHash: el hash que identifica la transacción y su recibo.blockHashyblockNumber: dónde se incluyó la transacción.transactionIndex: posición dentro del bloque.fromyto: remitente y destinatario;toes null para la creación de contratos.contractAddress: la dirección desplegada cuando la transacción creó un contrato; de lo contrario, null.cumulativeGasUsed: gas utilizado por todas las transacciones hasta esta inclusive en el bloque.gasUsed: gas consumido solo por esta transacción.effectiveGasPrice: el precio realmente pagado por unidad de gas (post-EIP-1559 en cadenas compatibles).status:0x1para éxito,0x0para ejecución revertida.logs: matriz de registros de eventos, cada uno con address, topics, data y log index.logsBloom: un filtro probabilístico sobre los registros del bloque, útil para clientes ligeros y algunos indexadores.type: tipo de transacción, por ejemplo legacy o EIP-1559.
Obtener un recibo con curl
El método estándar es eth_getTransactionReceipt. Toma un único argumento, el hash de la transacción, y devuelve el objeto de recibo o null. La siguiente solicitud se puede ejecutar una vez que reemplaces los marcadores.
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getTransactionReceipt",
"params": ["0xYOUR_TRANSACTION_HASH"]
}'Obtener y verificar un recibo en JavaScript
En el código de la aplicación rara vez quieres una única llamada sin seguimiento. Quieres sondear hasta que el recibo exista, luego afirmar el éxito e inspeccionar los registros. El siguiente ejemplo usa fetch directamente para que funcione sin biblioteca, y trata null como pendiente en lugar de como error.
const RPC_URL = process.env.RPC_URL;
const TX_HASH = process.env.TX_HASH;
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.error.message);
return json.result;
}
async function waitForReceipt(hash, timeoutMs = 120000, intervalMs = 2000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const receipt = await rpc('eth_getTransactionReceipt', [hash]);
if (receipt) return receipt;
await new Promise(r => setTimeout(r, intervalMs));
}
throw new Error('Timed out waiting for receipt');
}
const receipt = await waitForReceipt(TX_HASH);
if (receipt.status !== '0x1') {
throw new Error(`Transaction reverted in block ${receipt.blockNumber}`);
}
console.log('Gas used:', parseInt(receipt.gasUsed, 16));
console.log('Logs emitted:', receipt.logs.length);Obtener un recibo en Python
Los servicios en Python a menudo agrupan comprobaciones de recibos en muchos hashes. El siguiente fragmento usa solo la biblioteca estándar y demuestra la misma distinción entre pendiente y revertido. Para indexación de alto volumen, las solicitudes por lotes o una suscripción websocket a nuevas cabezas suele ser más eficiente que el sondeo por hash.
import json, os, time, urllib.request
RPC_URL = os.environ["RPC_URL"]
TX_HASH = os.environ["TX_HASH"]
def rpc(method, params):
payload = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode()
req = urllib.request.Request(RPC_URL, data=payload, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=30) as resp:
body = json.loads(resp.read())
if "error" in body:
raise RuntimeError(body["error"]["message"])
return body["result"]
def wait_for_receipt(tx_hash, timeout=120, interval=2):
deadline = time.time() + timeout
while time.time() < deadline:
receipt = rpc("eth_getTransactionReceipt", [tx_hash])
if receipt:
return receipt
time.sleep(interval)
raise TimeoutError("receipt not found before timeout")
receipt = wait_for_receipt(TX_HASH)
assert receipt["status"] == "0x1", f"reverted in block {receipt['blockNumber']}"
print("gasUsed", int(receipt["gasUsed"], 16))
print("logs", len(receipt["logs"]))Resultado esperado de la verificación
Una verificación exitosa tiene tres resultados observables. Primero, el objeto de recibo no es nulo y su transactionHash coincide con el hash que consultaste. Segundo, status es 0x1. Tercero, si tu aplicación depende de eventos, las entradas de registro esperadas aparecen con la dirección del contrato emisor correcta y la firma topic0.
Si estás confirmando la finalidad en lugar de la mera inclusión, compara el blockNumber del recibo con la cabeza actual de eth_blockNumber y exige una profundidad de confirmación adecuada a la cadena. Las cadenas con finalidad probabilística necesitan una profundidad mayor que las cadenas con gadgets de finalidad explícitos. Registra la profundidad que uses; es una decisión de política, no una constante del protocolo.
Cuando realices mediciones de latencia, captúralas en una tabla en lugar de afirmar un número. Un formato reproducible se ve así:
- Columnas: red, ruta RPC, tamaño de muestra, latencia p50 del recibo, latencia p95 del recibo, tasa de nulos en el primer sondeo.
- Procedimiento: transmite o selecciona una transacción conocida, sondea
eth_getTransactionReceipta un intervalo fijo y marca la hora de la primera respuesta no nula. - Repite en varias transacciones y a diferentes horas del día; informa percentiles, no solo promedios.
Solución de problemas con recibos nulos, revertidos y desajustados
Un recibo null es el problema más común y casi siempre significa que la transacción aún no se ha incluido. También puede significar que la transacción se eliminó del mempool, fue reemplazada por una transacción con mayor tarifa y el mismo nonce, o nunca se transmitió. Verifica el nonce del remitente y el estado del mempool antes de concluir que la transacción falló.
Un recibo con status: 0x0 significa que la transacción se incluyó y se pagó, pero la ejecución se revirtió. El gas se gastó de todos modos. Para diagnosticar, reproduce la llamada con eth_call en el bloque padre, o inspecciona el rastro de la transacción si tu proveedor admite métodos de trazado. No trates un recibo revertido como un error de red; es un resultado válido en la cadena.
Un recibo que existe pero cuyos registros no coinciden con las expectativas suele indicar una dirección de contrato incorrecta, una firma de evento cambiada o una actualización de proxy. Decodifica topics[0] contra el ABI que crees que está desplegado y confirma la dirección emisora en cada registro.
Por último, un recibo obtenido de la cadena incorrecta normalmente será null, pero si operas varias redes, etiqueta cada solicitud con un ID de cadena y verifícalo en tu cliente. Los problemas del lado del proveedor, como la limitación de velocidad, suelen manifestarse como errores JSON-RPC en lugar de resultados nulos, así que distingue los errores de transporte de la ausencia genuina.
Limitaciones y compensaciones
Los recibos no son un registro de auditoría completo. No incluyen llamadas internas, motivos de reversión ni diferencias de estado a menos que uses API de trazado, que muchos proveedores restringen o miden por separado. Si necesitas detalles completos de ejecución, planifica una infraestructura con capacidad de trazado y presupuesta en consecuencia.
El sondeo de recibos es simple pero ineficiente a escala. Las suscripciones websocket a nuevas cabezas, combinadas con filtrado local, reducen el volumen de solicitudes pero añaden complejidad de gestión de conexiones y lógica de reconexión. Las solicitudes JSON-RPC por lotes son un punto intermedio para indexadores que procesan muchos hashes por bloque.
Los requisitos de archivo importan si rellenas recibos históricos. Los recibos recientes suelen ser servidos por nodos completos, pero el historial profundo puede requerir acceso de archivo. Confirma la retención de tu proveedor antes de diseñar un trabajo de relleno, y consulta las opciones de nodo dedicado si necesitas capacidad predecible. Los precios para acceso RPC de mayor rendimiento se describen en la página de precios RPC.
Próximos pasos
Comienza instrumentando tu ruta de recibos: registra el hash, el ID de cadena, el número de sondeos y el estado final de cada transacción que envíe tu aplicación. Ese único cambio convierte la mayoría de los informes de 'transacción atascada' en datos diagnosticables.
Luego decide tu política de confirmación por cadena y codifícala como configuración en lugar de constantes dispersas. Si operas en muchas redes, revisa la lista de redes admitidas y estandariza tu cliente de recibos para que las peculiaridades específicas de cada cadena se manejen en un solo lugar.
Para los equipos que eligen un endpoint para transportar tráfico de recibos, la decisión concreta del proveedor se maneja mejor con el RPC Assistant, que compara las opciones de endpoint con tus necesidades de latencia y fiabilidad. Usa este artículo para la mecánica y tus propias mediciones para el veredicto.