Branching Oracle Base Layer
Draft
Zoltar White Paper
Zoltar is a generalized branching oracle and coordination layer for binary, categorical, and scalar questions. Rather than forcing a single answer when disagreement persists, it represents unresolved outcomes as explicit child universes. After a fork, REP holders may migrate or split their claims across one or more selected branches.
The design closely follows the Colored Coins model: the protocol branches state first, while applications and their users later coordinate which branch ultimately carries economic value.
Zoltar is not itself a prediction market or an outcome-reporting oracle. Instead, it provides the shared infrastructure for questions, REP tokens, and universe forks. Any application can build on top of it and define its own local dispute process. If disagreement cannot be resolved locally, anyone able to commit the required REP threshold may fork an unforked universe using an eligible ended global question. The resulting child universes become the new foundations from which applications may choose to continue.
Lifecycle Timeline
#Zoltar's lifecycle has six conceptual steps, from question registration through post-fork coordination.
ZoltarQuestionData stores the question and defines its valid answer space.
After the question end date, any address able to supply the threshold REP can call forkUniverse. Zoltar does not verify that a disagreement exists; applications decide when a fork is justified.
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 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 valid universes 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 register a question,
trigger a fork after that question ends, and consume the
child-universe structure that Zoltar defines. Only Zoltar can mint
the child-universe ReputationToken; genesis REP is
separately deployed and supplied to Zoltar. Mainnet uses the existing
external REP deployment, while Sepolia uses the reviewed deterministic
genesis deployment.
Zoltar Contracts
Zoltar: universe forks and REP splittingZoltarQuestionData: question registry and outcome encodingReputationToken: child-universe REP minted and burned by ZoltarScalarOutcomes: scalar formatting and interpolation logic
2. Universe Model
#Security assumptions: A23 verified deployment, A26 Ethereum execution, and A27 cryptographic identity.
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 network-specific REP
token is supplied to the Zoltar constructor. Mainnet uses
the existing REP deployment; Sepolia deterministically deploys a genesis REP
token from the configured initial-holder allocations before deploying
Zoltar.
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.
Universe Tree
Universe treeA 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.
Accepted eligibility boundary Zoltar intentionally accepts any existing ended global question. It does not require a minimum creation age, prior universe registration, or proof that the fork's disruption is smaller than its economic benefit. The fork-threshold REP commitment and configured haircut are the protocol's admission mechanism. A stricter relevance, aging, or cost-benefit rule is not a Zoltar invariant.
3. Fork Thresholds and REP Economics
#
To trigger a Zoltar fork, the caller must commit the
forkThreshold: the parent universe's theoretical REP
supply divided by the configured threshold divisor, rounded down.
The REP threshold and haircut are the intended admission cost for a
fork, including unnecessary forks that impose migration work on
applications using that universe.
Both the threshold and the haircut are intended to make unnecessary forks economically costly. Forking affects applications and users relying on that parent universe; each application chooses which child branch, if any, to continue using.
Security assumptions: A07 fork value preservation, A08 REP valuation, A05 lineage access value, A21 genesis REP behavior, and A25 safe immutable parameters.
Fork ThresholdWith the default divisor of 20, this is approximately 5% of theoretical REP supply, subject to integer flooring.
After triggering the fork, the full threshold leaves the parent
universe: forkUniverse removes that parent REP and
reduces parent theoretical supply by the full threshold. The burn
divisor then controls the child-REP minting credit. With
forkBurnDivisor = 5, the uncredited haircut is
⌊forkThreshold / 5⌋, approximately 20% of the threshold.
The remaining forkThreshold - ⌊forkThreshold / 5⌋ becomes
migration credit: equivalently ⌈4 × forkThreshold / 5⌉,
approximately 80%.
The constructor rejects forkBurnDivisor < 5. This
caps the uncredited haircut at 20%: a larger haircut can leave an
own-fork escalation continuation—the child game carrying unresolved deposits after its pool's
escalation game triggers the universe fork—with less REP than its
maximum winning payout.
Fork HaircutThe default uncredited share is ⌊forkThreshold / 5⌋, approximately 20% of the threshold deposit and rounded down; the full parent-REP threshold is still removed from the parent universe.
Initiator Migration BalanceWith the default burn divisor of 5, the initiator receives forkThreshold - ⌊forkThreshold / 5⌋ = ⌈4 × forkThreshold / 5⌉: approximately 80% of the threshold, rounded up when the threshold is not divisible by five.
forkUniverse applies the threshold, removes the full parent-REP threshold, reduces parent theoretical supply by that amount, and credits only the post-haircut migration balance to the initiator.
Threshold Deposit Split
Threshold Deposit SplitThe full parent-REP threshold leaves parent supply. Most is re-expressed as migratable child-REP credit; the haircut is not credited.
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. Later REP added to a migration balance
converts 1:1; only fork initiation pays this admission cost. A holder
can voluntarily remove REP and reduce theoretical supply through
Zoltar.burnRep; the child token's burn
entrypoint itself is restricted to Zoltar.
Repeated-fork threshold decay
Along one lineage, each fork subtracts its floored haircut from child
theoretical supply. With threshold divisor 20 and burn divisor
5, supply is close to 99% of the previous generation, subject to
Solidity integer flooring.
Repeated-Fork DecayThe filled curve normalizes genesis theoretical supply to 100%. The dashed curve is the next fork threshold. Integer flooring can shift exact onchain values slightly from this continuous approximation.
| Parameter | Current value | Meaning |
|---|---|---|
genesisReputationToken |
Constructor parameter | Network-specific genesis-universe REP token address. Current values are published in the mainnet and Sepolia deployment manifests; Sepolia derives its value from the deterministic deployment init code and allocation list. |
BURN_ADDRESS |
0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF |
Burn sink used for genesis REP |
forkBurnDivisor |
5 |
Constructor immutable with a minimum value of 5. The initiator receives the threshold minus ⌊forkThreshold / forkBurnDivisor⌋ as migration credit. |
forkThresholdDivisor |
20 |
Constructor immutable. Fork threshold is ⌊totalTheoreticalRepSupply / forkThresholdDivisor⌋; the default deployment value makes it about 5% of 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 valid outcome indices. Supplying no outcome indices is accepted as a no-op at the Zoltar layer.
Security assumptions: A09 truthful-branch value, A11 practical migration, A13 REP liquidity, and A06 lifecycle executors.
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. This is the security model's EG01 truth-selection exclusion.
Security assumptions: A07 fork value preservation, A09 truthful-branch preference, A03 independent participants, A05 lineage access value, and A15 intended resolvable questions.
- users can choose which child universe (or child universes) to continue using after a fork
- users prefer to continue in the universes 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.
The enforced recursive-fork gas bound and its implementation evidence
are maintained in
EXT-05.
6. Questions and Outcome Encoding
#
ZoltarQuestionData.QuestionData
stores title, description, start and end time, scalar metadata such as
numTicks, displayValueMin,
displayValueMax, and answerUnit.
Security assumptions: A10 timely outcome evidence, A20 chain-data availability, A27 collision resistance, and A15 question identity and resolvability.
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.
Every call to createQuestion also requires
endTime >= startTime. The contract rejects any question whose end
time is earlier than its start time before it reaches scalar or categorical
validation.
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.
Bits 254...240 are reserved and must be zero. Any nonzero reserved bit, or any payout pair whose two 120-bit numerators fail to sum to numTicks, is malformed rather than invalid.
Packed Scalar Answer
Packed Scalar AnswerA scalar answer is easier to read as reserved bits, a namespace flag, and two payout fields: bits 254...240 must be zero, 0 in bit 255 is the invalid namespace, and 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.
The all-zero word is the only canonical scalar Invalid value. Any
other word with the highest bit clear is malformed, and any word with a nonzero
reserved bit in 254...240 is malformed even if the payout fields would
otherwise decode cleanly.
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 ⌊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
- binary questions, represented by the higher-level Statoblast convention of categorical
YesandNolabels plus the implicitInvalidresolution - 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.