Los eventos Move de Sui se emiten con sui::event::emit, llevan un tipo totalmente cualificado y son indexados por los fullnodes para poder consultarlos con suix_queryEvents usando un EventFilter y un cursor. La unión EventFilter incluye All, Transaction, MoveModule, MoveEventType, MoveEventField, Sender, TimeRange y Package, cada uno con semánticas de coincidencia distintas. La paginación es únicamente por cursor: la respuesta devuelve data[] más nextCursor y hasNextPage, y se recorren las páginas pasando el nextCursor anterior con un límite y un orden. Un indexador fiable almacena el último cursor procesado (o txDigest y eventSeq), sondea desde ese cursor y reconcilia los eventos perdidos tras una desconexión, respetando la ventana de retención del fullnode.
Qué es un evento Move de Sui y cómo se almacena
Un evento Move de Sui es un registro estructurado que se emite durante la ejecución de una transacción con sui::event::emit. Cada evento lleva un tipo totalmente cualificado, como 0x2::sui::SUI o una ruta package::module::Struct, y el fullnode lo indexa para que los clientes puedan consultarlo después. La descripción autorizada de la emisión e indexación se encuentra en la página Emitting Events de la documentación de Sui.
Cuando consultas eventos, el RPC devuelve cada evento con un id global de evento, el digest de la transacción y el número de secuencia, la cadena de tipo, una representación parsedJson y una carga canónica bcs(base64). El parsedJson es cómodo para la lógica de la aplicación, pero el campo bcs es la representación canónica que debes usar cuando necesites verificar o volver a serializar el evento exactamente como se emitió.
Los eventos son indexados por el fullnode, no se almacenan para siempre. Los eventos más antiguos pueden quedar fuera de la ventana de retención del nodo, por lo que las consultas de eventos históricos a menudo requieren un archivo o un indexador dedicado. La página de OnFinality sobre nodos de archivo de Sui y RPC histórico explica cómo el acceso a archivos amplía el historial consultable más allá de un fullnode estándar.
- Se emite con sui::event::emit durante la ejecución de la transacción.
- Lleva un tipo totalmente cualificado: package::module::Struct.
- Se devuelve con id, txDigest, sequence, type, parsedJson y bcs(base64).
- Indexado por el fullnode; la retención es limitada y varía según el nodo.
El método suix_queryEvents y sus parámetros
El método suix_queryEvents es el punto de entrada JSON-RPC para leer eventos indexados. Acepta un objeto de consulta que contiene un EventFilter, un limit, un cursor y un order de Ascending o Descending. La respuesta es un QueryEventsResult con un array data y un nextCursor, además de un indicador hasNextPage en las implementaciones actuales. El método y sus tipos están documentados en la Referencia de la API de Sui y en la documentación del crate sui_json_rpc.
El limit controla cuántos eventos se devuelven por página. El cursor es un token opaco del nextCursor de la página anterior; debes tratarlo como una caja negra y nunca construirlo ni analizarlo tú mismo. El order determina si la página avanza hacia delante o hacia atrás por el flujo de eventos indexados.
Dado que el método forma parte de la superficie JSON-RPC heredada, algunos proveedores lo marcan como obsoleto en favor de API de eventos más recientes. La documentación de migración de JSON-RPC de Sui registra esta transición. Si tu endpoint informa de obsolescencia, verifica la API de eventos recomendada actual para tu caso de uso antes de construir una integración de larga duración.
- query: { filter, limit, cursor, order }.
- Respuesta: { data[], nextCursor, hasNextPage }.
- order es Ascending o Descending.
- El cursor es opaco; reutiliza siempre el nextCursor devuelto.
Variantes de EventFilter y su semántica exacta de coincidencia
La unión EventFilter define cómo se seleccionan los eventos. All coincide con todos los eventos. Transaction coincide con eventos de un digest de transacción específico. MoveModule coincide con eventos de un par paquete y módulo. MoveEventType coincide con una cadena de tipo de estructura totalmente cualificada. MoveEventField coincide con una ruta de campo analizada y un valor. Sender coincide con eventos emitidos por una dirección específica. TimeRange coincide con eventos dentro de un rango de marcas de tiempo. Package coincide con eventos de un paquete.
La distinción entre StructType y MoveEventType es importante. La coincidencia de StructType compara el tipo de estructura del evento, mientras que la coincidencia de MoveEventType compara la etiqueta de tipo del evento. En la práctica, esto significa que la cadena que pasas debe ser el tipo totalmente cualificado exactamente como se emitió, incluida la dirección 0x inicial y los nombres de módulo y estructura. Una cadena parcial o no cualificada devolverá silenciosamente ningún resultado.
Los filtros MoveEventField comparan valores analizados, por lo que los números y los ids deben coincidir con la representación analizada. Si filtras por un campo que es una cadena en parsedJson, pasa una cadena; si es un número, pasa un número. Los tipos no coincidentes son una causa común de conjuntos de resultados vacíos.
- All: sin filtrado.
- Transaction: por digest de transacción.
- MoveModule: por paquete y módulo.
- MoveEventType: por cadena de tipo de estructura totalmente cualificada.
- MoveEventField: por ruta de campo analizada y valor.
- Sender: por dirección emisora.
- TimeRange: por rango de marcas de tiempo.
- Package: por dirección de paquete.
Paginación por cursor: la única forma correcta de paginar
Las consultas de eventos de Sui no admiten paginación por desplazamiento. Se pagina pasando el nextCursor de la respuesta anterior en la siguiente solicitud. Esta es la única forma correcta de recorrer el flujo de eventos sin omitir ni duplicar eventos, porque el índice subyacente puede cambiar entre solicitudes y los desplazamientos se desviarían.
El orden Descending más un cursor es la forma estándar de paginar hacia atrás en el historial. Si quieres los eventos más recientes primero, establece order en Descending y comienza con un cursor vacío. Cada solicitud posterior utiliza el nextCursor de la página anterior. Cuando hasNextPage es false o nextCursor es null, has llegado al final del historial disponible para ese filtro.
Un error común es reutilizar el mismo cursor con filtros diferentes o ignorar el cursor por completo y volver a consultar desde el principio. Ambos provocan bucles o procesamiento duplicado. Almacena siempre el cursor junto con el filtro que lo produjo y solo reutilízalo con el mismo filtro y orden.
- Sin paginación por desplazamiento; usa solo nextCursor.
- El orden Descending recorre el historial hacia atrás.
- hasNextPage false o nextCursor null significa fin del historial.
- Almacena el cursor con su filtro y orden para evitar bucles.
Un ejemplo ejecutable en Node.js: filtrar por MoveEventType y paginar con cursores
El siguiente ejemplo en Node.js llama a suix_queryEvents con un filtro MoveEventType para una estructura específica y luego itera sobre nextCursor para recopilar un número fijo de páginas. Imprime los campos clave de cada evento para que puedas ver la forma de la respuesta. Reemplaza la URL del endpoint y el tipo de estructura por tus propios valores.
El ejemplo usa fetch, que está disponible en Node.js moderno. Almacena el cursor entre iteraciones y se detiene cuando no hay página siguiente o cuando se alcanza el límite de páginas. Este es el mismo patrón que usarías en un indexador de producción, con la adición de la persistencia del cursor.
const ENDPOINT = 'https://your-sui-rpc-endpoint';
const STRUCT_TYPE = '0x2::sui::SUI';
const PAGE_LIMIT = 50;
const MAX_PAGES = 5;
async function queryEventsPage(cursor) {
const body = {
jsonrpc: '2.0',
id: 1,
method: 'suix_queryEvents',
params: [
{
MoveEventType: STRUCT_TYPE
},
cursor,
PAGE_LIMIT,
true // descending
]
};
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const json = await res.json();
if (json.error) throw new Error(JSON.stringify(json.error));
return json.result;
}
async function collectPages() {
let cursor = null;
let pages = 0;
const all = [];
while (pages < MAX_PAGES) {
const result = await queryEventsPage(cursor);
for (const event of result.data) {
all.push(event);
console.log({
id: event.id,
txDigest: event.id.txDigest,
sequence: event.id.eventSeq,
type: event.type,
parsedJson: event.parsedJson,
bcs: event.bcs
});
}
pages += 1;
if (!result.hasNextPage || !result.nextCursor) break;
cursor = result.nextCursor;
}
console.log('collected', all.length, 'events across', pages, 'pages');
}
collectPages().catch(console.error);Filtrar por remitente y por digest de transacción
Filtrar por remitente es útil cuando quieres todos los eventos emitidos por una dirección específica, independientemente del tipo. El filtro es Sender con la cadena de dirección. Esto es común para feeds de actividad de monederos y para auditar las interacciones de una cuenta específica con múltiples paquetes.
Filtrar por digest de transacción es útil cuando ya conoces la transacción y quieres inspeccionar sus eventos. El filtro es Transaction con la cadena de digest. Este es el filtro más preciso y devuelve solo los eventos de esa única transacción, en orden de secuencia.
Ambos filtros admiten la misma paginación por cursor. Si un remitente tiene un historial de eventos muy grande, lo recorrerás con nextCursor igual que con cualquier otro filtro. La ventana de retención sigue aplicándose, por lo que los eventos de remitente muy antiguos pueden no estar disponibles en un fullnode estándar.
- Sender: todos los eventos de una dirección.
- Transaction: todos los eventos de un digest de transacción.
- Se aplican las mismas reglas de paginación por cursor y retención.
Patrón de sondeo para un indexador continuo
Un indexador continuo debe conservar el último cursor procesado, o el último par (txDigest, eventSeq) procesado, y sondear queryEvents desde ese cursor en un intervalo. Esto garantiza que cada sondeo se reanude exactamente donde se detuvo el anterior, sin huecos ni duplicados. Si prefieres la entrega en tiempo real, la suscripción WebSocket descrita en Suscripciones a eventos por WebSocket de Sui puede enviar eventos a medida que se emiten, pero aún así deberías almacenar un cursor para poder reconciliar tras una desconexión.
Tras una desconexión, vuelve a consultar desde el cursor almacenado para obtener los eventos que se emitieron mientras estabas desconectado. Este paso de reconciliación es lo que hace que el indexador sea fiable. Sin él, una conexión WebSocket caída pierde eventos silenciosamente.
Para indexación de alto volumen, considera agrupar tus consultas y usar un endpoint dedicado. Las páginas de OnFinality sobre servicio de API y precios de RPC describen los planes disponibles y cómo dimensionarlos para el sondeo continuo de eventos.
- Almacena el último cursor o (txDigest, eventSeq).
- Sondea desde ese cursor en un intervalo.
- Usa WebSocket para tiempo real, pero conserva un cursor para la reconciliación.
- Vuelve a consultar desde el cursor almacenado tras una desconexión.
Tabla de resultados: mide los límites de consulta de eventos de tu endpoint
El comportamiento de las consultas de eventos varía según el proveedor y la configuración del nodo. Usa la tabla a continuación para registrar lo que informa tu propio endpoint. Rellena cada fila ejecutando una consulta y observando la respuesta, o consultando la documentación de tu proveedor. No asumas que los valores de otro proveedor se aplican al tuyo.
Para el límite máximo por página, prueba con un límite grande y observa si el endpoint lo limita o devuelve un error. Para la ventana de retención, consulta un evento que sepas que es antiguo y observa si se devuelve. Para la obsolescencia, comprueba si el endpoint devuelve un aviso de obsolescencia o si el proveedor documenta una API de reemplazo.
- Límite máximo por página: [tu valor]
- Ventana de retención: [tu valor]
- Obsoleto en favor de una nueva API de eventos: [sí/no]
- Estabilidad del cursor entre solicitudes: [tu observación]
- Límite de velocidad para consultas de eventos: [tu valor]
Fallos comunes y cómo diagnosticarlos
Los resultados vacíos son el fallo más común. Lo primero que hay que comprobar es si el tipo de filtro es el tipo de estructura totalmente cualificado. Un prefijo 0x ausente, un nombre de módulo incorrecto o una cadena de tipo parcial no devolverán nada. Verifica la cadena de tipo exacta a partir del campo type del evento en una transacción conocida.
Los eventos anteriores a la ventana de retención del nodo también devolverán nada. Si estás consultando el historial y obtienes páginas vacías, comprueba si los eventos están dentro de la ventana de retención. Si no lo están, necesitas un archivo o un indexador dedicado. La página Nodos de archivo de Sui y RPC histórico cubre esto.
El uso incorrecto del cursor provoca bucles o duplicados. Si reutilizas un cursor con un filtro diferente, o si ignoras el cursor y vuelves a consultar desde el principio, procesarás los mismos eventos repetidamente. Almacena siempre el cursor con su filtro y orden, y solo reutilízalo con la misma consulta.
Confiar en la ruta JSON-RPC obsoleta en lugar de la API de eventos actual puede provocar fallos si el endpoint ha eliminado o restringido el método. Consulta la documentación de tu proveedor y las notas de migración de JSON-RPC de Sui antes de construir una integración de larga duración.
- Resultados vacíos: verifica el tipo de estructura totalmente cualificado.
- Resultados vacíos: comprueba la ventana de retención.
- Bucles o duplicados: almacena y reutiliza el cursor correctamente.
- Obsolescencia: verifica la API de eventos actual para tu endpoint.
Limitaciones y compensaciones
Las consultas de eventos están limitadas por la ventana de retención del fullnode. Un fullnode estándar no almacena todo el historial, por lo que los eventos muy antiguos requieren un archivo o un indexador externo. Esta es una compensación fundamental entre el coste de almacenamiento y la capacidad de consulta.
La paginación por cursor es fiable pero no de acceso aleatorio. No puedes saltar a un desplazamiento arbitrario; debes recorrer desde un cursor conocido. Para historiales grandes, esto significa que tu primera consulta puede requerir muchas páginas antes de llegar a los eventos que deseas.
La representación parsedJson es cómoda pero no canónica. Si necesitas verificar o volver a serializar un evento exactamente, usa el campo bcs. El parsedJson puede cambiar de formato entre versiones, así que no dependas de su forma exacta para el almacenamiento a largo plazo.
Los límites de velocidad de consulta de eventos y los límites de tamaño de página varían según el proveedor. OnFinality no publica cifras específicas de latencia o rendimiento de consultas de eventos porque dependen del endpoint y la carga de trabajo. Mide contra tu propio endpoint usando la tabla de resultados anterior.
- La ventana de retención limita las consultas históricas.
- La paginación por cursor no es de acceso aleatorio.
- parsedJson no es canónico; usa bcs para la verificación.
- Los límites de velocidad y de página varían según el proveedor.
Lista de comprobación para la resolución de problemas
Usa esta lista de comprobación cuando una consulta de eventos no se comporte como se espera. Recórrela en orden, porque las causas más comunes también son las más fáciles de comprobar.
Si eres nuevo en Sui RPC, la Guía de Sui RPC (RPC Assistant) proporciona una orientación más amplia. Para lecturas de objetos y campos dinámicos, consulta Leer objetos de Sui: getObject, campos dinámicos y paginación. Para la simulación de transacciones, consulta Simular transacciones de Sui con devInspectTransaction.
- ¿El tipo de filtro está totalmente cualificado con el prefijo 0x?
- ¿El evento está dentro de la ventana de retención del nodo?
- ¿Estás pasando el nextCursor de la página anterior?
- ¿Estás usando el mismo filtro y orden con el cursor?
- ¿El método está obsoleto en tu endpoint?
- ¿Estás alcanzando un límite de velocidad o un límite de tamaño de página?
- ¿Estás leyendo parsedJson cuando deberías leer bcs?
Próximos pasos y lecturas adicionales
Para profundizar, comienza con el centro de aprendizaje de OnFinality para guías relacionadas de Sui, y la Guía de Sui RPC (RPC Assistant) para una orientación método a método. Si necesitas eventos históricos más allá de la ventana de retención de un fullnode, revisa Nodos de archivo de Sui y RPC histórico. Para entrega en tiempo real, consulta Suscripciones a eventos por WebSocket de Sui.
Cuando estés listo para ejecutar consultas de eventos en producción, compara planes en la página de precios de RPC y revisa el servicio de API para endpoints gestionados. Para detalles específicos de la red, consulta la página de la red Sui.
Las fuentes primarias autorizadas para las afirmaciones de este artículo son la página Emitting Events de la documentación de Sui y la Referencia de la API de Sui para suix_queryEvents. Verifica siempre el comportamiento del método contra tu propio endpoint, porque las implementaciones de los proveedores y las ventanas de retención difieren.
- Revisa el centro de aprendizaje de OnFinality para guías relacionadas.
- Consulta los nodos de archivo para eventos históricos.
- Usa suscripciones WebSocket para entrega en tiempo real.
- Verifica el comportamiento contra tu propio endpoint.