Skip to main content

Object Changes

Track real-time state changes to specific on-chain Sui objects. Perfect for building reactive applications that respond instantly when game characters level up, NFTs are modified, or vault balances change.

Overview

Object Changes let you monitor specific on-chain objects and receive instant notifications whenever their state changes. Whether you're tracking an NFT, a game character, a DeFi vault, or any other Sui object, you get real-time updates via Server-Sent Events (SSE).

Unlike Address Events which track all activity at an address, Object Changes focus on individual objects — making it ideal for applications that need to watch specific assets evolve over time.

Use Cases

Gaming Applications

  • Watch game character stats update in real-time
  • Track equipment changes on player avatars
  • Monitor in-game asset modifications (weapons, items, land)
  • Build live leaderboards based on character progression

NFT Platforms

  • Track metadata updates to dynamic NFTs
  • Monitor ownership transfers for specific NFTs
  • Watch trait changes on evolving NFTs
  • Build provenance tracking for high-value assets

DeFi Protocols

  • Monitor specific vault or pool objects for balance changes
  • Track collateral ratios on lending positions
  • Watch oracle price feed updates
  • Build alerts for liquidation risks on specific positions

Real-World Assets (RWA)

  • Track tokenized asset state changes
  • Monitor custody transfers for physical-backed tokens
  • Watch compliance status updates on regulated assets
  • Build audit trails for specific RWA tokens

Social & Identity

  • Monitor profile object updates
  • Track reputation score changes
  • Watch credential or badge modifications
  • Build notification systems for social graphs

How to Subscribe

Endpoint

GET /events?api-key={your_api_key}&last-id={resumption_id}

Base URL (Mainnet): https://flux.surflux.dev

Base URL (Testnet): https://testnet-flux.surflux.dev

Parameters

ParameterRequiredDescription
api-keyYesYour Surflux API key for authentication
last-idNoResume stream from a specific event ID

Stream Resumption with last-id

ValueBehavior
0 (default)Start from the beginning (server buffer limits apply)
$Only receive new events from now onwards
{event-id}Resume from after this specific event ID

Tip: Your application should persist the event ID from each received event. Use it to resume the stream after disconnections without missing object changes.

Connection Limits

Only one client per API key can be connected at a time. Need multiple connections? Contact support for additional API keys.

Event Structure

Object Change Format

{
"type": "object_change",
"timestamp_ms": 1688699385937,
"checkpoint_id": 7021618,
"tx_hash": "AgLyV6xd5wNHa7kojMTEJ9W8no9MK33uf7nH9hPtv8Td",
"data": {
"object_type": "0xee496a0cc04d06a345982ba6697c90c619020de9e274408c7819f787ff66e1a1::suifrens::SuiFren<0xee496a0cc04d06a345982ba6697c90c619020de9e274408c7819f787ff66e1a1::capy::Capy>",
"owner": "0xf239d288e96a672fc3dd019a3afa7ed01757f5940fe0e8500cb3bacd3631ae46",
"contents": {
"id": {
"id": "0xe33f28037520df419f6aaa9885c32101ba34196259c415641fd975a9cedb8108"
},
"field": "a field on the object",
"anotherField": "another field on the object"
}
}
}

Field Descriptions

FieldTypeDescription
typestringAlways "object_change" for object change events
timestamp_msnumberUnix timestamp in milliseconds when the change occurred
checkpoint_idnumberSui checkpoint number where this change was recorded
tx_hashstringTransaction digest that modified this object
data.object_typestringFull type path of the object
data.ownerstringCurrent owner address of the object
data.contentsobjectComplete decoded object state after the change

Contents Structure

The contents field contains the full decoded state of the object. The structure depends on the object's Move type definition:

  • id — Always present; contains the object's unique identifier
  • Custom fields — Project-specific fields defined in the Move contract
  • Nested objects — Complex objects may have nested structures

Code Examples

JavaScript / TypeScript

const apiKey = "your_api_key_here";
const trackedObjectId = "0xe33f28037520df419f6aaa9885c32101ba34196259c415641fd975a9cedb8108";

const eventSource = new EventSource(
`https://flux.surflux.dev/events?api-key=${apiKey}&last-id=$`
);

eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);

if (data.type === "object_change") {
const objectId = data.data.contents?.id?.id;

// Filter for our tracked object
if (objectId === trackedObjectId) {
console.log("Object changed:", data.data.object_type);
console.log("New owner:", data.data.owner);
console.log("Updated contents:", data.data.contents);

// Persist event ID
localStorage.setItem("lastEventId", event.lastEventId);
}
}
};

eventSource.onerror = (error) => {
console.error("Connection error:", error);
eventSource.close();

// Implement reconnection logic here
};

Track Multiple Objects

const trackedObjects = new Set([
"0xe33f28037520df419f6aaa9885c32101ba34196259c415641fd975a9cedb8108",
"0xabc123def456...",
"0x789xyz..."
]);

eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);

if (data.type === "object_change") {
const objectId = data.data.contents?.id?.id;

if (trackedObjects.has(objectId)) {
console.log(`Tracked object ${objectId} changed`);
handleObjectUpdate(objectId, data.data);
}
}
};

function handleObjectUpdate(objectId: string, objectData: any) {
// Update your application state
// Trigger UI updates
// Send notifications
}

Track Ownership Changes

const objectOwners = new Map<string, string>(); // objectId -> owner

eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);

if (data.type === "object_change") {
const objectId = data.data.contents?.id?.id;
const newOwner = data.data.owner;

if (objectOwners.has(objectId)) {
const oldOwner = objectOwners.get(objectId);

if (oldOwner !== newOwner) {
console.log(`Object ${objectId} transferred`);
console.log(`From: ${oldOwner}`);
console.log(`To: ${newOwner}`);
}
}

objectOwners.set(objectId, newOwner);
}
};

Monitor Game Character Stats

interface GameCharacter {
id: { id: string };
name: string;
level: number;
health: number;
experience: number;
equipment: any[];
}

eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);

if (data.type === "object_change") {
const objectType = data.data.object_type;

// Filter for game character objects
if (objectType.includes("::game::Character")) {
const character = data.data.contents as GameCharacter;

console.log(`Character ${character.name} updated`);
console.log(`Level: ${character.level}`);
console.log(`Health: ${character.health}`);
console.log(`XP: ${character.experience}`);

// Update UI, trigger level-up animations, etc.
updateCharacterDisplay(character);
}
}
};

Best Practices

  • Filter by Object ID — The stream includes all object changes on the blockchain. Always filter for your specific object IDs on the client side.
  • Track Object Type — Use object_type to filter for specific kinds of objects (e.g., all NFTs from a collection, all vaults from a protocol).
  • Maintain Local State — Build a local cache of tracked objects and update it as change events arrive. This gives you instant access to current state.
  • Handle Ownership Transfers — When owner changes, update your tracking accordingly. The object may no longer be relevant to your use case after a transfer.
  • Process Complex Objects — Some objects have deeply nested contents. Parse them carefully and extract only the fields you need.
  • Monitor Performance — Tracking many objects can generate significant event volume. Consider batching UI updates or using debouncing to maintain performance.
  • Persist Event IDs — Always save the last event ID to enable seamless SSE stream resumption after connection interruptions.