Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Guías de red y protocolo13 min de lectura

Efectos de transacción de Sui RPC: analizar objectChanges y balanceChanges

Una guía práctica para leer los efectos de transacción de Sui a través de JSON-RPC, analizar objectChanges y balanceChanges, y conciliar un digest con lo que realmente ocurrió en la cadena.

TL;DR

Los efectos de transacción de Sui son el registro autoritativo del protocolo sobre lo que realmente hizo una transacción: su estado, el uso de gas y el conjunto de cambios de objetos y saldos que produjo. Para leerlos a través de JSON-RPC, llama a sui_getTransactionBlock con las opciones showEffects, showObjectChanges, showBalanceChanges y showEvents, y luego trata effects.status.status como la señal de éxito o fallo en lugar de la respuesta de envío. objectChanges distingue objetos creados, mutados, eliminados, envueltos, desenvueltos y publicados, y cada entrada incluye objectId, objectType, owner, version y previousVersion; version es tu token de concurrencia optimista. balanceChanges informa coinType, owner y amount como cadenas decimales con signo, por lo que los deltas netos deben calcularse con aritmética de enteros o decimal, nunca con punto flotante. Este artículo recorre la forma de la respuesta, el modelo de propietario, un analizador Node.js ejecutable, una tabla de resultados que puedes completar con tu propio endpoint y los modos de fallo que hacen que los efectos sean nulos o engañosos.

Qué representan los efectos de transacción de Sui en el protocolo

En Sui, se envía una transacción, pero sus efectos son lo que la red realmente confirmó. El objeto de efectos es el registro del protocolo del resultado de la transacción: si tuvo éxito o falló, cuánto gas consumió y el conjunto exacto de objetos y saldos que cambiaron. Por eso leer los efectos es la forma correcta de confirmar una transacción, en lugar de confiar en la respuesta de envío, que solo te dice que un fullnode aceptó la solicitud para su ejecución.

La distinción importa porque Sui separa la ejecución de la finalidad. Un fullnode puede devolver un digest antes de que la transacción se incluya en un checkpoint, y una transacción puede fallar durante la ejecución mientras sigue consumiendo gas. El objeto de efectos, obtenido después de la ejecución, es la respuesta canónica a "¿qué ocurrió?". La referencia JSON-RPC de Sui documenta los esquemas de efectos y cambios en docs.sui.io/sui-api-ref, y el sobre JSON-RPC 2.0 que transporta la solicitud se especifica en jsonrpc.org/specification.

Para los equipos que construyen indexadores, billeteras o trabajos de conciliación, los efectos son el punto de unión entre un digest enviado y el estado en la cadena que debes actualizar. Si eres nuevo en la red, comienza con la descripción general de la red Sui y la guía de Sui RPC antes de integrar los efectos en producción.

  • Los efectos son el resultado confirmado, no el acuse de recibo del envío.
  • Incluyen estado, gas, cambios de objetos, cambios de saldos y eventos.
  • Son la fuente correcta para la conciliación y la indexación.

La forma de SuiTransactionBlockResponse y las opciones que la completan

sui_getTransactionBlock devuelve un SuiTransactionBlockResponse. Los campos que más usarás son digest, effects, events, objectChanges, balanceChanges, checkpoint y timestampMs. Es crucial que varios de estos solo se completan cuando los solicitas: options.showEffects, options.showObjectChanges, options.showBalanceChanges y options.showEvents. Si los omites, los campos vuelven nulos o vacíos y podrías concluir erróneamente que una transacción no tuvo consecuencias.

La respuesta está envuelta en un sobre de resultado JSON-RPC 2.0 estándar, por lo que la carga útil se encuentra bajo result. El SDK de TypeScript de Sui tipa esta respuesta como SuiTransactionBlockResponse y expone getTransactionBlock, que está documentado en sdk.mystenlabs.com. El SDK tipa las uniones de efectos por ti, pero la semántica de cada tipo de cambio sigue proviniendo de la documentación del protocolo.

Por lo tanto, una solicitud mínima se ve como una llamada JSON-RPC con un arreglo params que contiene el digest y un objeto options. La siguiente sección muestra una forma con curl que puedes ejecutar de inmediato contra cualquier endpoint de fullnode de Sui.

