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.