Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Integración y Desarrollo10 min de lectura

Recibo de transacción explicado: campos, obtención y verificación

Una guía práctica sobre los recibos de transacciones blockchain: qué significa cada campo, cómo obtener y verificar un recibo mediante JSON-RPC, y cómo manejar recibos pendientes, revertidos y ausentes.

TL;DR

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.
  • blockHash y blockNumber: dónde se incluyó la transacción.
  • transactionIndex: posición dentro del bloque.
  • from y to: remitente y destinatario; to es 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: 0x1 para éxito, 0x0 para 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_getTransactionReceipt a 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.

Nunca te preocupes por la infraestructura nuevamente

OnFinality elimina la carga pesada de DevOps para que puedas construir de forma más inteligente y rápida.

Comenzar