A Prediction Market on the AT Protocol

feat(markets.ts): add score / rep calculations

Ciaran b1da0f1c 8a29dab2

+44 -5
+23 -1
src/core/markets.ts
··· 1 1 import type { Market } from "@/web/providers/cumulus-provider" 2 + import type { Did } from "@atcute/lexicons"; 2 3 import { getUnixTime } from "date-fns"; 3 4 4 5 function lsmr(q1: number, q2: number, b: number) { ··· 20 21 .map(bet => { 21 22 bet.position === "yes" ? countYes++ : countNo++; 22 23 const { priceYes, priceNo } = getPrices(countYes, countNo, market.liquidity); 23 - return { ...bet, countYes, countNo, positionPriceYes: priceYes, positionPriceNo: priceNo } 24 + const costPaid = bet.position === "yes" ? priceYes : priceNo; 25 + return { ...bet, countYes, countNo, positionPriceYes: priceYes, positionPriceNo: priceNo, costPaid } 24 26 }); 25 27 26 28 return { ··· 35 37 isResolved: market.resolution !== null, 36 38 } 37 39 } 40 + 41 + export function calculateScoreAndRep(markets: Array<ReturnType<typeof parseMarket>>, did: Did) { 42 + let score = 0; // Profit and Loss 43 + let rep = 0; // Popularity of markets 44 + 45 + for (const market of markets) { 46 + const answer = market?.resolution?.answer; 47 + if (!answer) continue; 48 + for (const bet of market.bets ?? []) { 49 + if (market.did === did) rep++; 50 + if (bet.did === did) { 51 + if (bet.position === answer) score += (1.0 - parseFloat(bet.costPaid)) 52 + else if (bet.position === answer) score -= parseFloat(bet.costPaid) 53 + } 54 + } 55 + } 56 + 57 + 58 + return { score, rep }; 59 + }
+5 -1
src/web/app.tsx
··· 5 5 6 6 7 7 export default function App() { 8 - const { markets } = useCumulus(); 8 + const { markets, score, rep } = useCumulus(); 9 9 10 10 if (markets.isLoading) return <div className="p-4"><Spinner className='m-auto' /></div> 11 11 12 12 return <div className="grid p-2 md:grid-cols-2 gap-2"> 13 13 {markets.data?.map(market => <Market key={market.uri} market={market} />)} 14 + <div> 15 + <p className="text-6xl first-letter:text-coral-500">Score: {score}</p> 16 + <p className="text-4xl first-letter:text-coral-500">Rep: {rep}</p> 17 + </div> 14 18 <AddMarket /> 15 19 </div> 16 20 }
+16 -3
src/web/providers/cumulus-provider.tsx
··· 1 1 import { treaty } from "@elysiajs/eden" 2 - import { createContext, type PropsWithChildren } from "react"; 2 + import { createContext, useMemo, type PropsWithChildren } from "react"; 3 3 import { useQuery, type UseQueryResult } from "@tanstack/react-query"; 4 4 import { type CumulusServer } from "@/server/types"; 5 5 import type { InferSelectModel } from "drizzle-orm"; 6 6 import type { betsTable, marketsTable, resolutionsTable } from "@/db"; 7 - import { parseMarket } from "@/core/markets"; 7 + import { calculateScoreAndRep, parseMarket } from "@/core/markets"; 8 + import { useAuth } from "../hooks/useAuth"; 8 9 9 10 10 11 export type Market = InferSelectModel<typeof marketsTable> & { ··· 14 15 15 16 export interface CumulusContext { 16 17 markets: UseQueryResult<Array<ReturnType<typeof parseMarket>>>, 18 + score: number, 19 + rep: number, 17 20 } 18 21 19 22 export const CumulusContext = createContext<CumulusContext | undefined>(undefined); 20 23 21 24 export default function Cumulus({ children }: PropsWithChildren) { 22 25 26 + const { profile } = useAuth(); 23 27 const server = treaty<CumulusServer>(window.location.origin); 24 28 25 29 const markets = useQuery({ ··· 31 35 }, 32 36 }); 33 37 34 - return <CumulusContext.Provider value={{ markets }}>{children}</ CumulusContext.Provider> 38 + const { score, rep } = useMemo(() => 39 + markets.data 40 + ? calculateScoreAndRep(markets.data, profile.did) 41 + : { score: 0, rep: 0 }, 42 + [markets.data, profile.did] 43 + ); 44 + 45 + return <CumulusContext.Provider value={{ markets, score, rep }}> 46 + {children} 47 + </ CumulusContext.Provider> 35 48 }