Branching Oracle Layer
Protocol draft • Base layer for Placeholder
Zoltar White Paper
A visual guide to Zoltar universes, deterministic question encoding, scalar outcomes, and Colored Coins-style REP branching.
Abstract
#Zoltar is the base oracle layer in this repository. It does not run a market. It registers questions, records forks, and lets REP holders move value from a parent universe into one or more child universes after a fork.
Zoltar preserves disagreement explicitly. Instead of picking one winner onchain when a dispute becomes irresolvable inside one branch, Zoltar creates the valid child branches and lets later coordination determine which branch matters economically.
Zoltar supports both categorical and scalar questions, treats
Invalid as a legitimate answer state, and keeps market
collateral, trading, and underwriting logic out of the oracle layer.
In Zoltar terminology, Invalid is a valid answer branch
for a well-formed question, while Malformed means an
encoded answer option is rejected before it can become a branch or
final answer.
Lifecycle Timeline
#Zoltar's lifecycle has six contract-visible steps, from question registration through post-fork coordination.
ZoltarQuestionData stores the question and defines its valid answer space.
After the question end time, any caller with threshold REP can call forkUniverse; applications decide when disagreement justifies doing so.
Zoltar records the fork question and deterministically defines every valid child branch.
Branches exist by deterministic id first; contracts are deployed only when needed.
Migration balances mint child REP into one or more selected, non-malformed branches.
The protocol does not pick a winner. Users and applications decide where durable activity continues.
1. System Overview
#Zoltar has one job: define the truth-layer objects that higher-level applications can rely on. It does not implement the market, collateral, or underwriting system built on top of it elsewhere in the repo. Its role is narrower and more fundamental:
- register questions
- encode valid answer spaces
- represent forks as child universes
- mint and burn child-universe REP
- turn migration balances into selected child REP
In practice, that means an application can hand Zoltar a question, later trigger a fork if disagreement persists, and then consume the child-universe structure that Zoltar defines. Applications therefore depend on the same order in operation: question, disagreement, fork, then child-universe state.
Core Contract Map
Zoltar: universe forks and REP splittingZoltarQuestionData: question registry and outcome encodingReputationToken: child-universe REP minted and burned by ZoltarScalarOutcomes: scalar formatting and interpolation logic
Zoltar owns universe branching and splitMigrationRep; ZoltarQuestionData owns question validity.
The market, vault, collateral, and auction systems live above this layer and are intentionally not encoded into Zoltar.
Core Layer Boundary
Core Layer BoundaryZoltar is the branching oracle layer: applications feed it questions and later consume the child-universe structure it defines.
2. Universe Model
#
A universe is one branch of protocol state. The
Zoltar.Universe
struct stores the minimum data needed to identify that branch and
connect it to its parent:
forkTime: when the universe forkedforkQuestionId: the question id recorded byforkUniverseand used to define child branchesforkingOutcomeIndex: the outcome index represented by the universe when it is a childreputationToken: the REP token used inside that universeparentUniverseId: the parent branch
Universe 0 is the genesis universe. Its REP token is an external
genesis token configured in Constants.GENESIS_REPUTATION_TOKEN.
Child universes are identified deterministically as:
Child Universe IdChild universe ids are deterministic hashes of the parent universe and outcome index.
Deterministic child ids make each branch reproducible from parent universe and outcome index alone. Child universes are deployed lazily when a forked branch is actually needed.
forkUniverse records the parent fork, and deployChild materializes a deterministic child universe.
Child ids are derived from parentUniverseId and outcomeIndex, so callers can predict branch ids before deployment.
Fork Branch Set
Fork Branch SetA fork creates the full valid branch set; no branch is privileged by the contract.
Global Question Scope
Questions are global protocol objects in Zoltar. forkUniverse checks
that the target universe exists, still has supply, has not already forked, and
that the supplied question exists and has ended. It does not require the question
to have been created for that universe. Applications that need a stricter
universe/question relationship enforce it above Zoltar.
Question Registry vs Application Binding
Question Registry BoundaryZoltar enforces global question validity and fork timing. Higher-level protocols decide whether a specific question is eligible for a specific market or pool.
3. Fork Thresholds and REP Economics
#
The threshold is a REP commitment large enough to force branch creation, but it
is not itself a choice of the winning branch. The constructor stores
forkThresholdDivisor; the current deployment default is
20.
Fork ThresholdWith the default divisor of 20, the threshold is one twentieth of theoretical REP supply, rounded down by Solidity integer division.
Initiating a fork requires supplying the computed threshold. With the
default divisor, that is 5% of the universe's theoretical supply before
integer flooring. The full threshold deposit leaves the parent universe:
forkUniverse burns or transfers that entire parent-REP amount and
reduces the parent theoretical supply by the full threshold. The burn divisor
then controls how much child-REP minting credit the initiator receives. Under
the current deployment default forkBurnDivisor = 5, one fifth of
the threshold is an uncredited haircut and the remaining four fifths becomes
the initiator's migration balance.
Fork HaircutThe default uncredited share is one fifth of the threshold deposit, rounded down; the full parent-REP threshold is still burned or transferred out of the parent universe.
Initiator Migration BalanceWith the default burn divisor of 5, the initiator receives the threshold minus the floored one-fifth haircut.
forkUniverse applies the threshold, burns or transfers the full parent-REP threshold out of the parent universe, reduces parent theoretical supply by that full amount, and credits only the post-haircut migration balance to the initiator.
Genesis REP is transferred to BURN_ADDRESS; child REP can be burned directly by ReputationToken.
Threshold Deposit Split
Threshold Deposit SplitThe full parent-REP threshold leaves parent supply. Most of that amount is re-expressed as migratable child-REP credit, while the haircut is not credited to the initiator.
Genesis REP cannot be burned natively, so the contract transfers it to the
configured burn address. Child-universe REP is minted and burned directly by
ReputationToken
under Zoltar’s control.
A child's theoretical supply is a maximum for REP that can be minted in that branch. It starts from the parent's pre-fork theoretical supply and subtracts only the uncredited haircut. The initiator's remaining threshold deposit is migration credit, so it remains part of the maximum amount that could be minted in each child.
Repeated-fork threshold decay
Along one lineage, let theoreticalSupplyBeforeForkₙ be the
theoretical supply before fork n. With the mainnet threshold divisor
of 20 and burn divisor of 5, the exact integer recurrence is
forkThresholdₙ = floor(theoreticalSupplyBeforeForkₙ / 20),
haircutₙ = floor(forkThresholdₙ / 5), and
theoreticalSupplyBeforeForkₙ₊₁ = theoreticalSupplyBeforeForkₙ - haircutₙ.
This is close to multiplying supply by 0.99 at each generation,
but both Solidity flooring operations matter once supply becomes small.
25,501,749 on July 10, 2026, calling
getTotalTheoreticalSupply() on REPv2 at
0x221657776846890989a759BA2973e427DfF5C9bB returned
7,825,488.326666847200078019 REP. Starting from that value, the
default recurrence reaches three relevant boundaries: at child depth
1,213, the fork threshold is approximately 1.986 REP, so
half of it no longer exceeds the configured 1 REP escalation
starting bond; at depth 1,282, the fork threshold is below
1 REP; and at depth 5,303, theoretical supply is
99 wei, the fork threshold is 4 wei, and the haircut
floors to zero. Supply then stops decreasing along further descendants rather
than reaching a zero fork threshold.
These depths require more than a thousand consecutive forks along the same
surviving branch before escalation parameters become incompatible. They are
therefore remote limits rather than near-term fork incentives. Implementers
should nevertheless treat the first boundary as an explicit parameter limit:
SecurityPool initializes an escalation game's non-decision threshold
as half the current fork threshold, while EscalationGame requires that
value to be strictly greater than its starting bond. A deployment expected to
survive thousands of recursive forks needs either a minimum fork threshold or
a compatible rule for scaling escalation parameters.
| Parameter | Current value | Meaning |
|---|---|---|
GENESIS_REPUTATION_TOKEN |
0x221657776846890989a759BA2973e427DfF5C9bB |
Genesis-universe REP token address |
BURN_ADDRESS |
0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF |
Burn sink used for genesis REP |
forkThresholdDivisor |
20 |
Constructor immutable. Fork threshold is floor(totalTheoreticalRepSupply / forkThresholdDivisor); the default deployment value makes it about 5% of supply. |
forkBurnDivisor |
5 |
Constructor immutable. The uncredited migration haircut is floor(forkThreshold / forkBurnDivisor); the full threshold parent-REP amount still leaves parent supply. |
4. Child Universes and REP Splitting
#
Once a universe forks, child universes can be deployed lazily through
deployChild. A user can also add more REP into the migration balance
with addRepToMigrationBalance. The core post-fork action is
splitMigrationRep, which lets a holder mint child-universe REP for
non-malformed outcome indices. Supplying no outcome indices is accepted as a
no-op at the Zoltar layer.
For categorical questions, Invalid and any in-range categorical
outcome are allowed, while out-of-range values are rejected. For scalar
questions, only well-formed scalar encodings are allowed.
The fork question defines the child-branch shape for the whole universe. A child
branch may therefore be keyed by Invalid, a categorical outcome
index, or a scalar encoding depending on which parent-universe question
forkUniverse used for the fork. Downstream applications should treat
that branch selector as universe-level metadata rather than assume every child
branch is binary.
addRepToMigrationBalance prepares additional REP, and splitMigrationRep mints child REP for selected outcomes.
isMalformedAnswerOption distinguishes valid branches from rejected malformed answers.
Migration Balance Reproduction
Migration Balance ReproductionSelecting multiple branches mints child REP in each selected child universe from the same migration balance.
At the implementation level, splitMigrationRep validates each
selected outcome against the parent universe's fork question, lazily deploys any
missing child universe, and records how much of the caller's migration balance
has already been minted into each child. A caller cannot mint more into one child
than the source migration balance available to that caller, but the same source
balance can be reproduced into multiple valid children.
5. Assumptions and Security Model
#Zoltar is a Colored Coins-style system, so its security argument depends on user behavior and value concentration rather than on the contract being able to identify one objectively correct branch onchain.
- users can choose which child universe to continue using after a fork
- users prefer to continue in the universe they regard as truthful
- the protocol itself does not know which universe is truthful and treats all valid branches symmetrically
- most durable economic activity concentrates in the branch that users expect other users to keep using
- dishonest or abandoned branches may continue to exist, but are assumed to retain little long-term value compared with the branch that market participants keep coordinating around
A fork only creates branches. If users and future activity concentrate in the branch they regard as truthful, the value of child-universe REP also concentrates there. Holders can reproduce a migration balance into multiple valid child universes, subject to the per-child cap and the gas and coordination costs of doing so. The economic pressure is therefore value concentration: REP in an abandoned branch may keep existing, but it is expected to have little long-term value compared with REP in the branch that markets keep using.
Global forks are migrations, not permanent shutdowns
Zoltar questions are global protocol objects: any existing question whose end time has passed can identify the branches of any still-unforked universe. Recording the fork stops new operational changes in parent-universe pools so that their accounting cannot move while it is being snapshotted. Placeholder provides permissionless, user-driven paths to migrate REP, shares, vault positions, collateral, and unresolved escalation state into child-universe pools. A child pool can return to the operational state once the migration window and any required truth-auction and finalization transactions complete. Timely progress therefore depends on callers submitting those transactions and on the available migration participation. The parent boundary is a transition into child universes, not an intended permanent freeze of market activity.
A caller cannot trigger that transition for free under normal parameters. The caller supplies the full fork threshold in parent REP, loses the uncredited haircut, and receives migration credit for only the remainder. Existing REP holders can migrate their own positions and benefit from the reduced supply in the branches where economic activity continues. Consequently, an unrelated fork is economically evaluated as a costly forced migration: its risk is operational migration latency and coordination burden, while its deterrent is the initiator's threshold commitment and permanent haircut.
Likewise, a question with many valid outcomes creates more possible children, but it does not multiply value in the branch that ultimately retains economic coordination. Deploying and using many children costs gas, and REP in abandoned branches is expected to have little value. Outcome count by itself is therefore a gas and state-growth consideration, not a demonstrated value-extraction attack.
6. Questions and Outcome Encoding
#
ZoltarQuestionData.QuestionData
stores title, description, start and end time, scalar metadata such as
numTicks, displayValueMin,
displayValueMax, and answerUnit.
Question ids are deterministic hashes of the question data. For categorical questions, that hash path also includes the sorted categorical outcome options. For scalar questions, there are no categorical labels to include, so the id is determined from the scalar question fields alone.
Categorical Questions
Categorical questions store sorted outcome labels. The implementation requires
labels to be non-empty and strictly ordered by hash: each label's
keccak256(abi.encode(label)) value must be lower than the previous
label's hash, so callers provide labels in descending hash order. The contract
stores the labels in outcomeLabels[questionId]. Any number of categorical
outcomes can exist at the Zoltar level as long as those conditions hold.
Scalar Questions
Scalar questions store no categorical labels. Instead, numTicks,
displayValueMin, displayValueMax, and
answerUnit define the answer space. At creation time, scalar
questions must satisfy numTicks > 0 and
displayValueMax > displayValueMin.
Onchain, each scalar answer is encoded into a single uint256 that packs:
- the highest bit as an invalid flag
- a 120-bit first payout numerator
- a 120-bit second payout numerator
createQuestion stores categorical or scalar questions, and ScalarOutcomes formats scalar displays.
The two 120-bit payout numerators must sum to numTicks; otherwise the answer is malformed, not invalid.
Packed Scalar Answer
Packed Scalar AnswerA scalar answer is easier to read as a namespace flag plus two payout fields: 0 means invalid namespace, while 1 means the payout fields must sum to numTicks.
The all-zero encoding is the canonical Invalid answer for scalar
questions. For a valid scalar answer, the two payout numerators must sum exactly
to numTicks:
Scalar ValidityA valid scalar answer must allocate all ticks across the two payout numerators.
At the helper and UI level, a scalar tick index named tickIndex is
encoded as:
firstPayoutNumerator = numTicks - tickIndexsecondPayoutNumerator = tickIndexhighest bit = 1
Scalar Tick EncodingThe helper encodes a tick as left and right payout numerators.
ScalarOutcomes
interprets secondPayoutNumerator as the position along the scalar
range:
Scalar Display ValueThe contract interpolates the scalar answer with mulDiv, so uneven divisions round down before the value is formatted.
displayValueMin and displayValueMax are stored
as 18-decimal fixed-point display bounds. Formatting divides the atomic
result by 1e18 and trims trailing zeroes. For example, with
displayValueMin = 0, displayValueMax = 10e18,
numTicks = 6, and secondPayoutNumerator = 1,
the atomic value is floor(10e18 / 6), which displays as
1.666666666666666666 rather than an unrounded real number.
7. Question Types Supported by Zoltar
#
At the Zoltar layer, market type support comes from how questions and outcomes
are encoded in ZoltarQuestionData.
- categorical questions, implemented as an ordered array of non-empty outcome labels
- scalar questions, implemented as a tick-based numeric range with no categorical labels
Zoltar can represent arbitrary categorical questions and scalar questions, while higher-level protocols may choose narrower market shapes on top of it. It defines answer spaces and forkable resolution state, not the collateralized trading mechanics that may later be attached to those questions.
8. Invalid vs Malformed
#Zoltar distinguishes Invalid answers from Malformed answers.
| Term | Meaning | Effect |
|---|---|---|
Invalid |
A legitimate resolution state. | Can be a valid branch and final outcome. |
Malformed |
A submitted outcome index or scalar encoding that does not fit the question’s answer space. | Rejected during child-universe REP splitting and fork-aware asset branching. |
This distinction matters because malformed answers are rejected, while
Invalid remains a valid branch and a valid final outcome.
9. Example Fork Lifecycle
#
Consider a universe parentUniverse and a question
forkQuestion. After that question ends,
forkUniverse can record it as the fork question for
parentUniverse.
forkUniverse(parentUniverse, forkQuestion)recordsforkQuestionas the fork question forparentUniverse.- Child universes are defined deterministically from
parentUniverseand each valid outcome index. - The fork initiator’s full parent-REP deposit leaves parent supply, and the post-haircut amount becomes migration balance.
- Any REP holder can add more REP into migration balance for the forked universe.
- REP holders call
splitMigrationRepto mint child-universe REP across the outcome branches they support.
At that point, Zoltar has turned one disputed universe into multiple child universes with separate reputation tokens. The higher-level economic meaning of those branches is left to protocols built on top.
10. Design Thesis
#Zoltar provides a generalized branching oracle layer for categorical and scalar questions. Rather than forcing a single answer when disagreement persists, it turns unresolved decisions into explicit child universes and lets REP holders split post-fork claims across one or more selected branches.
In that sense it closely follows the Colored Coins model: the protocol branches state first, and later coordination determines which branch carries durable value.