diff --git a/.changeset/lazy-trades-fix.md b/.changeset/lazy-trades-fix.md new file mode 100644 index 0000000..ea1170f --- /dev/null +++ b/.changeset/lazy-trades-fix.md @@ -0,0 +1,6 @@ +--- +"@polymarket/client": patch +"@polymarket/bindings": patch +--- + +Move account trade listing to the unified CLOB account trades endpoint. diff --git a/packages/bindings/src/clob/account.test.ts b/packages/bindings/src/clob/account.test.ts new file mode 100644 index 0000000..afcaad1 --- /dev/null +++ b/packages/bindings/src/clob/account.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'vitest'; +import { ClobTradeSchema } from './account'; + +const baseTrade = { + token_id: '1', + bucket_index: 7, + fee_rate_bps: '0', + id: 'trade-1', + maker_address: `0x${'aa'.repeat(20)}`, + maker_orders: [ + { + fee_rate_bps: '0', + maker_address: `0x${'bb'.repeat(20)}`, + matched_amount: '1.5', + order_id: 'order-1', + outcome: 'Yes', + owner: 'owner-1', + price: '0.5', + side: 'BUY', + token_id: '1', + }, + ], + market_id: `0x${'cc'.repeat(32)}`, + outcome: 'Yes', + owner: 'owner-1', + price: '0.5', + side: 'BUY', + size: '1.5', + status: 'CONFIRMED', + taker_order_id: 'order-2', + trader_side: 'TAKER', + transaction_hash: `0x${'dd'.repeat(32)}`, +}; + +describe('ClobTradeSchema', () => { + it('normalizes unified account trade responses', () => { + const trade = ClobTradeSchema.parse({ + ...baseTrade, + match_time: 1_777_996_829_000, + last_update: 1_777_996_840_000, + }); + + expect(trade.market).toBe(baseTrade.market_id); + expect(trade.tokenId).toBe(baseTrade.token_id); + expect(trade.makerOrders[0]?.tokenId).toBe('1'); + expect(trade.matchedAt).toBe('2026-05-05T16:00:29.000Z'); + expect(trade.updatedAt).toBe('2026-05-05T16:00:40.000Z'); + }); +}); diff --git a/packages/bindings/src/clob/account.ts b/packages/bindings/src/clob/account.ts index 8f4cbb1..d4ffe91 100644 --- a/packages/bindings/src/clob/account.ts +++ b/packages/bindings/src/clob/account.ts @@ -91,7 +91,6 @@ export type OpenOrdersPage = z.infer; export const MakerOrderSchema = z .object({ - asset_id: TokenIdSchema, fee_rate_bps: DecimalStringSchema, maker_address: z.string(), matched_amount: DecimalStringSchema, @@ -100,18 +99,19 @@ export const MakerOrderSchema = z owner: z.string(), price: DecimalStringSchema, side: z.string(), + token_id: TokenIdSchema, }) .transform( ({ - asset_id, fee_rate_bps, maker_address, matched_amount, order_id, + token_id, ...rest }) => ({ ...rest, - tokenId: asset_id, + tokenId: token_id, feeRateBps: fee_rate_bps, makerAddress: maker_address, matchedAmount: matched_amount, @@ -121,14 +121,14 @@ export const MakerOrderSchema = z export const ClobTradeSchema = z .object({ - asset_id: TokenIdSchema, + token_id: TokenIdSchema, bucket_index: z.number(), - fee_rate_bps: DecimalStringSchema, + fee_rate_bps: DecimalStringSchema.optional(), id: z.string(), last_update: EpochMillisecondsToIsoDateTimeStringSchema, maker_address: z.string(), maker_orders: z.array(MakerOrderSchema), - market: z.string(), + market_id: z.string(), match_time: EpochMillisecondsToIsoDateTimeStringSchema, outcome: z.string(), owner: z.string(), @@ -138,16 +138,17 @@ export const ClobTradeSchema = z status: z.string(), taker_order_id: z.string(), trader_side: z.enum(['TAKER', 'MAKER']), - transaction_hash: z.string(), + transaction_hash: z.string().optional(), }) .transform( ({ - asset_id, + token_id, bucket_index, fee_rate_bps, last_update, maker_address, maker_orders, + market_id, match_time, taker_order_id, trader_side, @@ -155,7 +156,8 @@ export const ClobTradeSchema = z ...rest }) => ({ ...rest, - tokenId: asset_id, + market: market_id, + tokenId: token_id, bucketIndex: bucket_index, feeRateBps: fee_rate_bps, updatedAt: last_update, @@ -170,7 +172,15 @@ export const ClobTradeSchema = z export type ClobTrade = z.infer; -export const ClobTradesPageSchema = createCursorPageSchema(ClobTradeSchema); +export const ClobTradesPageSchema = z + .object({ + data: z.array(ClobTradeSchema), + has_more: z.boolean(), + }) + .transform(({ has_more, ...rest }) => ({ + ...rest, + hasMore: has_more, + })); export type ClobTradesPage = z.infer; diff --git a/packages/client/src/actions/account.ts b/packages/client/src/actions/account.ts index 668aba7..cbd3ef0 100644 --- a/packages/client/src/actions/account.ts +++ b/packages/client/src/actions/account.ts @@ -40,7 +40,13 @@ import { UserInputError, } from '../errors'; import { parseUserInput } from '../input'; -import { PageSizeSchema, type Paginated, paginate } from '../pagination'; +import { + decodeOffsetCursor, + encodeOffsetCursor, + PageSizeSchema, + type Paginated, + paginate, +} from '../pagination'; import { validateWith } from '../response'; import { toSignatureType } from '../wallet'; import { snakeCase, toSearchParams } from './params'; @@ -240,6 +246,8 @@ const ListAccountTradesRequestSchema = z .object(ListAccountTradesRequestFields) .default({}); +const ACCOUNT_TRADES_PAGE_SIZE = 50; + export type ListAccountTradesRequest = z.input< typeof ListAccountTradesRequestSchema >; @@ -303,27 +311,35 @@ export function listAccountTrades( ListAccountTradesRequestSchema, ); - return paginate( - (nextCursor) => - client.secureClob - .get('/data/trades', { - params: toSearchParams( - { ...params, nextCursor }, - snakeCase({ tokenId: 'asset_id' }), - ), - }) - .andThen(validateWith(ClobTradesPageSchema)) - .map((response) => ({ - items: response.data, - hasMore: response.nextCursor !== END_CURSOR, - nextCursor: - response.nextCursor === END_CURSOR - ? undefined - : toPaginationCursor(response.nextCursor), - totalCount: response.count, - })), - cursor, - ); + return paginate((nextCursor) => { + const decoded = decodeOffsetCursor(nextCursor, ACCOUNT_TRADES_PAGE_SIZE); + + return client.secureClob + .get('/v1/account/trades', { + params: toSearchParams( + { ...params, offset: decoded.offset }, + snakeCase({ market: 'market_id', tokenId: 'token_id' }), + ), + }) + .andThen(validateWith(ClobTradesPageSchema)) + .map((response) => { + const items = response.data.slice(0, decoded.pageSize); + const hasMore = + items.length > 0 && + (response.hasMore || response.data.length > decoded.pageSize); + + return { + items, + hasMore, + nextCursor: hasMore + ? encodeOffsetCursor({ + offset: decoded.offset + items.length, + pageSize: decoded.pageSize, + }) + : undefined, + }; + }); + }, cursor); } export type FetchNotificationsError =