Schemas
What is Schemas?
In Dubhe, Schemas are a typed on-chain storage layer. When building with Dubhe, developers declare all contract state in a dubhe.config.ts file. The schemagen CLI then generates a complete set of Move modules that handle storage reads, writes, and events automatically.
This replaces the traditional Move approach of defining data structures and table access manually:
Traditional Move storage
// Declare manually
struct Player has store {
level: u32,
hp: u64,
}
// Store and retrieve manually
let player = Player { level: 1, hp: 100 };
table::add(&mut storage, addr, player);
let p = table::borrow(&storage, addr);Dubhe Schemas
// dubhe.config.ts — declare once
resources: {
level: 'u32',
hp: 'u64',
}// Generated Move — type-safe, no boilerplate
level::set(dh, player, 1, ctx);
let lv = level::get(dh, player);All storage operations emit standardized events that enable zero-code indexing and off-chain data synchronization.
Core storage model
Schemas use a three-level key-value store backed by Dubhe’s shared DappHub object:
DappHub → dapp_key → resource_account → key_tuple → value- DappHub — a single shared object that holds all dapp state on chain.
- dapp_key — a unique identifier per published package derived from its
type_name. - resource_account — a
Stringthat identifies the entity (typically an address). Omitted for global resources. - key_tuple — zero or more additional key fields defined in the config.
Resource patterns
There are six patterns depending on the combination of global, keys, and fields:
| Pattern | Config shape | Primary use case |
|---|---|---|
| Global singleton | global: true | Package-level counters, config flags |
| Entity single value | 'MoveType' shorthand | Simple per-player properties |
| Entity multi-field record | fields only | Structs keyed by entity |
| Keyed single value | fields + keys (1 value field) | Maps like (player, item_id) → quantity |
| Keyed multi-value record | fields + keys (multiple value fields) | Rich indexed data |
| Offchain | offchain: true | Events only, no on-chain storage |
See Config Reference for the full API generated by each pattern.
Enum and error support
Custom enum types can be declared alongside resources and used as field or key types. Each enum compiles to a Move module with constructors, matchers, and BCS helpers.
Custom error codes compile to assertion helpers that abort with a message string, making contract revert reasons immediately readable.
Why Schemas?
Schemas address several hard problems in on-chain application development:
- Separation of state from logic — business logic lives in system contracts; all state lives in
DappHub. Upgrading logic never migrates data. - Granular access control — different system contracts can access different slices of the same data store.
- Automatic sync to frontends — all writes emit standardised events that a Dubhe indexer can consume without any extra code.
- Cross-contract queries — any contract that receives
DappHubcan read data from any registered dapp. - Gas efficiency — values are tightly packed in the underlying storage, often cheaper than hand-written table access.