Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions core/src/exchanges/suibets/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { suibetsErrorMapper } from './errors';

export interface SuibetsRawOffer {
id: string;
matchId: string;
matchName: string;
sport: string;
matchId?: string;
eventId?: string;
matchName?: string;
eventName?: string;
sport?: string;
sportName?: string;
homeTeam: string;
awayTeam: string;
creatorWallet: string;
Expand All @@ -30,7 +33,8 @@ export interface SuibetsRawEvent {
name: string;
homeTeam: string;
awayTeam: string;
sport: string;
sport?: string;
sportName?: string;
leagueName?: string;
matchDate: string;
status: string;
Expand Down Expand Up @@ -165,12 +169,13 @@ export class SuibetsFetcher implements IExchangeFetcher<SuibetsRawOffer, Suibets
(data as { offers?: SuibetsRawOffer[] }).offers ??
(Array.isArray(data) ? (data as SuibetsRawOffer[]) : []);

// Group offers by matchId using a Map; each entry is built immutably.
// Group offers by matchId/eventId using a Map; each entry is built immutably.
const byMatch = new Map<string, SuibetsRawOffer[]>();
for (const offer of offers) {
if (!offer.matchId) continue;
const existing = byMatch.get(offer.matchId) ?? [];
byMatch.set(offer.matchId, [...existing, offer]);
const eventId = offer.matchId ?? offer.eventId;
if (!eventId) continue;
const existing = byMatch.get(eventId) ?? [];
byMatch.set(eventId, [...existing, offer]);
}

const q = params.query?.toLowerCase();
Expand All @@ -181,19 +186,20 @@ export class SuibetsFetcher implements IExchangeFetcher<SuibetsRawOffer, Suibets

if (q) {
const matches =
first.matchName?.toLowerCase().includes(q) ||
(first.matchName ?? first.eventName)?.toLowerCase().includes(q) ||
first.homeTeam?.toLowerCase().includes(q) ||
first.awayTeam?.toLowerCase().includes(q) ||
first.sport?.toLowerCase().includes(q);
(first.sport ?? first.sportName)?.toLowerCase().includes(q);
if (!matches) continue;
}

events.push({
id: matchId,
name: first.matchName || `${first.homeTeam} vs ${first.awayTeam}`,
name: first.matchName || first.eventName || `${first.homeTeam} vs ${first.awayTeam}`,
homeTeam: first.homeTeam,
awayTeam: first.awayTeam,
sport: first.sport,
sport: first.sport ?? first.sportName,
sportName: first.sportName,
leagueName: first.leagueName,
matchDate: first.matchDate,
status: 'active',
Expand Down
29 changes: 22 additions & 7 deletions core/src/exchanges/suibets/normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ function liquidity(offer: SuibetsRawOffer): number {
return mistToSui(remaining);
}

function offerEventId(offer: SuibetsRawOffer): string | undefined {
return offer.matchId ?? offer.eventId;
}

function offerEventName(offer: SuibetsRawOffer): string | undefined {
return offer.matchName ?? offer.eventName;
}

function offerSport(offer: SuibetsRawOffer | SuibetsRawEvent): string | undefined {
return offer.sport ?? offer.sportName;
}

export class SuibetsNormalizer implements IExchangeNormalizer<SuibetsRawOffer, SuibetsRawEvent> {
normalizeMarket(raw: SuibetsRawOffer): UnifiedMarket | null {
if (!raw?.id) return null;
Expand All @@ -35,6 +47,9 @@ export class SuibetsNormalizer implements IExchangeNormalizer<SuibetsRawOffer, S
const volume24h = mistToSui(raw.totalMatched ?? 0);

const marketId = toMarketId(raw.id);
const sport = offerSport(raw);
const eventId = offerEventId(raw);
const eventName = offerEventName(raw);
const creatorOutcome = {
outcomeId: toOutcomeId(raw.id, 'creator'),
marketId,
Expand All @@ -50,10 +65,10 @@ export class SuibetsNormalizer implements IExchangeNormalizer<SuibetsRawOffer, S

const market: UnifiedMarket = {
marketId,
eventId: raw.matchId ? toMarketId(raw.matchId) : undefined,
title: `${raw.matchName || `${homeTeam} vs ${awayTeam}`} \u2014 ${sideLabel(raw, 'creator')} @ ${odds}x`,
eventId: eventId ? toMarketId(eventId) : undefined,
title: `${eventName || `${homeTeam} vs ${awayTeam}`} \u2014 ${sideLabel(raw, 'creator')} @ ${odds}x`,
description: [
`P2P offer on ${raw.sport || 'sports'} match.`,
`P2P offer on ${sport || 'sports'} match.`,
`Creator bets ${sideLabel(raw, 'creator')} at ${odds}x odds.`,
`Taker backs ${sideLabel(raw, 'taker')} at ${(1 / noProb).toFixed(2)}x implied odds.`,
raw.leagueName ? `League: ${raw.leagueName}.` : '',
Expand All @@ -67,7 +82,7 @@ export class SuibetsNormalizer implements IExchangeNormalizer<SuibetsRawOffer, S
url: 'https://www.suibets.com/p2p',
status: mapStatus(raw.status),
category: 'Sports',
tags: ['Sports', 'P2P', raw.sport, raw.leagueName].filter((t): t is string => Boolean(t)),
tags: ['Sports', 'P2P', sport, raw.leagueName].filter((t): t is string => Boolean(t)),
contractAddress: raw.onchainOfferId,
yes: creatorOutcome,
no: takerOutcome,
Expand All @@ -93,7 +108,7 @@ export class SuibetsNormalizer implements IExchangeNormalizer<SuibetsRawOffer, S
title: raw.name || `${homeTeam} vs ${awayTeam}`,
description: [
raw.leagueName ? `${raw.leagueName} \u2014` : '',
raw.sport,
offerSport(raw),
'P2P betting on SuiBets.',
].filter(Boolean).join(' '),
slug: raw.id,
Expand All @@ -102,14 +117,14 @@ export class SuibetsNormalizer implements IExchangeNormalizer<SuibetsRawOffer, S
volume: totalVolume,
url: 'https://www.suibets.com/p2p',
category: 'Sports',
tags: ['Sports', 'P2P', 'Sui', raw.sport, raw.leagueName].filter((t): t is string => Boolean(t)),
tags: ['Sports', 'P2P', 'Sui', offerSport(raw), raw.leagueName].filter((t): t is string => Boolean(t)),
};
}

normalizePosition(raw: SuibetsRawOffer): Position {
const odds = Number(raw.creatorOdds) || 2;
return {
marketId: toMarketId(raw.matchId ?? raw.id),
marketId: toMarketId(offerEventId(raw) ?? raw.id),
outcomeId: toOutcomeId(raw.id, 'creator'),
outcomeLabel: sideLabel(raw, 'creator'),
size: mistToSui(raw.creatorStake ?? 0),
Expand Down
41 changes: 41 additions & 0 deletions core/test/exchanges/suibets-fetcher.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { SuibetsFetcher } from '../../src/exchanges/suibets/fetcher';

describe('SuibetsFetcher', () => {
it('groups offers by renamed eventId when matchId is absent', async () => {
const http = {
get: jest.fn().mockResolvedValue({
data: {
offers: [
{
id: 'offer-1',
eventId: 'event-1',
eventName: 'Mets vs Yankees',
sportName: 'Baseball',
homeTeam: 'Mets',
awayTeam: 'Yankees',
creatorWallet: '0xabc',
creatorTeam: 'Mets',
creatorOdds: 2,
creatorStake: 1_000_000_000,
takerStake: 1_000_000_000,
matchDate: '2026-07-01T18:00:00Z',
expiresAt: '2026-07-01T17:00:00Z',
status: 'OPEN',
},
],
},
}),
};
const fetcher = new SuibetsFetcher({ http } as any, 'https://api.example.test');

const events = await fetcher.fetchRawEvents({} as any);

expect(events).toHaveLength(1);
expect(events[0]).toMatchObject({
id: 'event-1',
name: 'Mets vs Yankees',
sport: 'Baseball',
});
expect(events[0].offers).toHaveLength(1);
});
});
19 changes: 19 additions & 0 deletions core/test/normalizers/suibets-normalizer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,25 @@ describe('SuibetsNormalizer', () => {
// -------------------------------------------------------------------------

describe('normalizeMarket', () => {
it('uses renamed eventId, eventName, and sportName fields from current SuiBets offers', () => {
const renamedOffer = {
...rawOffer,
matchId: undefined,
matchName: undefined,
sport: undefined,
eventId: 'event-789',
eventName: 'Mets vs Yankees',
sportName: 'Baseball',
} as unknown as SuibetsRawOffer;

const market = normalizer.normalizeMarket(renamedOffer)!;

expect(market.eventId).toBe('suibets:event-789');
expect(market.title).toContain('Mets vs Yankees');
expect(market.description).toContain('Baseball match');
expect(market.tags).toContain('Baseball');
});

it('returns a non-null result for a valid offer', () => {
const market = normalizer.normalizeMarket(rawOffer);
expect(market).not.toBeNull();
Expand Down
Loading