Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Target use case: prediction-market resolution. Statements like "Kanye West's Del

## How It Works

1. **Assert** — someone posts a statement, a USDC bond, and a Resolution Spec (the rubric; stored off-chain on Arweave, its hash on-chain)
1. **Assert** — someone posts a statement, a USDC bond, and a Resolution Spec (the rubric; stored off-chain on Arweave, with a retrievable transaction ID and SHA-256 digest on-chain)
2. **Wait** — liveness window where anyone can dispute
3. **Undisputed** — if no dispute, resolves `True`
4. **Disputed** — the first dispute triggers on-chain LLM resolution: a single trusted off-chain resolver posts the verdict via the resolver-gated `submit_llm_resolution` instruction `[Built]`; the off-chain service that makes the LLM call is `[MVP-target]` (the former 3-feed Switchboard council was removed per [ADR-0002](docs/adr/0002-trusted-llm-resolver.md))
Expand All @@ -25,7 +25,7 @@ The LLM layer is deliberately trusted, not trustless: a wrong verdict is challen

- `True` — verified under the spec
- `False` — contradicted under the spec
- `Unresolvable` — cannot be decided under the spec (ambiguous, conflicting, premature, or no outcome reached the vote's supermajority). `[MVP-target]` Settles **no-fault**: both bonds are returned, no one is slashed, and the assertion is voided (today any non-`True` outcome is settled like `False`) ([ADR-0005](docs/adr/0005-no-fault-unresolvable.md)).
- `Unresolvable` — cannot be decided under the spec (ambiguous, conflicting, premature, or no outcome reached the vote's supermajority). `[Built]` Settles **no-fault**: all assertion/dispute bonds are returned, no fee is taken, no one is slashed, and the assertion is voided ([ADR-0005](docs/adr/0005-no-fault-unresolvable.md)).

## Voting `[MVP-target]`

Expand Down Expand Up @@ -102,10 +102,10 @@ The optimistic core is built: the account model, the six-state machine (`Asserte

These pieces are v1 targets, not yet shipped:

- **Trusted LLM resolver** — the on-chain half is built: `submit_llm_resolution` is gated on a dedicated `ProtocolConfig.resolver` key (separate from `authority`, so a leaked hot resolver key can only post a challengeable verdict) and accepts only `True`/`False`/`Unresolvable`. The off-chain service that makes the real LLM call and posts verdicts is the remaining target; the former 3-feed Switchboard council was removed per ADR-0002. On-chain LLM provenance (prompt/response/evidence hashing) is deferred to a `[Vision]` trust-minimized resolver.
- **Trusted LLM resolver** — the on-chain instruction and off-chain worker are built: `submit_llm_resolution` is gated on a rotatable `ProtocolConfig.resolver` key (separate from `authority`), and the worker verifies each Resolution Spec before calling Gemini and posting `True`/`False`/`Unresolvable`. Live Google Search grounding remains disabled until the team supplies a paid Gemini key. On-chain provenance hashes remain a `[Vision]` item.
- **Private staked voting** — opening a vote sets up the round, but real MagicBlock private voting (delegation, ER settlement) is the MVP target.
- **Resolution Spec on Arweave** — the on-chain `auxiliary_hash` field exists; off-chain Arweave storage and integrity-checking is planned.
- **No-fault settlement & reward split** — `Unresolvable` no-fault settlement and the share-based settlement split (`llm_disputer_reward_share_bps`, `vote_disputer_reward_share_bps`, `voter_reward_share_bps`, `treasury_share_bps`) are planned; only `protocol_fee_bps` is applied today.
- **Resolution Spec upload UX** — `create_assertion` now requires a bounded `ar://<tx-id>#sha256=<digest>` reference, and the resolver fetches and verifies it before judging. Product-side Arweave upload UI is still planned.
- **Share-based reward split** — `Unresolvable` no-fault settlement is built. The share-based settlement split (`llm_disputer_reward_share_bps`, `vote_disputer_reward_share_bps`, `voter_reward_share_bps`, `treasury_share_bps`) is still planned; only `protocol_fee_bps` is applied to decisive settlements today.
- **Field names** — state and config fields still carry the legacy `pusd` prefix; a later PR renames them to `usdc` to match the committed asset.

## Vision (post-MVP)
Expand Down
55 changes: 33 additions & 22 deletions docs/architecture.md

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ The on-chain object created when an asserter posts a statement, a USDC bond, and
**Statement** — `statement: [u8; 280]` `[Built]`
The short human-readable sentence whose truth is asserted. Null-terminated, stored on-chain.

**Resolution Spec** — `auxiliary_hash` + off-chain content `[MVP-target]`
The asserter-supplied rubric that defines _how_ the statement resolves: authoritative sources and their priority, key definitions, ambiguity handling, and when the statement becomes resolvable. **It is the source of truth** — the LLM resolver and voters apply it; they do not judge absolute reality. The spec lives off-chain on Arweave (permanent, content-addressed); only its hash is stored on-chain (`auxiliary_hash`, ≤128 bytes), so anyone can fetch it and verify integrity. See [ADR-0001](adr/0001-rubric-relative-truth.md).
**Resolution Spec** — `auxiliary_hash` + off-chain content `[Built]`
The asserter-supplied rubric that defines _how_ the statement resolves: authoritative sources and their priority, key definitions, ambiguity handling, and when the statement becomes resolvable. **It is the source of truth** — the LLM resolver and voters apply it; they do not judge absolute reality. The spec lives off-chain on Arweave; `auxiliary_hash` stores `ar://<tx-id>#sha256=<digest>` so anyone can fetch and verify the exact bytes. See [ADR-0001](adr/0001-rubric-relative-truth.md).
_Avoid_: "auxiliary data" framed as optional hints; "evidence" — it is the spec, not a hint.

**Rubric-relative truth** `[MVP-target]`
Expand All @@ -49,8 +49,8 @@ The terminal result on the assertion; `OUTCOME_NONE` (255) until `state == Resol

- `True` (0) `[Built]` — verified under the spec.
- `False` (1) `[Built]` — contradicted under the spec.
- `Unresolvable` (3) `[MVP-target]` — cannot be decided under the spec (ambiguous, conflicting, premature, or below the vote's supermajority). Settles **no-fault** (see Economics).
- `TooEarly` (2) — the code value persists and `validate_outcome_code` still accepts it; no path is intended to emit it. **Merging it into `Unresolvable` is `[MVP-target]`.** _Avoid_ treating it as a distinct outcome.
- `Unresolvable` (3) `[Built]` — cannot be decided under the spec (ambiguous, conflicting, premature, or below the vote's supermajority). Settles **no-fault** (see Economics).
- `TooEarly` (2) — deprecated. The code value persists, but the shared outcome validator rejects it; use `Unresolvable`. _Avoid_ treating it as a distinct outcome.
- `None` (255) — sentinel for unset.

## Participants
Expand All @@ -60,7 +60,7 @@ The terminal result on the assertion; `OUTCOME_NONE` (255) until `state == Resol
**LLM Disputer** `[Built]` — the first disputer; challenges the default `True` and triggers LLM resolution.
**Vote Disputer** `[Built]` — the second disputer; challenges the LLM verdict and triggers the staked vote.
**Voter** `[MVP-target]` — anyone who stakes USDC into a specific vote round; weight is linear in stake; wrong-side stake is slashed, right-side stake earns rewards.
**LLM Resolver** `[MVP-target]` — a trusted off-chain service that calls one LLM and posts the verdict via `submit_llm_resolution` `[Built]`, gated on the dedicated `ProtocolConfig.resolver` key. (On-chain LLM provenance hashing is deferred to `[Vision]`.) See [ADR-0002](adr/0002-trusted-llm-resolver.md).
**LLM Resolver** `[Built]` — a trusted off-chain service that verifies the Resolution Spec, calls one LLM, and posts the verdict via `submit_llm_resolution`, gated on the dedicated, governance-rotatable `ProtocolConfig.resolver` key. Live search grounding awaits a paid Gemini key; on-chain provenance hashing is deferred to `[Vision]`. See [ADR-0002](adr/0002-trusted-llm-resolver.md).
_Avoid_: "council" for the resolver — the 3-feed Switchboard council was removed per ADR-0002 and was never the resolver design.
**Integrator** — any app consuming Opal outcomes; must read the Resolution Spec to judge whether an outcome is meaningful for its use case, and should require `state == Resolved` before irreversible settlement.

Expand All @@ -75,7 +75,7 @@ _Avoid_: calling the asset "pusd" (it is USDC — `pusd` is only the legacy on-c

**Schelling-point vote** `[MVP-target]` — the staked vote is a coordination game on the truth: losing-side voters are slashed and winning-side voters are paid from the losing side, so the equilibrium is to vote the spec's honest answer. Security comes from this slashing, not from any weight curve. See [ADR-0003](adr/0003-private-staked-voting.md).

**No-fault settlement** `[MVP-target]` — when an assertion resolves `Unresolvable`, all bonds are returned and no one is slashed; the assertion is voided. See [ADR-0005](adr/0005-no-fault-unresolvable.md).
**No-fault settlement** `[Built]` — when an assertion resolves `Unresolvable`, all bonds are returned, no fee is taken, and no one is slashed; the assertion is voided. See [ADR-0005](adr/0005-no-fault-unresolvable.md).
_Avoid_: the legacy `!= True ⇒ disputer correct` rule, which slashed on indeterminate outcomes.

**Settlement Split** `[MVP-target]` — the slashed pot is divided by configured shares (`llm_disputer_reward_share_bps`, `vote_disputer_reward_share_bps`, `voter_reward_share_bps`, `treasury_share_bps`, summing ≤ 100%). Today only `protocol_fee_bps` is applied `[Built]`; the share-based split is `[MVP-target]`.
Expand Down
20 changes: 11 additions & 9 deletions docs/resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ Throughout, "truth" means **rubric-relative truth**: the answer is judged agains
Every assertion includes:

1. An on-chain `statement`: a short natural-language sentence (max 280 bytes, null-terminated). `[Built]`
2. An on-chain `auxiliary_hash`: the content hash of the off-chain **Resolution Spec** (max 128 bytes). `[Built]` (the field); `[MVP-target]` (the spec workflow)
2. An on-chain `auxiliary_hash`: `ar://<43-character-transaction-id>#sha256=<64-character-hex-digest>` for the off-chain **Resolution Spec** (120 of the available 128 bytes). `[Built]`

### The Resolution Spec `[MVP-target]`
### The Resolution Spec `[Built]`

The Resolution Spec is the asserter-supplied rubric that defines _how_ the statement resolves: authoritative sources and their priority, key definitions, ambiguity handling, and when the statement becomes resolvable. **It is the source of truth.** The LLM resolver and the voters _apply_ the spec; they do not adjudicate absolute reality, so the same statement text can resolve differently across assertions. See [ADR-0001](adr/0001-rubric-relative-truth.md).

The spec lives off-chain on **Arweave** (permanent, content-addressed); only its hash is stored on-chain in `auxiliary_hash`, so anyone can fetch the spec and verify its integrity. The protocol does not enforce a schema for the spec content. If the spec is vague, missing, or contradictory, the statement is more likely to resolve `Unresolvable` — vetting the spec before trusting an outcome is the integrator's responsibility.
The spec lives off-chain on **Arweave** (permanent, content-addressed). The on-chain `auxiliary_hash` stores both its transaction ID and SHA-256 digest in the bounded form `ar://<tx-id>#sha256=<digest>`, so anyone can retrieve the exact transaction and verify its bytes. `create_assertion` validates this reference format; the resolver enforces fetch timeout/size limits, verifies the digest and UTF-8, and only then passes the spec to the model. The protocol does not enforce a schema for the spec content. If the spec is vague or contradictory, the statement is more likely to resolve `Unresolvable` — vetting the spec before trusting an outcome is the integrator's responsibility.

## Outcome Rules

Expand All @@ -27,10 +27,10 @@ The evidence, applied through the spec, verifies the statement.
**`False` (1)** `[Built]`
The evidence, applied through the spec, contradicts the statement.

**`Unresolvable` (3)** `[MVP-target]`
**`Unresolvable` (3)** `[Built]`
The statement cannot be decided under the spec: source priority is unclear, evidence conflicts or is unavailable, the statement is ambiguous, the spec is too weak, the truth does not exist yet, or the vote failed to reach `supermajority_bps`. Settles **no-fault** (see [Settlement Logic](#settlement-logic)).

**`TooEarly` (2)** — the code constant persists and `validate_outcome_code` still accepts it (0–3); `submit_llm_resolution` explicitly rejects it, though `finalize_vote_resolution_placeholder` can still technically accept it until the vote path is rebuilt. **Merging it into `Unresolvable` is `[MVP-target]`**: "the real-world truth does not exist yet" is just one way to be `Unresolvable`, and the target is that it settles identically. See [ADR-0005](adr/0005-no-fault-unresolvable.md).
**`TooEarly` (2)** — deprecated. The code constant persists, but the shared outcome validator rejects it. "The real-world truth does not exist yet" is represented by `Unresolvable`. See [ADR-0005](adr/0005-no-fault-unresolvable.md).

**`None` (255)** `[Built]` — sentinel for unset; the value of `outcome` until `state == Resolved`.

Expand All @@ -57,7 +57,7 @@ When an assertion is created (`create_assertion`):
- `state = Asserted`
- `outcome = None` (255)
- `liveness_deadline` is set from `liveness_window_seconds`
- `auxiliary_hash` points to the off-chain Resolution Spec
- `auxiliary_hash` points to and authenticates the off-chain Resolution Spec

While `Asserted`, the statement is treated as `True` by default. The liveness window is the only time a first dispute can be filed; if it expires undisputed, the assertion can be finalized `True` (`finalize_undisputed`).

Expand All @@ -71,7 +71,9 @@ When the first dispute is filed (`dispute_assertion`):
- `state = PendingLLM`
- `dispute_count = 1`

The on-chain half is `[Built]`: `submit_llm_resolution` posts the verdict and is gated on a dedicated `ProtocolConfig.resolver` key — deliberately separate from `authority`, so a leaked hot resolver key can only post a challengeable verdict, not act as governance. It accepts only `True`, `False`, or `Unresolvable` (`TooEarly` is rejected per [ADR-0005](adr/0005-no-fault-unresolvable.md)). The off-chain **trusted resolver** service that makes the single LLM call and submits through this instruction is `[MVP-target]`. The LLM layer does not need to be trustless because the staked vote backstops it (a wrong verdict is challengeable). Binding LLM provenance (prompt/response/evidence hashes) on-chain for auditability is **not part of the MVP**; it is deferred to a `[Vision]` trust-minimized/permissionless resolver.
The resolver path is `[Built]`: `submit_llm_resolution` posts the verdict and is gated on a dedicated, governance-rotatable `ProtocolConfig.resolver` key — deliberately separate from `authority`, so a leaked hot resolver key can only post a challengeable verdict, not act as governance. The off-chain worker fetches and verifies the Resolution Spec, makes the LLM call, and submits only `True`, `False`, or `Unresolvable`. Live search grounding remains operationally deferred until a paid Gemini key is available. Binding prompt/response/evidence hashes on-chain is a post-MVP `[Vision]` item.

If no resolver verdict arrives by `requested_at + llm_resolution_timeout_seconds`, anyone may call `recover_pending_llm`. It transitions the round to a challengeable `Unresolvable` result and opens the standard LLM challenge window; a later no-fault finalization returns both principals. The resolver submission and recovery paths share the `PendingLLM` guard, so only one can win.

On localnet, integration tests call `submit_llm_resolution` directly with a test resolver keypair; the former `mock-llm` feature and `submit_mock_llm_resolution` were removed when the real instruction landed. (An earlier instruction of the same name belonged to the 3-feed Switchboard council — `set_council_feeds`, `council_feeds`, `switchboard_*`, and `*_hash` fields — removed per [ADR-0002](adr/0002-trusted-llm-resolver.md); it was compiled but never operationally stood up.)

Expand Down Expand Up @@ -136,7 +138,7 @@ If the liveness window expires with no dispute:
- asserter bond is returned minus the protocol fee
- treasury receives the fee

### Indeterminate (`Unresolvable`) — no-fault `[MVP-target]`
### Indeterminate (`Unresolvable`) — no-fault `[Built]`

If any path produces `Unresolvable`, the assertion settles **no-fault**:

Expand All @@ -156,7 +158,7 @@ If `finalize_llm_resolution` settles a `True` or `False` outcome (no vote disput
- the asserter wins: receives their bond + the LLM disputer bond, minus fee
- treasury receives the fee

> Target `[MVP-target]`: `Unresolvable` takes the no-fault path above and does not flow through this win/lose branch. Today the code has no no-fault path — `Unresolvable` (like any `outcome != True`) still routes through the disputer-wins branch here (`llm_dispute_correct = final_outcome != OUTCOME_TRUE`), settling like `False`; this is the legacy behavior [ADR-0005](adr/0005-no-fault-unresolvable.md) replaces.
`Unresolvable` takes the no-fault path above and does not flow through this win/lose branch. This replaces the legacy `outcome != True` behavior described in [ADR-0005](adr/0005-no-fault-unresolvable.md).

### Second Dispute (Vote Resolution) — `True` / `False` `[MVP-target]`

Expand Down
8 changes: 3 additions & 5 deletions docs/tokenomics.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ llm_dispute_correct = settlement_resolution != True
vote_dispute_correct = settlement_resolution != challenged_llm_resolution
```

Today the LLM-dispute rule slashes the asserter on **any** non-`True` outcome (`False`, plus the still-accepted `TooEarly`/`Unresolvable` codes), not only on a genuine `False`. This is the legacy `!= True` rule, which wrongly assigns fault on indeterminate outcomes.

> **No-fault override** `[MVP-target]`. When `settlement_resolution` is `Unresolvable`, neither side is "correct" or "wrong" — the assertion settles no-fault (below). This **replaces** the legacy `settlement_resolution != True ⇒ disputer correct` rule, which wrongly slashed the asserter (or, under a True-fallback, the disputer) whenever the outcome was merely indeterminate. Only a genuine `True`/`False` settlement assigns fault. See [ADR-0005](adr/0005-no-fault-unresolvable.md).
> **No-fault override** `[Built]`. When `settlement_resolution` is `Unresolvable`, neither side is "correct" or "wrong" — the assertion settles no-fault (below). This **replaces** the legacy `settlement_resolution != True ⇒ disputer correct` rule, which wrongly slashed the asserter whenever the outcome was merely indeterminate. Only a genuine `True`/`False` settlement assigns fault. See [ADR-0005](adr/0005-no-fault-unresolvable.md).

## Settlement Defaults

Expand All @@ -68,15 +66,15 @@ If the liveness window expires with no dispute:
- the asserter bond is returned minus the protocol fee (`protocol_fee_bps`),
- the treasury receives the fee.

### Unresolvable — No-Fault `[MVP-target]`
### Unresolvable — No-Fault `[Built]`

If the settled outcome is `Unresolvable` (ambiguous, conflicting, premature, or below the vote's `supermajority_bps`):

- both the asserter bond and any disputer bond(s) are **returned in full**,
- no one is slashed and no fee is taken,
- the assertion is **voided** — re-assert later if it becomes determinable.

Merging `TooEarly` into `Unresolvable` is `[MVP-target]`; the `OUTCOME_TOO_EARLY` (2) constant persists and `validate_outcome_code` still accepts it, but no path is intended to emit it. See [ADR-0005](adr/0005-no-fault-unresolvable.md).
The deprecated `OUTCOME_TOO_EARLY` (2) constant persists, but the shared outcome validator rejects it. Prematurity is represented by `Unresolvable`. See [ADR-0005](adr/0005-no-fault-unresolvable.md).

### First Dispute Settles Correct (`False`) `[Built]`

Expand Down
Loading