Derived Objects
Derived objects are Sui objects with deterministic IDs computed from a parent object and a key. You can predict a derived object's ID offchain before the transaction that creates it runs, which enables indexer-free lookups, batch queries, and state verification.
When to use this pattern
Use this pattern when you need to:
-
Look up an object by a known key (a user address, a string identifier, a counter) without an indexer or event query.
-
Predict an object's ID before creating it, so the frontend or another contract can reference it immediately.
-
Build paginated collections where each item's ID is derived from a sequential counter, enabling reverse-order traversal without an indexer.
-
Create 1-to-1 mappings between a parent object and child records (for example, a user profile per game, a config per module).
-
Avoid dynamic fields when you need the derived object to be a standalone top-level object with its own ownership and transferability.
What you learn
This example teaches:
-
Derived objects: Top-level Sui objects whose IDs are deterministically computed from a parent UID and a serialized key. The same parent + key always produces the same ID.
-
Derivation strategies: 4 key types that cover different use cases:
u64(incremental counter),address(per-user objects),String(named resources), and custom structs (composite keys). -
Offchain ID prediction: The
deriveObjectIDfunction in the TS SDK computes the same ID the chain computes, so you can reference an object before it exists. -
Indexer-free queries: Because IDs are deterministic, you can enumerate objects by iterating over a known key space (for example, counter values 0 through N) and computing each ID locally.
Architecture
The example has 2 components: a Move package that creates derived objects onchain, and a TypeScript test suite that predicts IDs offchain and verifies them against Testnet. The diagram below traces the derivation flow for 1 object.
The following steps walk through the flow:
-
The TypeScript test computes the expected object ID offchain using
deriveObjectID(parentObjectId, keyType, serializedKey). No network call is needed. -
The test builds and submits a transaction that calls 1 of the 4
create_*entry functions with the parent object and the key. -
The Move function calls
derived_object::claimwith the parent's mutable UID reference and the key. The chain computes the object ID from the same inputs. -
The newly created object appears at exactly the ID the test predicted. The test asserts they match.
This pattern means any client that knows the parent ID and the key can compute the derived object's ID without querying the chain.
Learn more about derived objects.
Prerequisites
- Prerequisites
-
Download and install an IDE. The following are recommended, as they offer Move extensions:
-
VSCode, corresponding Move extension
-
Emacs, corresponding Move extension
-
Vim, corresponding Move extension
-
Zed, corresponding Move extension
Alternatively, you can use the Move web IDE, which does not require a download. It does not support all functions necessary for this guide, however.
-
-
Node.js 18 or later
Setup
Follow these steps to set up the example locally.
Step 1: Clone the repo
$ git clone -b solution https://github.com/MystenLabs/sui-move-bootcamp.git
$ cd sui-move-bootcamp/C5
Step 2: Publish the Move package
$ cd contracts/derived_objects
$ sui client switch --env testnet
$ sui move build
$ sui client publish --gas-budget 200000000
Record the package ID and parent object ID from the output.
│ ┌── │
│ │ ObjectID: 0xcb497ec47a994a71c04c351cd307682c49b31f586251cb51aa0acae3cb91d59e <---- Parent Object ID │
│ │ Sender: 0x9ac241b2b3cb87ecd2a58724d4d182b5cd897ad307df62be2ae84beddc9d9803 │
│ │ Owner: Shared( 847518303 ) │
│ │ ObjectType: 0x67a9682e05ec2a0023f3500f1b4d07c7569b5ed4929b350b61d0bb0453f4867::parent::ParentObject │
│ │ Version: 847518303 │
│ │ Digest: 76emagtHjhr6kVeFiS4QA2YjYchHdu2BF1QpyczS3sUX │
│ └──
...
│ Published Objects: │
│ ┌── │
│ │ PackageID: 0x067a9682e05ec2a0023f3500f1b4d07c7569b5ed4929b350b61d0bb0453f4867 <----- Package ID │
│ │ Version: 1
Step 3: Configure the TypeScript tests
$ cd ../../ts
$ bun install
$ cp .env.example .env
Edit .env with the values from the publish step. You can get your USER_SECRET_KEY from the command sui keytool export --key-identity <ADDRESS>
NETWORK=testnet
PACKAGE_ID=PACKAGE_ID_FROM_STEP_2
PARENT_OBJECT_ID=PARENT_OBJECT_ID_FROM_STEP_2
USER_SECRET_KEY=YOUR_TESTNET_PRIVATE_KEY
DERIVED_STRUCT_KEY_SIGNATURE=DerivedObjectStructKey
Run the example
Run the test suite against Testnet:
$ bun test
The tests create derived objects using each of the 4 key types, predict their IDs offchain, and assert the onchain results match. A passing suite confirms that offchain ID prediction works correctly for all derivation strategies.
Key code highlights
The following snippets are the parts of the code worth reading carefully.
Parent object with incremental counter
The ParentObject struct holds the shared UID that all derived objects derive from, plus an incremental counter for sequential derivation.
C5/contracts/derived_objects/sources/parent.move. You probably need to run `pnpm prebuild` and restart the site.The uid_mut_ref function exposes the parent's UID as a mutable reference, which derived_object::claim requires. The incremental_counter gives each sequentially derived object a unique key.
Offchain ID prediction
The deriveObjectIDFromParent helper computes a derived object's ID using the Sui SDK, without any network call.
C5/ts/src/helpers/deriveObjectID.ts. You probably need to run `pnpm prebuild` and restart the site.The function serializes the key with BCS, passes it to deriveObjectID along with the parent object ID and the key's type tag, and returns the predicted ID. The tests verify this matches the onchain result.
Common modifications
-
Per-user profiles: Use
create_addresswith the player's wallet address as the key. Each user gets exactly 1 derived profile object per game. Look it up by computingderiveObjectID(gameId, "address", userAddress). -
Named configuration objects: Use
create_stringwith config names like"fee_schedule"or"whitelist". Any client can find the config object by name without querying events. -
Batch create and verify: Build a PTB that calls
create_incrementalmultiple times in a single transaction. Predict all IDs offchain from the current counter value before submitting, then verify the batch.