Skip to content

fix(hypercore): index outcome sides by position and model outcome settlement - #97

Merged
ifdario merged 1 commit into
infinitefield:mainfrom
vignesh-chaturvedi:hip4-outcome-meta
Oct 10, 2026
Merged

ifdario merged 1 commit into
infinitefield:mainfrom
vignesh-chaturvedi:hip4-outcome-meta

Conversation

@vignesh-chaturvedi

Copy link
Copy Markdown
Contributor

outcomes() treated a side as side 0 only if it was named exactly "Yes". Template outcomes name their sides template:Yes and template:No, or after the participants (template:{shortNameA}), so both of their sides got the second side's asset index. On mainnet that is 157 of the 296 outcomes listed today. An order placed with the first side's OutcomeMarket traded the other side, and the first side could not be reached at all.

The asset encoding is 10 * outcome + side, where side is the position in sideSpecs, so sides are now indexed by position. Settlement fills agree: across 60 settled mainnet outcomes, 58 of them templates, every #<outcome>0 fill settled at the outcome's settleFraction and every #<outcome>1 fill at 1 - settleFraction.

Two more gaps I hit while building a HIP-4 settlement keeper on the SDK, which reads both endpoints as raw JSON for now:

  • outcomeMeta returns quoteToken, venue and deployerFeeScale for each outcome, and the SDK dropped them. OutcomeInfo now carries all three, and implements Deserialize. venue and deployer_fee_scale are optional: on mainnet, the 8 daily "Recurring" outcomes (BTC, ETH, SOL and HYPE) have no venue, and 72 outcomes have no fee scale.
  • settled_outcome returned raw JSON. It now returns Option<SettledOutcome>: the outcome's spec, its settle_fraction, the deployer's details and, for an outcome in a question, the question with its OutcomeQuestionState (Active or Settled, with any other shape kept as raw JSON). The exchange answers null for an outcome that has not settled or does not exist, which is None.

Breaking:

  • HttpClient::settled_outcome returns Option<SettledOutcome> instead of serde_json::Value.
  • OutcomeInfo gained quote_token, venue and deployer_fee_scale, so struct literals need them.

Verified:

  • cargo test --lib: 185 passed, 5 ignored (the live audits). New: outcome_markets_index_sides_by_position, outcome_info_deserialize_venue_fields, settled_outcome_deserialize and outcome_question_state_keeps_unknown_shapes, using payloads copied from mainnet and testnet.
  • New live test test_http_outcome_markets_are_distinct (mainnet and testnet) passes here. With the old name check put back, it fails on mainnet with 435 distinct indices for 592 sides, and outcome_markets_index_sides_by_position fails too.
  • test_http_undocumented_typed_responses also parses settled_outcome(9) and checks that an unknown ID is None.
  • cargo clippy -p hypersdk --all-targets: no new warnings. cd hypecli && cargo check passes.
  • cargo +nightly fmt. Nightly's import reorder in types/deploy.rs is left out.

…tlement

outcomes() treated a side as side 0 only if it was named exactly "Yes".
Template outcomes name their sides "template:Yes" and "template:No", or
after the participants, so both of their sides got the second side's
asset index: 157 of the 296 outcomes on mainnet. An order placed with
the first side's OutcomeMarket traded the other side. Sides are now
indexed by their position in side_specs, as the asset encoding
10 * outcome + side expects.

- OutcomeInfo carries the quoteToken, venue and deployerFeeScale that
  outcomeMeta returns, and implements Deserialize
- HttpClient::settled_outcome returns Option<SettledOutcome> instead of
  raw JSON: the spec, settle fraction, details and, for an outcome in a
  question, the question's state. null means not settled
@ifdario
ifdario merged commit 2c8d9a8 into infinitefield:main Oct 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants