๐งบ Indexes
Indexes are the second major architecture layer after Digests i.e., A Direct Digest.
If Digest is:
single economic state anchor
then Index is:
a grouped digest structure with weighted ownership
It allows one commitment to safely distribute value across many digests.
This is used for:
- ๐ staking baskets
- โ๏ธ weighted validators
- ๐ณ grouped governance targets
- ๐ portfolio commitments
- ๐ distributed protocol positions
๐ฏ What an Index Really Isโ
An Index is
- not balance.
- not ownership.
- not a commitment.
An Index is:
an indirect or a pointer digest
that points to multiple underlying digests using shares.
Index Digest -> Entry Digests -> Real economic balances
The index itself is still a digest:
type IndexDigest = Digest
which means:
type IndexDigest = AccountId
It behaves like a digest, but internally distributes across entries.
Exampleโ
Instead of:
Alice -> Validator A
we now support:
Alice -> Validator Basket
|-- A (40%)
|-- B (35%)
|-- C (25%)
Alice commits only once.
The pallet distributes across all entry digests.
๐งฉ The CommitIndex Traitโ
This trait defines all index operations.
impl CommitIndex<Proprietor<T>> for pallet_commitment::Pallet<T, I> { ... }
It extends commitment behavior (Commitment trait) from:
single digest
to
digest collection with weighted shares
This is the architecture boundary between direct commitments and grouped commitments.
Key Methodsโ
| Key Methods | Purpose |
|---|---|
index_exists(reason, index_of) | Verifies that an index digest is registered and available for grouped commitments |
get_index(reason, index_of) | Returns the complete IndexInfo, including entries, shares, variants, and internal structure |
get_index_value(reason, index_of) | Calculates the current live aggregate value of the index by summing all underlying real-time entry digest values |
prepare_index(who, reason, entries) | Validates and constructs an immutable index structure IndexInfo from (digest, shares) entry pairs |
gen_index_digest(from, reason, index) | Generates a deterministic unique digest that represents the prepared index structure |
set_index(who, reason, index, digest) | Registers the prepared index under its digest and makes it usable inside the commitment system |
set_entry_shares(who, reason, index_of, entry_of, shares) | Updates one entry's share allocation by rebuilding the index and producing a new index digest |
reap_index(reason, index_of) | Permanently removes an index when no active commitments or remaining balances exist |
These are the primary methods that define the index lifecycle.
๐ท๏ธ Core Typesโ
Index Digestโ
type IndexDigest = Digest
The identifier of the index itself.
This is what users commit to.
Entry Digestโ
type EntryDigest = Digest
The underlying digest inside the index.
This is where actual lazy balances live.
Sharesโ
type Shares = Config::Shares
Compile-time configured weighted ownership unit (unsigned integer).
Used to determine proportional allocation.
Examples:
- 100 shares total
- A = 40
- B = 35
- C = 25
Shares are converted safely into asset math.
EntryInfoโ
Every entry inside an index is represented by EntryInfo structure.
It stores:
- entry digest
- shares
- semantic variant
This means an entry can point to:
Digest + Variant + Share Weight
not just digest alone.
Example:
Validator A
-> Positive Variant
-> 40 Shares
This makes indexes variant-aware via IndexVariant trait dispatch as CommitIndex takes in a default variant as fallback.
IndexInfoโ
This is the main index structure.
It stores the full index definition including:
- index digest
- bounded entries collection
- share totals
- internal invariant checks
Think of it as:
Index = digest registry + allocation rules
It does not store user ownership.
It stores index topology.
๐๏ธ Storage Mapsโ
Indexes require multiple maps.
This is where most people get confused.
IndexMapโ
(Reason, IndexDigest) -> IndexInfo
This stores:
what the index IS
Meaning:
- which entries exist
- what shares they have
- what variants they use
This is structure storage.
Not user ownership or balance depots.
EntryMapโ
(Reason, IndexDigest, EntryDigest, Proprietor) -> Commits
This stores:
what a specific user owns, inside each entry
This is extremely important.
Because resolving an index requires per-entry receipts. Not just one top-level receipt.
So every deposit creates:
many receipts
one for each entry digest and those receipts are stored here.
CommitMapโ
(Proprietor, Reason) -> CommitInfo
This still exists as seen in Base Commitment. It points to the Index Digest, not the entry digests.
This is the user's top-level commitment identity and thus the one commitment per reason invariant is achieved.
๐ Deposit Flowโ
User calls via Commitmentโ
place_commit(.., index_digest, ..)
System determinesโ
DigestVariant::Index(...)
using:
DigestModel::determine_digest(...)
Then Internal Helpers callโ
CommitDeposit::deposit_to_index(...)
// or its equivalent function
Internal flowโ
-> read IndexInfo
-> iterate entries
-> split value using shares
-> deposit into each entry digest
-> create one receipt per entry
-> store receipts in EntryMap
-> save top-level CommitMap (placeholder)
- This is not one deposit.
- It is many coordinated deposits.
๐ก Index Invariantsโ
Indexes preserve strict invariants to keep grouped commitments safe and deterministic.
This includes:
- ๐งฑ stable index per digest
- ๐ฆ bounded entries
- ๐ unique digests
- ๐ซ no duplicate entries
- โ๏ธ valid non-zero shares
- ๐งฎ safe share totals
- ๐ฏ stable variants
- ๐งพ immutable receipts
- โก lazy resolution via entries
- ๐งน reap only when empty
These invariants ensure that deposits, resolution, ownership tracking, and final settlement remain economically correct.
โก Lazy Resolution of Indexesโ
Exactly like digest architecture:
receipts are immutable
but
entry digests change (mutate)
Example:
Alice commits to index (A, B, C)
A gets rewards
B gets slashed
C stays neutral
When Alice resolves:
withdraw from all entries
-> aggregate final result
-> return one final amount
She never updates manually and the system resolves lazily during commit-resolution.
This is why indexes are composable.
๐งฎ Why EntryMap Existsโ
This is one of the most misunderstood parts of index architecture.
A common question is:
Why not store only one index-level receipt?
Because:
the index itself has no real balance
It is only a grouped digest structure.
The actual committed value lives inside:
entry digests
Each entry digest holds its own lazy balance and evolves independently.
That means final resolution must happen from:
every underlying digest
which requires:
every underlying receipt
That is why:
EntryMap is mandatory
Every deposit into an index creates multiple receipts:
one receipt per entry digest
and those are stored inside EntryMap.
๐ง Why CommitMap Still Existsโ
Even though actual commit instances live in EntryMap, CommitMap still matters.
(Proprietor, Reason) -> CommitInfo
stores the top-level ownership identity.
It points to the Index Digest, not the entry digests.
This is how indexes safely build on top of the base commitment invariant:
one proprietor -> one commitment per reason
Instead of creating many direct commitments, ownership remains singular at the index level, while actual receipts are distributed indirectly across many entry digests.
That indirection is what makes grouped commitments possible.
๐งฑ Indexes Are Immutableโ
Once an index is created, it cannot be modified in place.
This guarantees:
- stable receipt resolution
- deterministic ownership tracking
- safe lazy balance evaluation
- upgrade-safe invariant preservation
Receipts depend on index structure remaining stable.
If the structure changed directly, old receipts could become invalid.
Immutability prevents that.
๐ก What Index Construction Enforcesโ
Every index must satisfy:
- bounded entries
- deterministic uniqueness
- non-zero valid shares
- safe share totals
- stable semantic variants
This ensures the index remains safe for:
deposit
resolve
reap
live value inspection
throughout its full lifecycle.
That is why fields remain private and mutation only happens through controlled methods.
๐ Changing an Entry Creates a New Indexโ
If an entry must change, the old index is never mutated.
Instead:
set_entry_shares()
-> rebuild index
-> generate new index digest
This creates a completely new index digest.
The previous index remains valid and continues serving all existing commitments until they are resolved and reaped.
This preserves safety for historical receipts.
๐งน Reaping an Indexโ
An index can only be removed when:
no active commitments and no remaining funds
exist.
Only then can reap_index() safely delete it.
This prevents accidental destruction of active economic state.
๐ Why Pools Existโ
Indexes are designed for deterministic grouped structure.
They are not built for active live management.
For continuously changing allocation strategies:
indexes are too strict
That is why pools exist.
Index = immutable grouped structure
Pool = managed dynamic allocation
- Pools support manager-controlled reallocation.
- Indexes preserve strict invariant safety.
Both exist for different purposes.
But Both provides non-custodial solutions, one is rigid another is delegatable.
๐ In One Sentenceโ
An Index is an indirect digest that distributes one commitment across many digests using weighted shares.
Digest is one state.
Index is orchestrated multi-state commitment internally.
๐ Next Stepโ
Where indexes become manager-controlled dynamic allocation systems with commissions.
๐ Architecture -> Pools