curl -s https://your-sui-endpoint.example \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "sui_getTransactionBlock",
    "params": [
      "0xYOUR_TRANSACTION_DIGEST",
      {
        "showEffects": true,
        "showObjectChanges": true,
        "showBalanceChanges": true,
        "showEvents": true
      }
    ]
  }'

Leer effects.status como la señal autoritativa de éxito o fallo

El campo effects.status.status es "success" o "failure". Cuando es "failure", effects.status.error contiene un error estructurado que describe qué salió mal. Esta es la señal autoritativa: un digest que existe en la cadena no es lo mismo que una transacción que tuvo éxito. Una transacción fallida igual consume gas, y sus efectos aún registran el costo de gas y cualquier estado parcial que el protocolo confirmó.

Un caso sutil pero importante es una transacción que tiene éxito mientras que comandos individuales no producen cambios de objetos. Por ejemplo, una llamada a Move que lee el estado y devuelve un valor, o un comando cuyos efectos son completamente internos, puede tener éxito con un arreglo objectChanges vacío. No trates un conjunto de cambios vacío como un fallo; verifica primero effects.status.status y luego interpreta el conjunto de cambios.

Como el fallo igual consume gas, la lógica de conciliación debe manejar explícitamente los digests fallidos. Si tu trabajo solo procesa transacciones exitosas, filtra por effects.status.status === "success" y registra el motivo del fallo por separado para observabilidad.

  • effects.status.status es "success" o "failure".
  • effects.status.error explica los fallos.
  • Un digest exitoso aún puede ser una transacción fallida.
  • Un arreglo objectChanges vacío es válido en una transacción exitosa.

objectChanges: tipos de cambio y los campos que importan

objectChanges es un arreglo de entradas ObjectChange. Cada entrada tiene un campo type que es uno de created, mutated, deleted, wrapped, unwrapped o published. Los campos que importan para la conciliación son objectId, objectType, owner, version, previousVersion y digest. Para las entradas published, los campos packageId y modules identifican el nuevo paquete.

Para detectar una mutación frente a una creación, inspecciona directamente el campo type en lugar de inferirlo por la presencia de previousVersion. Un objeto creado no tiene previousVersion; un objeto mutado tiene tanto version como previousVersion. La documentación de Sui sobre propiedad de objetos, versiones y el modelo de efectos en docs.sui.io/concepts es autoritativa sobre por qué existen estas distinciones.

version es tu token de concurrencia optimista. Cuando luego mutas un objeto, debes referenciar la versión que observaste por última vez. Si otra transacción lo ha mutado desde entonces, tu versión está obsoleta y la transacción fallará. Por lo tanto, almacenar la versión de objectChanges es cómo mantienes tu estado local consistente con la cadena.

  • Tipos: created, mutated, deleted, wrapped, unwrapped, published.
  • Campos clave: objectId, objectType, owner, version, previousVersion, digest.
  • Usa type, no la presencia de campos, para clasificar un cambio.
  • version es el token de concurrencia optimista para escrituras posteriores.

balanceChanges y el cálculo de deltas netos sin punto flotante

balanceChanges es un arreglo de entradas BalanceChange con coinType, owner y amount. El amount es una cadena decimal con signo, no un número. Los montos positivos son créditos y los negativos son débitos, y el costo de gas aparece como un cambio de saldo negativo para el pagador del gas. Como los montos son cadenas, debes analizarlos con una biblioteca de enteros grandes o decimales, nunca con Number de JavaScript, que pierde precisión en valores grandes.

Para calcular un delta de saldo neto por tipo de moneda y por propietario, agrupa las entradas por el par (coinType, owner) y suma los montos enteros analizados. El resultado es el cambio neto para ese propietario en ese tipo de moneda, incluido el gas. Esta es la forma correcta de responder "¿cuánto ganó o perdió esta dirección?" sin desviación de punto flotante.

Un error común es sumar solo las entradas positivas e ignorar el gas, lo que exagera el monto recibido. Incluye siempre la entrada negativa de gas para el pagador. Si necesitas el monto bruto transferido por separado del gas, suma las entradas que no son de gas y reporta el gas como una línea aparte.

  • amount es una cadena decimal con signo; analízala como un entero.
  • Agrupa por (coinType, owner) y suma para obtener deltas netos.
  • El gas aparece como un cambio de saldo negativo para el pagador.
  • Nunca uses punto flotante para montos de Sui.

