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:

POST /ebus/execute
{
  "action": {
    "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".

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:

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:

  • ListGET /objects returns objects with cursor pagination. Filter by template, owner, or properties.
  • SearchPOST /objects/search accepts a structured query payload for more complex filtering across property values, date ranges, and ownership.
  • CountPOST /objects/count returns the number of objects matching a query without fetching the full list. Useful for dashboards and analytics.
  • By IDGET /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.
  • WalletsWallets own objects. Ownership is the primary access control mechanism for object data.
  • ActionsActions are the only way to modify object state (properties, ownership). Direct mutation is not possible — all changes go through the Event Bus for auditability.
  • FacesFaces determine how the object is visually rendered. Since faces are registered on templates, all objects of the same template share the same visual definitions.