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
| Parameter | Required | Description |
|---|---|---|
api-key | Yes | Your Surflux API key for authentication |
last-id | No | Resume stream from a specific event ID |
Stream Resumption with last-id
| Value | Behavior |
|---|---|
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
| Field | Type | Description |
|---|---|---|
type | string | Always "object_change" for object change events |
timestamp_ms | number | Unix timestamp in milliseconds when the change occurred |
checkpoint_id | number | Sui checkpoint number where this change was recorded |
tx_hash | string | Transaction digest that modified this object |
data.object_type | string | Full type path of the object |
data.owner | string | Current owner address of the object |
data.contents | object | Complete 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_typeto 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
ownerchanges, 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.
Related Streams
- Package Events — Subscribe to smart contract event emissions
- Address Events — Monitor all activity for specific addresses