diff --git a/src/client.ts b/src/client.ts index f92582d..907c4ee 100644 --- a/src/client.ts +++ b/src/client.ts @@ -595,7 +595,7 @@ export class ClobClient { params?: TradeParams, only_first_page = false, next_cursor?: string, - ): Promise { + ): Promise { this.canL2Auth(); const endpoint = GET_TRADES; @@ -622,8 +622,10 @@ export class ClobClient { headers, params: _params, }); + const data = this.getPaginatedResponseData(response); + if (!data) return response; next_cursor = response.next_cursor; - results = [...results, ...response.data]; + results = [...results, ...data]; } return results; } @@ -934,7 +936,7 @@ export class ClobClient { params?: OpenOrderParams, only_first_page = false, next_cursor?: string, - ): Promise { + ): Promise { this.canL2Auth(); const endpoint = GET_OPEN_ORDERS; const l2HeaderArgs = { @@ -960,8 +962,10 @@ export class ClobClient { headers, params: _params, }); + const data = this.getPaginatedResponseData(response); + if (!data) return response; next_cursor = response.next_cursor; - results = [...results, ...response.data]; + results = [...results, ...data]; } return results; } @@ -969,7 +973,7 @@ export class ClobClient { public async getPreMigrationOrders( only_first_page = false, next_cursor?: string, - ): Promise { + ): Promise { this.canL2Auth(); const endpoint = GET_PRE_MIGRATION_ORDERS; const l2HeaderArgs = { @@ -992,8 +996,10 @@ export class ClobClient { headers, params: _params, }); + const data = this.getPaginatedResponseData(response); + if (!data) return response; next_cursor = response.next_cursor; - results = [...results, ...response.data]; + results = [...results, ...data]; } return results; } @@ -1184,7 +1190,9 @@ export class ClobClient { } // Rewards - public async getEarningsForUserForDay(date: string): Promise { + public async getEarningsForUserForDay( + date: string, + ): Promise { this.canL2Auth(); const endpoint = GET_EARNINGS_FOR_USER_FOR_DAY; @@ -1213,8 +1221,10 @@ export class ClobClient { headers, params, }); + const data = this.getPaginatedResponseData(response); + if (!data) return response; next_cursor = response.next_cursor; - results = [...results, ...response.data]; + results = [...results, ...data]; } return results; } @@ -1251,7 +1261,7 @@ export class ClobClient { order_by = "", position = "", no_competition = false, - ): Promise { + ): Promise { this.canL2Auth(); const endpoint = GET_REWARDS_EARNINGS_PERCENTAGES; @@ -1283,8 +1293,10 @@ export class ClobClient { headers, params, }); + const data = this.getPaginatedResponseData(response); + if (!data) return response; next_cursor = response.next_cursor; - results = [...results, ...response.data]; + results = [...results, ...data]; } return results; } @@ -1312,28 +1324,34 @@ export class ClobClient { return this.get(`${this.host}${endpoint}`, { headers, params: _params }); } - public async getCurrentRewards(): Promise { + public async getCurrentRewards(): Promise { let results: MarketReward[] = []; let next_cursor = INITIAL_CURSOR; while (next_cursor !== END_CURSOR) { const response = await this.get(`${this.host}${GET_REWARDS_MARKETS_CURRENT}`, { params: { next_cursor }, }); + const data = this.getPaginatedResponseData(response); + if (!data) return response; next_cursor = response.next_cursor; - results = [...results, ...response.data]; + results = [...results, ...data]; } return results; } - public async getRawRewardsForMarket(conditionId: string): Promise { + public async getRawRewardsForMarket( + conditionId: string, + ): Promise { let results: MarketReward[] = []; let next_cursor = INITIAL_CURSOR; while (next_cursor !== END_CURSOR) { const response = await this.get(`${this.host}${GET_REWARDS_MARKETS}${conditionId}`, { params: { next_cursor }, }); + const data = this.getPaginatedResponseData(response); + if (!data) return response; next_cursor = response.next_cursor; - results = [...results, ...response.data]; + results = [...results, ...data]; } return results; } @@ -1419,6 +1437,10 @@ export class ClobClient { return builderCode !== undefined && builderCode !== bytes32Zero; } + private getPaginatedResponseData(response: { data?: unknown } | null | undefined): T[] | undefined { + return Array.isArray(response?.data) ? [...response.data] : undefined; + } + private async _ensureMarketInfoCached(tokenID: string): Promise { if (tokenID in this.feeInfos) return; diff --git a/src/types/clob.ts b/src/types/clob.ts index 0ebbad5..926a002 100644 --- a/src/types/clob.ts +++ b/src/types/clob.ts @@ -415,7 +415,9 @@ export interface BuilderApiKeyResponse { } export type ClobErrorResponseBody = { - error: string; + error: unknown; + status?: number; + [key: string]: unknown; }; export interface TradesPaginatedResponse { diff --git a/tests/client/paginationErrorHandling.test.ts b/tests/client/paginationErrorHandling.test.ts new file mode 100644 index 0000000..fbb4b10 --- /dev/null +++ b/tests/client/paginationErrorHandling.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("../../src/headers/index.js", () => ({ + createL1Headers: vi.fn(), + createL2Headers: vi.fn(async () => ({})), +})); + +import { Chain, ClobClient } from "../../src"; + +const makeClient = () => + new ClobClient({ + host: "http://localhost:8080", + chain: Chain.AMOY, + signer: {} as any, + creds: { + key: "key", + secret: "secret", + passphrase: "passphrase", + }, + }); + +describe("pagination error handling", () => { + it("preserves structured errors for getOpenOrders", async () => { + const client = makeClient(); + vi.spyOn(client as any, "get").mockResolvedValue({ error: "boom", status: 500 }); + + await expect(client.getOpenOrders()).resolves.toMatchObject({ + error: "boom", + status: 500, + }); + }); + + it("preserves structured errors for getCurrentRewards", async () => { + const client = makeClient(); + vi.spyOn(client as any, "get").mockResolvedValue({ error: "boom", status: 503 }); + + await expect(client.getCurrentRewards()).resolves.toMatchObject({ + error: "boom", + status: 503, + }); + }); +});