examples/web-showcase. Read the overview first for how it works.
1
Install the SDK
pnpm add @outcome.xyz/hip4@beta
src/markets.ts
import {
createHIP4Adapter,
parseInstanceDescription,
templateIdOfOutcome,
} from "@outcome.xyz/hip4";
import type { HIP4Market, HLOutcomeTemplate } from "@outcome.xyz/hip4";
// The default network is testnet. Outcome's markets are on mainnet.
export const hip4 = createHIP4Adapter({ testnet: false });
2
Load the events
fetchMarkets returns one HIP4Market per outcome. Keep Outcome’s, and group a question’s options into one event.src/markets.ts
/**
* Outcome's live events, highest 24h volume first. An event is a question's
* options (Arsenal, Draw, Leeds United) or a single market.
*/
export async function loadEvents() {
const [markets, mids, templates] = await Promise.all([
hip4.events.fetchMarkets({ sortBy: "volume" }) as Promise<HIP4Market[]>,
hip4.client.fetchAllMids(),
hip4.client.fetchOutcomeTemplates(),
]);
const events = new Map<string, HIP4Market[]>();
for (const market of markets) {
// Other deployers publish on Hyperliquid too. Outcome's markets have venue
// "out". A question's fallback outcome has no order book.
if (market.raw.venue !== "out" || ("isFallback" in market && market.isFallback)) continue;
// Recurring markets stay in the catalog after they expire, until they settle.
if ("expiry" in market && market.expiry.getTime() <= Date.now()) continue;
const id = "questionId" in market ? `q${market.questionId}` : `o${market.outcomeId}`;
events.set(id, [...(events.get(id) ?? []), market]);
}
const options = [...events.values()].map((e) => e.sort((a, b) => a.outcomeId - b.outcomeId));
return { events: options, mids, templates };
}
3
Assign categories
src/markets.ts
export const CATEGORIES = ["sports", "crypto", "esports", "economics", "finance"];
const ESPORTS = ["esport", "esports", "lol", "leagueoflegends", "dota", "dota2", "cs2", "csgo", "counterstrike"];
/** The category outcome.xyz lists a market under, or "all" if none. */
export function categoryOf(market: HIP4Market, templates: HLOutcomeTemplate[]): string {
const event = "rawQuestion" in market ? market.rawQuestion : market.raw;
const keywords = {
...parseInstanceDescription(market.raw.description),
...parseInstanceDescription(event.description),
};
if (keywords.sport) {
return ESPORTS.includes(keywords.sport.toLowerCase().replace(/[^a-z0-9]/g, "")) ? "esports" : "sports";
}
// Templates tag their category, e.g. "metadata=category:economics|...".
const template = templates.find((t) => t.id === templateIdOfOutcome(event.name));
const tag = template?.description.match(/metadata=category:(\w+)/)?.[1];
if (tag && CATEGORIES.includes(tag)) return tag;
// A market on a Hyperliquid perp: BTC is crypto, xyz:GOLD (the xyz dex) is finance.
const perp = keywords.perp ?? keywords.underlying;
if (perp?.startsWith("xyz:")) return "finance";
if (perp && !perp.includes(":")) return "crypto";
return "all";
}
4
Link to outcome.xyz
src/markets.ts
/**
* The market's page on outcome.xyz. outcome.xyz records utm parameters on a
* visitor's first visit, so set utm_source to your brand.
*/
export function outcomeUrl(market: HIP4Market, category: string): string {
const url =
"questionId" in market
? new URL(`https://outcome.xyz/markets/${category}/q${market.questionId}?outcome=${market.outcomeId}`)
: new URL(`https://outcome.xyz/markets/${category}/o${market.outcomeId}`);
url.searchParams.set("utm_source", "acme-games");
url.searchParams.set("utm_medium", "referral");
return url.toString();
}
5
Show the markets
Render them however fits your site. A basic example:The example app shows each event as a card, with category tabs and a refresh every minute. See
const { events, mids, templates } = await loadEvents();
for (const options of events) {
const category = categoryOf(options[0], templates);
for (const market of options) {
const price = Number(mids[market.sides[0].coin]); // "0.61" is a 61% chance
const link = document.createElement("a");
link.href = outcomeUrl(market, category);
link.target = "_blank";
link.rel = "noopener";
// A side with no price yet (or 0) shows "-" instead of NaN%.
const chance = price > 0 ? `${Math.round(price * 100)}%` : "-";
link.textContent = `${market.parsedName ?? market.name} ${chance}`;
document.body.append(link);
}
}
src/main.ts.Next steps
- Full trading integration, to let users trade on your site
- Fetch markets, for every option of
fetchMarkets