import { get } from './request' /** 分页结果 */ export interface PageResult { list: T[] page: number pageSize: number total: number } /** * Event 单项结构(与 doc.json definitions["polymarket.PmEvent"] 对齐) * 用于 /PmEvent/getPmEventPublic 列表项 与 /PmEvent/findPmEvent 的 data */ export interface PmEventListItem { ID: number createdAt?: string updatedAt?: string slug?: string ticker?: string title: string description?: string resolutionSource?: string startDate?: string endDate?: string creationDate?: string closedTime?: string startTime?: string image?: string icon?: string active?: boolean archived?: boolean closed?: boolean featured?: boolean new?: boolean restricted?: boolean enableOrderBook?: boolean competitive?: number liquidity?: number liquidityAmm?: number liquidityClob?: number openInterest?: number volume?: number commentCount?: number seriesSlug?: string markets?: PmEventMarketItem[] series?: PmEventSeriesItem[] tags?: PmEventTagItem[] [key: string]: unknown } /** * 对应 definitions polymarket.PmMarket 常用字段 * - outcomes: 选项展示文案,如 ["Yes", "No"] 或 ["Up", "Down"],与 outcomePrices 一一对应 * - outcomePrices: 各选项价格(首项为第一选项概率,如 Yes/Up) */ export interface PmEventMarketItem { /** 市场 ID(部分接口返回大写 ID) */ ID?: number /** 市场 ID(部分接口或 JSON 序列化为小写 id) */ id?: number question?: string slug?: string /** 选项展示文案,与 outcomePrices 顺序一致 */ outcomes?: string[] /** 各选项价格,outcomes[0] 对应 outcomePrices[0] */ outcomePrices?: string[] | number[] /** 市场对应的 clob token id 与 outcomePrices、outcomes 顺序一致,outcomes[0] 对应 clobTokenIds[0] */ clobTokenIds?: string[] endDate?: string volume?: number [key: string]: unknown } /** 从市场项取 marketId,兼容 ID / id */ export function getMarketId(m: PmEventMarketItem | null | undefined): string | undefined { if (!m) return undefined const raw = m.ID ?? m.id return raw != null ? String(raw) : undefined } /** 从市场项取 clobTokenId,outcomeIndex 0=Yes/第一选项,1=No/第二选项 */ export function getClobTokenId( m: PmEventMarketItem | null | undefined, outcomeIndex: 0 | 1 = 0, ): string | undefined { if (!m?.clobTokenIds?.length) return undefined const id = m.clobTokenIds[outcomeIndex] return id != null ? String(id) : undefined } /** 对应 definitions polymarket.PmSeries 常用字段 */ export interface PmEventSeriesItem { ID?: number ticker?: string title?: string slug?: string [key: string]: unknown } /** 对应 definitions polymarket.PmTag 常用字段 */ export interface PmEventTagItem { label?: string slug?: string ID?: number [key: string]: unknown } /** 接口统一响应 */ export interface PmEventListResponse { code: number data: PageResult msg: string } /** * GET /PmEvent/getPmEventPublic 请求参数(与 doc.json 对齐) */ export interface GetPmEventListParams { page?: number pageSize?: number keyword?: string /** 创建时间范围,如 ['2025-01-01', '2025-12-31'] */ createdAtRange?: string[] /** 标签 ID 列表,按分类筛选,传统数组方式传递 */ tagIds?: number[] } /** * 分页获取 Event 列表(公开接口,不需要鉴权) * GET /PmEvent/getPmEventPublic * * Query: page, pageSize, keyword, createdAtRange, tagIds * doc.json: paths["/PmEvent/getPmEventPublic"].get.parameters */ export async function getPmEventPublic( params: GetPmEventListParams = {}, ): Promise { const { page = 1, pageSize = 10, keyword, createdAtRange, tagIds } = params const query: Record = { page, pageSize, } if (keyword != null && keyword !== '') query.keyword = keyword if (createdAtRange != null && createdAtRange.length) query.createdAtRange = createdAtRange if (tagIds != null && tagIds.length > 0) { query.tagIds = tagIds } return get('/PmEvent/getPmEventPublic', query) } /** * GET /PmEvent/findPmEvent 响应体(200) * doc.json: responses["200"].schema = allOf [ response.Response, { data: polymarket.PmEvent, msg } ] */ export interface PmEventDetailResponse { code: number data: PmEventListItem msg: string } /** * findPmEvent 请求参数(Query) * doc.json: ID 与 slug 支持同时传入,至少传一个 */ export interface FindPmEventParams { /** Event 主键(数字 ID) */ id?: number /** Event 的 slug 标识 */ slug?: string } /** * 用 id 和/或 slug 查询 Event 详情 * GET /PmEvent/findPmEvent * * 请求参数(Query): * - ID: number,可选 * - slug: string,可选 * - ID 与 slug 至少传一个,可同时传 * 鉴权:需在 headers 中传 x-token、x-user-id * * 响应(200):PmEventDetailResponse { code, data: PmEventListItem, msg } */ export async function findPmEvent( params: FindPmEventParams, config?: { headers?: Record }, ): Promise { const query: Record = {} if (params.id != null) query.ID = params.id if (params.slug != null && params.slug !== '') query.slug = params.slug if (Object.keys(query).length === 0) { throw new Error('findPmEvent: 至少需要传 id 或 slug') } return get('/PmEvent/findPmEvent', query, config) } /** 多选项卡片中单个选项(用于左右滑动切换) */ export interface EventCardOutcome { title: string /** 第一选项概率(来自 outcomePrices[0]) */ chanceValue: number /** Yes 价格 0–1,来自 outcomePrices[0],供交易组件使用 */ yesPrice?: number /** No 价格 0–1,来自 outcomePrices[1],供交易组件使用 */ noPrice?: number yesLabel?: string noLabel?: string marketId?: string clobTokenIds?: string[] } /** * 首页卡片项(与 mapEventItemToCard 返回结构一致,用于缓存) * displayType:single = 单一 Yes/No,multi = 多个选项左右滑动 */ export interface EventCardItem { id: string /** Event slug,用于 findPmEvent 传参 */ slug?: string marketTitle: string chanceValue: number marketInfo: string imageUrl: string category: string expiresAt: string /** 展示类型:单一二元 或 多选项滑动 */ displayType: 'single' | 'multi' /** 多选项时每个选项的标题与概率,按顺序滑动展示 */ outcomes?: EventCardOutcome[] /** 单一类型时可选按钮文案,如 "Up"/"Down" */ yesLabel?: string noLabel?: string /** 是否显示 NEW 角标 */ isNew?: boolean /** 当前市场 ID(单 market 时为第一个 market 的 ID,供交易/Split 使用) */ marketId?: string /** 用于下单 tokenId,单 market 时取自 firstMarket.clobTokenIds */ clobTokenIds?: string[] /** Yes 价格 0–1,来自 outcomePrices[0],供交易组件使用 */ yesPrice?: number /** No 价格 0–1,来自 outcomePrices[1],供交易组件使用 */ noPrice?: number } /** 内存缓存:列表数据,切换页面时复用,下拉刷新时清空 */ let eventListCache: { list: EventCardItem[] page: number total: number pageSize: number } | null = null export function getEventListCache(): typeof eventListCache { return eventListCache } export function setEventListCache(data: { list: EventCardItem[] page: number total: number pageSize: number }) { eventListCache = data } export function clearEventListCache(): void { eventListCache = null } function marketChance(market: PmEventMarketItem): number { const raw = market?.outcomePrices?.[0] if (raw == null) return 17 const yesPrice = parseFloat(String(raw)) if (!Number.isFinite(yesPrice)) return 17 return Math.min(100, Math.max(0, Math.round(yesPrice * 100))) } /** * 将 list 单项映射为首页 MarketCard 所需字段 * - 单一 market 或无 market:displayType single,当前逻辑 * - 多个 markets:displayType multi,outcomes 为每项标题+概率,卡片内左右滑动切换 */ export function mapEventItemToCard(item: PmEventListItem): EventCardItem { const id = String(item.ID ?? '') const marketTitle = item.title ?? '' const imageUrl = item.image ?? item.icon ?? '' const markets = item.markets ?? [] const multi = markets.length > 1 let chanceValue = 17 const firstMarket = markets[0] if (firstMarket?.outcomePrices?.[0] != null) { chanceValue = marketChance(firstMarket) } let marketInfo = '$0 Vol.' if (item.volume != null && Number.isFinite(item.volume)) { const v = item.volume if (v >= 1000) { marketInfo = `$${(v / 1000).toFixed(1)}k Vol.` } else { marketInfo = `$${Math.round(v)} Vol.` } } let expiresAt = '' if (item.endDate) { try { const d = new Date(item.endDate) if (!Number.isNaN(d.getTime())) { expiresAt = d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', }) } } catch { expiresAt = item.endDate } } const category = item.series?.[0]?.title ?? item.tags?.[0]?.label ?? '' function parseOutcomePrices(m: PmEventMarketItem): { yesPrice: number; noPrice: number } { const y = m?.outcomePrices?.[0] const n = m?.outcomePrices?.[1] const yesPrice = y != null && Number.isFinite(parseFloat(String(y))) ? Math.min(1, Math.max(0, parseFloat(String(y)))) : 0.5 const noPrice = n != null && Number.isFinite(parseFloat(String(n))) ? Math.min(1, Math.max(0, parseFloat(String(n)))) : 1 - yesPrice return { yesPrice, noPrice } } const outcomes: EventCardOutcome[] | undefined = multi ? markets.map((m) => { const { yesPrice, noPrice } = parseOutcomePrices(m) return { title: m.question ?? '', chanceValue: marketChance(m), yesPrice, noPrice, yesLabel: m.outcomes?.[0] ?? 'Yes', noLabel: m.outcomes?.[1] ?? 'No', marketId: getMarketId(m), clobTokenIds: m.clobTokenIds, } }) : undefined const firstPrices = firstMarket ? parseOutcomePrices(firstMarket) : { yesPrice: 0.5, noPrice: 0.5 } return { id, slug: item.slug ?? undefined, marketTitle, chanceValue, marketInfo, imageUrl, category, expiresAt, displayType: multi ? 'multi' : 'single', outcomes, yesLabel: firstMarket?.outcomes?.[0] ?? 'Yes', noLabel: firstMarket?.outcomes?.[1] ?? 'No', isNew: item.new === true, marketId: getMarketId(firstMarket), clobTokenIds: firstMarket?.clobTokenIds, yesPrice: firstPrices.yesPrice, noPrice: firstPrices.noPrice, } }