diff --git a/core/src/router/Router.ts b/core/src/router/Router.ts index 2877212b..65eaca83 100644 --- a/core/src/router/Router.ts +++ b/core/src/router/Router.ts @@ -382,11 +382,9 @@ export class Router extends PredictionMarketExchange { if (params?.category) query.category = params.category; if (params?.limit !== undefined) query.limit = String(params.limit); - const res = await this.client.getArbitrage(query); - // getArbitrage already unwraps .data — res is the opportunities array. - const items: any[] = Array.isArray(res) ? res : (res?.data ?? []); + const items = await this.client.getArbitrage(query); - return items.map((r: any) => { + return items.map((r) => { if (r.spread == null || r.buyPrice == null || r.sellPrice == null) { throw new Error( `fetchArbitrageBulk: arbitrage record is missing required price fields ` + diff --git a/core/src/router/client.ts b/core/src/router/client.ts index fb1806da..f159c747 100644 --- a/core/src/router/client.ts +++ b/core/src/router/client.ts @@ -7,14 +7,39 @@ import { ExchangeNotAvailable, BadRequest, } from '../errors'; +import type { UnifiedMarket, UnifiedEvent } from '../types'; import type { FetchMatchesParams, FetchMarketMatchesParams, FetchEventMatchesParams, + MatchResult, + EventMatchResult, + ArbitrageOpportunity, + MatchRelation, RouterMarketSearchParams, RouterEventSearchParams, } from './types'; +// --------------------------------------------------------------------------- +// Raw API response shapes (before Router-level reshaping) +// --------------------------------------------------------------------------- + +interface MarketMatchesResponse { + matches: MatchResult[]; +} + +interface EventMatchesResponse { + matches: EventMatchResult[]; +} + +interface RawMatchedPair { + marketA: UnifiedMarket; + marketB: UnifiedMarket; + relation?: MatchRelation; + confidence?: number; + reasoning?: string | null; +} + const DEFAULT_BASE_URL = process.env.PMXT_API_URL || 'https://api.pmxt.dev'; export class PmxtApiClient { @@ -31,7 +56,7 @@ export class PmxtApiClient { }); } - async getMarketMatches(params: FetchMatchesParams): Promise { + async getMarketMatches(params: FetchMatchesParams): Promise { const id = params.marketId ?? params.slug ?? params.url; if (!id) throw new BadRequest('One of marketId, slug, or url is required', 'Router'); @@ -41,11 +66,11 @@ export class PmxtApiClient { if (params.limit !== undefined) query.limit = String(params.limit); if (params.includePrices) query.includePrices = 'true'; - const res = await this.request('GET', `/v0/markets/${encodeURIComponent(id)}/matches`, query); + const res = await this.request('GET', `/v0/markets/${encodeURIComponent(id)}/matches`, query); return res.data; } - async getEventMatches(params: FetchEventMatchesParams): Promise { + async getEventMatches(params: FetchEventMatchesParams): Promise { const id = params.eventId ?? params.slug; if (!id) throw new BadRequest('One of eventId or slug is required', 'Router'); @@ -55,11 +80,11 @@ export class PmxtApiClient { if (params.limit !== undefined) query.limit = String(params.limit); if (params.includePrices) query.includePrices = 'true'; - const res = await this.request('GET', `/v0/events/${encodeURIComponent(id)}/matches`, query); + const res = await this.request('GET', `/v0/events/${encodeURIComponent(id)}/matches`, query); return res.data; } - async browseMarketMatches(params: FetchMarketMatchesParams): Promise { + async browseMarketMatches(params: FetchMarketMatchesParams): Promise { const query: Record = {}; if (params.query) query.query = params.query; if (params.category) query.category = params.category; @@ -67,10 +92,10 @@ export class PmxtApiClient { if (params.minConfidence !== undefined) query.minConfidence = String(params.minConfidence); if (params.limit !== undefined) query.limit = String(params.limit); - const res = await this.request('GET', '/v0/matched-markets', query); + const res = await this.request('GET', '/v0/matched-markets', query); // Reshape { marketA, marketB, ... } pairs into MatchResult shape - const pairs: any[] = Array.isArray(res.data) ? res.data : []; - return pairs.map((pair: any) => ({ + const pairs: RawMatchedPair[] = Array.isArray(res.data) ? res.data : []; + return pairs.map((pair: RawMatchedPair) => ({ sourceMarket: pair.marketA, market: pair.marketB, relation: pair.relation || 'identity', @@ -81,7 +106,7 @@ export class PmxtApiClient { })); } - async browseEventMatches(params: FetchEventMatchesParams): Promise { + async browseEventMatches(params: FetchEventMatchesParams): Promise { const query: Record = {}; if (params.query) query.query = params.query; if (params.category) query.category = params.category; @@ -89,11 +114,11 @@ export class PmxtApiClient { if (params.minConfidence !== undefined) query.minConfidence = String(params.minConfidence); if (params.limit !== undefined) query.limit = String(params.limit); - const res = await this.request('GET', '/v0/events/matches', query); + const res = await this.request('GET', '/v0/events/matches', query); return res.data; } - async searchMarkets(params?: RouterMarketSearchParams): Promise { + async searchMarkets(params?: RouterMarketSearchParams): Promise { const query: Record = {}; if (params?.query) query.q = params.query; if (params?.sourceExchange) query.sourceExchange = params.sourceExchange; @@ -101,11 +126,11 @@ export class PmxtApiClient { if (params?.limit !== undefined) query.limit = String(params.limit); if (params?.offset !== undefined) query.offset = String(params.offset); if (params?.closed) query.closed = 'true'; - const res = await this.request('GET', '/v0/markets', query); + const res = await this.request('GET', '/v0/markets', query); return res.data; } - async searchEvents(params?: RouterEventSearchParams): Promise { + async searchEvents(params?: RouterEventSearchParams): Promise { const query: Record = {}; if (params?.query) query.q = params.query; if (params?.sourceExchange) query.sourceExchange = params.sourceExchange; @@ -113,12 +138,12 @@ export class PmxtApiClient { if (params?.limit !== undefined) query.limit = String(params.limit); if (params?.offset !== undefined) query.offset = String(params.offset); if (params?.closed) query.closed = 'true'; - const res = await this.request('GET', '/v0/events', query); + const res = await this.request('GET', '/v0/events', query); return res.data; } - async getArbitrage(query?: Record): Promise { - const res = await this.request('GET', '/v0/arbitrage', query); + async getArbitrage(query?: Record): Promise { + const res = await this.request('GET', '/v0/arbitrage', query); return res.data; } @@ -126,11 +151,11 @@ export class PmxtApiClient { // Internal // ----------------------------------------------------------------------- - private async request( + private async request( method: string, path: string, query?: Record, - ): Promise<{ data: any }> { + ): Promise<{ data: T }> { try { const response = await this.http.request({ method, @@ -138,12 +163,12 @@ export class PmxtApiClient { params: query, }); return response.data; - } catch (error: any) { + } catch (error: unknown) { throw this.mapError(error); } } - private mapError(error: any): Error { + private mapError(error: unknown): Error { if (axios.isAxiosError(error)) { const status = error.response?.status; const message = @@ -177,10 +202,14 @@ export class PmxtApiClient { } } - if (error?.code === 'ECONNREFUSED' || error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT') { - return new NetworkError(`Network error: ${error.message}`, 'Router'); + if (error instanceof Error) { + const code = (error as NodeJS.ErrnoException).code; + if (code === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'ETIMEDOUT') { + return new NetworkError(`Network error: ${error.message}`, 'Router'); + } + return error; } - return error instanceof Error ? error : new Error(String(error)); + return new Error(String(error)); } }