El modelo de propietario y lo que implica cada tipo para lecturas posteriores

El campo owner en un cambio de objeto te dice quién controla el objeto y, por lo tanto, cómo debes leerlo después. AddressOwner significa que una sola dirección lo posee; puedes leerlo con sui_getObject y mutarlo firmando con esa dirección. ObjectOwner significa que otro objeto lo posee, lo cual es común para campos dinámicos y objetos hijos; normalmente se accede a él a través de su padre.

Shared significa que el objeto es compartido y puede ser accedido por muchas transacciones, a menudo con ordenamiento por consenso. Immutable significa que el objeto nunca puede volver a mutarse, lo cual es típico de paquetes publicados y objetos congelados. Cada tipo cambia tu estrategia de lectura posterior, así que registra el tipo de propietario junto con el objectId.

Para un tratamiento más profundo de la lectura de objetos y sus campos dinámicos, consulta Leer objetos de Sui por RPC. Ese artículo cubre la ruta de lectura que complementa la ruta de cambios descrita aquí.

  • AddressOwner: control de una sola dirección, lecturas y escrituras directas.
  • ObjectOwner: propiedad de otro objeto, a menudo un campo dinámico.
  • Shared: accesible por muchas transacciones, ordenado por consenso.
  • Immutable: congelado para siempre, típico de paquetes.

Un analizador Node.js ejecutable para efectos, cambios de objetos y deltas de saldo

El siguiente ejemplo usa @mysten/sui para obtener un digest, verificar effects.status y luego imprimir una tabla de cambios de objetos, deltas de saldo e ids de paquetes publicados. Analiza los montos con BigInt para no perder precisión. Reemplaza el endpoint y el digest con tus propios valores.

El script agrupa los cambios de saldo por tipo de moneda y propietario, los suma como BigInt e imprime el delta neto. También recopila los ids de paquetes publicados de las entradas objectChanges de tipo published. Ejecútalo contra un fullnode que tenga la transacción indexada; si effects es nulo, el nodo no ha indexado o ha podado la transacción, lo cual cubre la sección de solución de problemas.

import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';

const client = new SuiClient({ url: getFullnodeUrl('mainnet') });
const digest = process.env.SUI_DIGEST;

const tx = await client.getTransactionBlock({
  digest,
  options: {
    showEffects: true,
    showObjectChanges: true,
    showBalanceChanges: true,
    showEvents: true,
  },
});

if (!tx.effects) {
  throw new Error('effects is null: node not indexed or pruned');
}

const status = tx.effects.status.status;
console.log('status:', status);
if (status === 'failure') {
  console.error('error:', tx.effects.status.error);
}

console.log('\nObject changes:');
for (const c of tx.objectChanges ?? []) {
  console.log([c.type, c.objectId, c.objectType, c.owner, c.version].join(' | '));
}

const deltas = new Map();
for (const b of tx.balanceChanges ?? []) {
  const key = b.coinType + '|' + JSON.stringify(b.owner);
  const prev = deltas.get(key) ?? 0n;
  deltas.set(key, prev + BigInt(b.amount));
}

console.log('\nNet balance deltas:');
for (const [key, amount] of deltas) {
  console.log(key, amount.toString());
}

const packages = (tx.objectChanges ?? [])
  .filter((c) => c.type === 'published')
  .map((c) => c.packageId);
console.log('\nPublished packages:', packages);

Una tabla de resultados para completar con tu propio endpoint

Como la disponibilidad y la latencia de los efectos varían según el proveedor, la única medición confiable es la que ejecutas contra tu propio endpoint. Usa la tabla a continuación como plantilla. Para cada digest, registra si effects estaba presente, el estado, el número de cambios de objetos, el número de cambios de saldos y el tiempo real de obtención. Repite con varios digests y al menos dos endpoints para comparar.

No trates ninguna fila individual como un benchmark. El propósito es caracterizar el comportamiento de indexación de tu proveedor y detectar brechas antes de que lleguen a producción. Si effects es nulo para un digest que sabes que está finalizado, eso es una señal para investigar la configuración de indexación o poda del nodo.

  • Columnas: digest, effects presente (sí/no), estado, recuento de objectChanges, recuento de balanceChanges, ms de obtención.
  • Ejecuta al menos 10 digests por endpoint para una muestra significativa.
  • Compara un digest reciente con uno más antiguo para sondear la poda.
  • Registra la URL del endpoint y la marca de tiempo para cada fila.

