| 196 | // ---------------------------------------------------------------------------- |
| 197 | |
| 198 | export class HyperliquidNormalizer implements IExchangeNormalizer<HyperliquidRawOutcomeWithQuestion, HyperliquidRawQuestion> { |
| 199 | |
| 200 | normalizeMarket(raw: HyperliquidRawOutcomeWithQuestion): UnifiedMarket | null { |
| 201 | if (!raw || !raw.outcome) return null; |
| 202 | |
| 203 | const outcome = raw.outcome; |
| 204 | const outcomeId = outcome.outcome; |
| 205 | const parsed = parseDescription(outcome.description); |
| 206 | |
| 207 | // Also parse the question description for context (e.g. priceThresholds) |
| 208 | const questionParsed = raw.question |
| 209 | ? parseDescription(raw.question.description) |
| 210 | : undefined; |
| 211 | |
| 212 | const midPrice = raw.midPrice ? parseFloat(raw.midPrice) : 0.5; |
| 213 | const yesPrice = Math.max(0, Math.min(1, midPrice)); |
| 214 | const noPrice = Math.max(0, Math.min(1, 1 - midPrice)); |
| 215 | |
| 216 | const outcomes: MarketOutcome[] = []; |
| 217 | for (const side of outcome.sideSpecs) { |
| 218 | const sideKey = side.name.toLowerCase() === 'yes' ? 'yes' as const : 'no' as const; |
| 219 | outcomes.push({ |
| 220 | outcomeId: toOutcomeId(outcomeId, sideKey), |
| 221 | marketId: toMarketId(outcomeId), |
| 222 | label: side.name, |
| 223 | price: sideKey === 'yes' ? yesPrice : noPrice, |
| 224 | }); |
| 225 | } |
| 226 | |
| 227 | // If no sideSpecs provided, default to Yes/No |
| 228 | if (outcomes.length === 0) { |
| 229 | outcomes.push( |
| 230 | { |
| 231 | outcomeId: toOutcomeId(outcomeId, 'yes'), |
| 232 | marketId: toMarketId(outcomeId), |
| 233 | label: 'Yes', |
| 234 | price: yesPrice, |
| 235 | }, |
| 236 | { |
| 237 | outcomeId: toOutcomeId(outcomeId, 'no'), |
| 238 | marketId: toMarketId(outcomeId), |
| 239 | label: 'No', |
| 240 | price: noPrice, |
| 241 | }, |
| 242 | ); |
| 243 | } |
| 244 | |
| 245 | // Resolution date: prefer outcome-level expiry, then question-level expiry tag, |
| 246 | // then a deadline phrase scraped from the question's prose description. |
| 247 | const expiryDate = parsed.expiryDate |
| 248 | ?? questionParsed?.expiryDate |
| 249 | ?? parseDeadlineFromText(raw.question?.description ?? ""); |
| 250 | |
| 251 | // Build a descriptive title from the parsed description |
| 252 | const title = buildTitle(outcome.name, parsed, questionParsed); |
| 253 | |
| 254 | // Derive underlying from outcome or question |
| 255 | const underlying = parsed.underlying ?? questionParsed?.underlying; |
nothing calls this directly
no outgoing calls
no test coverage detected