Objects

Tokenized instances of templates representing real-world or digital assets.

What are Objects?

Objects are the core asset unit in Dual, tokenized representations of real-world or digital items. Each object is an instance of a template and carries its own state, ownership, and activity history. Objects can represent anything: event tickets, loyalty points, product authenticity certificates, property deeds, or digital collectibles.

Every object has a unique id, belongs to an organization, is owned by a wallet, and references the template it was minted from. Its properties can be read, updated, and transferred through actions.

Emitting (Creating) Objects

Objects are not created directly, they are emitted through the Event Bus by executing an emit action. This ensures every object creation is signed, logged, and sequenced as part of the platform's audit trail:

Code
POST /ebus/actions
{
"action_type": "io.acme.product.emit",
"template_id": "tmpl_abc123",
"properties": {
"serial_number": "SN-2024-001",
"manufacture_date": "2024-01-15"
}
}

You can also create objects through the convenience endpoint POST /objects for simpler use cases where full Event Bus semantics aren't needed.

Object Properties

An object's properties are a JSON object inherited from its template and potentially overridden at emission time. Properties can be updated through actions, for example, an action might change a ticket's status from "valid" to "redeemed".

Code
GET /objects/obj_xyz
→ {
"id": "obj_xyz",
"template_id": "tmpl_abc123",
"owner_id": "w_alice",
"properties": {
"serial_number": "SN-2024-001",
"manufacture_date": "2024-01-15",
"status": "valid"
},
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}

Ownership & Transfers

Every object is owned by exactly one wallet at any given time. Ownership is recorded in the owner_id field. Transfers happen through actions, the Event Bus verifies that the sender is the current owner, then atomically updates owner_id to the recipient's wallet.

Ownership history is preserved in the object's activity log, providing a complete chain of custody.

Object Relationships

Objects can have parent-child relationships, enabling hierarchical asset structures. This is useful for modeling:

  • Bundle → Items, A "gift box" object that contains individual product objects.
  • Collection → Members, An album object with individual trading card objects.
  • License → Sub-licenses, A master license with derived usage licenses.

Query relationships with GET /objects/{id}/children and GET /objects/{id}/parents. Both endpoints support cursor pagination.

Activity Log

Every action performed on an object is recorded in its activity log, providing a complete and immutable audit trail. The activity log answers "who did what, when" for every state change:

Code
GET /objects/obj_xyz/activity
→ {
"items": [
{
"id": "act_001",
"action_type": "io.acme.product.emit",
"status": "confirmed",
"payload": { ... },
"created_at": "2024-01-15T10:30:00Z"
},
{
"id": "act_002",
"action_type": "io.acme.product.transfer",
"status": "confirmed",
"payload": { "to": "w_bob" },
"created_at": "2024-01-16T14:00:00Z"
}
],
"next": null
}

Activity logs are immutable, entries cannot be edited or deleted. They are backed by the Sequencer's hash chain, meaning any tampering would break the cryptographic integrity of the entire batch.

Search & Discovery

Dual provides several ways to find objects:

  • List, GET /objects returns objects with cursor pagination. Filter by template, owner, or properties.
  • Search, POST /objects/search accepts a structured query payload for more complex filtering across property values, date ranges, and ownership.
  • Count, POST /objects/count returns the number of objects matching a query without fetching the full list. Useful for dashboards and analytics.
  • By ID, GET /objects/{id} retrieves a single object by its unique identifier.

Relationship to Other Concepts

  • Templates, Every object is minted from a template. The template defines the schema; the object carries the state.
  • Wallets, Wallets own objects. Ownership is the primary access control mechanism for object data.
  • Actions, Actions are the only way to modify object state (properties, ownership). Direct mutation is not possible, all changes go through the Event Bus for auditability.
  • Faces, Faces determine how the object is visually rendered. Since faces are registered on templates, all objects of the same template share the same visual definitions.