Modos de fallo y limitaciones de la conciliación basada en efectos

El modo de fallo más común es que effects sea nulo en un fullnode no indexado o podado. Un fullnode que no ha indexado la transacción, o que ha podado el estado histórico, devolverá null para effects aunque la transacción esté finalizada. Esto es un problema de configuración del proveedor, no del protocolo, y varía según el proveedor. Si necesitas efectos históricos, verifica la política de retención de tu proveedor antes de depender de ella.

Un segundo modo de fallo es confundir eventos con efectos. Los eventos son emitidos por código Move y no son lo mismo que los cambios de objetos; una transacción puede emitir eventos sin cambiar objetos, y puede cambiar objetos sin emitir eventos. Usa Consultar eventos de Sui por RPC para la ruta de eventos y reserva los efectos para la conciliación de estado.

Un tercer modo de fallo es la aritmética con cadenas decimales. Sumar montos como punto flotante corrompe silenciosamente los valores grandes. Analiza siempre a BigInt o a una biblioteca decimal. Por último, algunos proveedores pueden paginar o truncar conjuntos de cambios grandes; si esperas muchos cambios, verifica la longitud completa del arreglo y considera la transmisión mediante Transmisión de checkpoints de Sui con el servicio de ledger gRPC para indexación de alto volumen.

  • effects nulo: fullnode no indexado o podado, dependiente del proveedor.
  • Los eventos no son efectos; no sustituyas uno por el otro.
  • Las sumas en punto flotante corrompen los montos en cadena decimal.
  • Los conjuntos de cambios grandes pueden paginarse o truncarse.

Solución de problemas comunes de análisis de efectos

Si effects es nulo, primero confirma que el digest es correcto y que la transacción está finalizada consultando un explorador de bloques o un segundo endpoint. Si un segundo endpoint devuelve effects, el primer nodo tiene una brecha de indexación o poda. Si ambos devuelven nulo, el digest puede estar mal formado o la transacción puede no existir.

Si objectChanges está vacío en una transacción exitosa, eso es válido; la transacción puede haber solo leído estado o producido efectos internos. Si balanceChanges está vacío pero esperabas una transferencia, verifica que showBalanceChanges se haya establecido en true en las opciones de la solicitud. Si los montos parecen incorrectos, verifica que los estés analizando como cadenas y no como números.

Si ves un desajuste de versión cuando luego intentas mutar un objeto, estás usando una versión obsoleta. Vuelve a obtener el objeto con Leer objetos de Sui por RPC y usa la versión más reciente. Para la validación previa al envío, Simular transacciones de Sui con devInspectTransaction te permite inspeccionar los efectos antes de confirmar.

  • Verifica el digest en un segundo endpoint.
  • Confirma que showBalanceChanges y showObjectChanges sean true.
  • Analiza los montos como cadenas, no como números.
  • Vuelve a obtener objetos para actualizar versiones obsoletas.

Próximos pasos para pipelines de efectos en producción

Para la conciliación en producción, combina el análisis de efectos con una cola duradera y escrituras idempotentes con clave en el digest. Usa Suscripciones WebSocket de Sui RPC para notificaciones de baja latencia, y recurre al sondeo de sui_getTransactionBlock para el relleno. Para indexación de alto volumen, el servicio de ledger gRPC suele ser una mejor opción que las llamadas RPC por digest.

Si necesitas endpoints gestionados con retención predecible, revisa la página de la red Sui, los precios de RPC y el servicio de API. El centro de aprendizaje de OnFinality reúne las guías relacionadas, y la guía de Sui RPC cubre la superficie más amplia de métodos.

Comienza ejecutando el analizador Node.js anterior contra un puñado de digests, completa la tabla de resultados y solo entonces integra los efectos en tu trabajo de conciliación. Esa secuencia evita que construyas sobre un endpoint cuyo comportamiento de indexación no has medido.

  • Clave las escrituras idempotentes en el digest.
  • Usa suscripciones WebSocket para baja latencia y sondeo para relleno.
  • Considera gRPC para indexación de alto volumen.
  • Mide tu endpoint antes de comprometerte con él.

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