From bc5e60f075c72792bde1bd55cf133561ad3fec38 Mon Sep 17 00:00:00 2001 From: Adam Boudjemaa Date: Wed, 11 Mar 2026 23:45:45 +0100 Subject: [PATCH 1/3] fix(types): add typed responses for postOrder and market data methods --- src/client.ts | 23 +++-- src/types.ts | 67 +++++++++++++- tests/client/types.test.ts | 180 +++++++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+), 10 deletions(-) create mode 100644 tests/client/types.test.ts diff --git a/src/client.ts b/src/client.ts index 7a2ff56..d530965 100644 --- a/src/client.ts +++ b/src/client.ts @@ -102,6 +102,7 @@ import type { L2HeaderArgs, L2PolyHeader, L2WithBuilderHeader, + Market, MarketPrice, MarketReward, MarketTradeEvent, @@ -119,10 +120,12 @@ import type { OrdersScoring, OrdersScoringParams, PaginationPayload, + PostOrderResponse, PostOrdersArgs, PriceHistoryFilterParams, ReadonlyApiKeyResponse, RewardsPercentages, + SimplifiedMarket, TickSize, TickSizes, TotalUserEarning, @@ -252,31 +255,35 @@ export class ClobClient { public async getSamplingSimplifiedMarkets( next_cursor = INITIAL_CURSOR, - ): Promise { + ): Promise> { return this.get(`${this.host}${GET_SAMPLING_SIMPLIFIED_MARKETS}`, { params: { next_cursor }, }); } - public async getSamplingMarkets(next_cursor = INITIAL_CURSOR): Promise { + public async getSamplingMarkets( + next_cursor = INITIAL_CURSOR, + ): Promise> { return this.get(`${this.host}${GET_SAMPLING_MARKETS}`, { params: { next_cursor }, }); } - public async getSimplifiedMarkets(next_cursor = INITIAL_CURSOR): Promise { + public async getSimplifiedMarkets( + next_cursor = INITIAL_CURSOR, + ): Promise> { return this.get(`${this.host}${GET_SIMPLIFIED_MARKETS}`, { params: { next_cursor }, }); } - public async getMarkets(next_cursor = INITIAL_CURSOR): Promise { + public async getMarkets(next_cursor = INITIAL_CURSOR): Promise> { return this.get(`${this.host}${GET_MARKETS}`, { params: { next_cursor }, }); } - public async getMarket(conditionID: string): Promise { + public async getMarket(conditionID: string): Promise { return this.get(`${this.host}${GET_MARKET}${conditionID}`); } @@ -936,7 +943,7 @@ export class ClobClient { orderType: T = OrderType.GTC as T, deferExec = false, postOnly = false, - ): Promise { + ): Promise { const order = await this.createOrder(userOrder, options); return this.postOrder(order, orderType, deferExec, postOnly); } @@ -946,7 +953,7 @@ export class ClobClient { options?: Partial, orderType: T = OrderType.FOK as T, deferExec = false, - ): Promise { + ): Promise { const order = await this.createMarketOrder(userMarketOrder, options); return this.postOrder(order, orderType, deferExec); } @@ -1001,7 +1008,7 @@ export class ClobClient { orderType: T = OrderType.GTC as T, deferExec = false, postOnly = false, - ): Promise { + ): Promise { this.canL2Auth(); const endpoint = POST_ORDER; const orderPayload = orderToJson( diff --git a/src/types.ts b/src/types.ts index 2464336..aaaea11 100644 --- a/src/types.ts +++ b/src/types.ts @@ -385,11 +385,74 @@ export interface FeeRates { [tokenId: string]: number; } -export interface PaginationPayload { +export interface PaginationPayload { readonly limit: number; readonly count: number; readonly next_cursor: string; - readonly data: any[]; + readonly data: T[]; +} + +export interface PostOrderResponse { + orderID: string; + status: string; + transactTime: string; + owner: string; + errorMsg?: string; + takingAmount?: string; + makingAmount?: string; + transactionsHashes?: string[]; +} + +export interface SimplifiedMarket { + condition_id: string; + tokens: Token[]; + rewards: { + min_size: number; + max_spread: number; + }; + min_incentive_size: string; + max_incentive_spread: string; + accepting_orders: boolean; + enable_order_book: boolean; +} + +export interface Market { + condition_id: string; + question_id: string; + question: string; + description: string; + market_slug: string; + end_date_iso: string; + game_start_time: string; + seconds_delay: number; + fpmm: string; + maker_base_fee: number; + taker_base_fee: number; + notifications_enabled: boolean; + neg_risk: boolean; + neg_risk_market_id: string; + neg_risk_request_id: string; + icon: string; + image: string; + tokens: Token[]; + tags: string[]; + is_50_50_outcome: boolean; + accepting_orders: boolean; + accepting_order_timestamp: string | null; + minimum_order_size: string; + minimum_tick_size: string; + active: boolean; + closed: boolean; + archived: boolean; + enable_order_book: boolean; + rewards: { + min_size: number; + max_spread: number; + event_start_date: string; + event_end_date: string; + in_game_multiplier: number; + reward_epoch: number; + }; } export interface MarketTradeEvent { diff --git a/tests/client/types.test.ts b/tests/client/types.test.ts new file mode 100644 index 0000000..83918ed --- /dev/null +++ b/tests/client/types.test.ts @@ -0,0 +1,180 @@ +import { ClobClient } from "../../src/client.ts"; +import { Chain } from "../../src/types.ts"; +import type { + Market, + PaginationPayload, + PostOrderResponse, + SimplifiedMarket, +} from "../../src/types.ts"; + +class StubClient extends ClobClient { + private _stub: unknown; + setStub(v: unknown) { + this._stub = v; + } + protected async get(): Promise { + return this._stub; + } + protected async post(): Promise { + return this._stub; + } +} + +describe("typed return values", () => { + const client = new StubClient("http://localhost", Chain.AMOY); + + describe("PaginationPayload", () => { + it("data is typed as Market[] from getMarkets", async () => { + const payload: PaginationPayload = { + limit: 100, + count: 1, + next_cursor: "LTE=", + data: [ + { + condition_id: "0xabc", + question_id: "0xdef", + question: "Will X happen?", + description: "", + market_slug: "will-x-happen", + end_date_iso: "2025-01-01T00:00:00Z", + game_start_time: "", + seconds_delay: 0, + fpmm: "", + maker_base_fee: 0, + taker_base_fee: 0, + notifications_enabled: false, + neg_risk: false, + neg_risk_market_id: "", + neg_risk_request_id: "", + icon: "", + image: "", + tokens: [], + tags: [], + is_50_50_outcome: false, + accepting_orders: true, + accepting_order_timestamp: null, + minimum_order_size: "5", + minimum_tick_size: "0.01", + active: true, + closed: false, + archived: false, + enable_order_book: true, + rewards: { + min_size: 0, + max_spread: 0, + event_start_date: "", + event_end_date: "", + in_game_multiplier: 1, + reward_epoch: 0, + }, + }, + ], + }; + client.setStub(payload); + const result = await client.getMarkets(); + expect(result.data[0].condition_id).to.equal("0xabc"); + expect(result.data[0].accepting_orders).to.equal(true); + }); + + it("data is typed as SimplifiedMarket[] from getSimplifiedMarkets", async () => { + const payload: PaginationPayload = { + limit: 100, + count: 1, + next_cursor: "LTE=", + data: [ + { + condition_id: "0xabc", + tokens: [{ token_id: "1", outcome: "Yes", price: 0.5 }], + rewards: { min_size: 5, max_spread: 0.02 }, + min_incentive_size: "5", + max_incentive_spread: "0.02", + accepting_orders: true, + enable_order_book: true, + }, + ], + }; + client.setStub(payload); + const result = await client.getSimplifiedMarkets(); + expect(result.data[0].condition_id).to.equal("0xabc"); + expect(result.data[0].tokens[0].outcome).to.equal("Yes"); + }); + }); + + describe("PostOrderResponse", () => { + it("postOrder returns typed PostOrderResponse", async () => { + const response: PostOrderResponse = { + orderID: "0x123", + status: "matched", + transactTime: "1234567890", + owner: "0xabc", + }; + client.setStub(response); + // postOrder requires L2 auth; test the type shape directly + const typed: PostOrderResponse = response; + expect(typed.orderID).to.equal("0x123"); + expect(typed.status).to.equal("matched"); + expect(typed.transactTime).to.equal("1234567890"); + expect(typed.owner).to.equal("0xabc"); + }); + + it("PostOrderResponse optional fields are optional", () => { + const minimal: PostOrderResponse = { + orderID: "0x456", + status: "live", + transactTime: "999", + owner: "0xdef", + }; + expect(minimal.errorMsg).to.be.undefined; + expect(minimal.takingAmount).to.be.undefined; + expect(minimal.makingAmount).to.be.undefined; + expect(minimal.transactionsHashes).to.be.undefined; + }); + }); + + describe("getMarket", () => { + it("returns a Market (not any)", async () => { + const market: Market = { + condition_id: "0xfeed", + question_id: "0xbeef", + question: "Test?", + description: "", + market_slug: "test", + end_date_iso: "", + game_start_time: "", + seconds_delay: 0, + fpmm: "", + maker_base_fee: 0, + taker_base_fee: 0, + notifications_enabled: false, + neg_risk: false, + neg_risk_market_id: "", + neg_risk_request_id: "", + icon: "", + image: "", + tokens: [], + tags: [], + is_50_50_outcome: false, + accepting_orders: false, + accepting_order_timestamp: null, + minimum_order_size: "5", + minimum_tick_size: "0.01", + active: false, + closed: true, + archived: false, + enable_order_book: false, + rewards: { + min_size: 0, + max_spread: 0, + event_start_date: "", + event_end_date: "", + in_game_multiplier: 1, + reward_epoch: 0, + }, + }; + client.setStub(market); + const result = await client.getMarket("0xfeed"); + expect(result.condition_id).to.equal("0xfeed"); + expect(result.closed).to.equal(true); + }); + }); +}); From 7404986b953deb3fa391357c13a798c648f89b1a Mon Sep 17 00:00:00 2001 From: Adam Boudjemaa Date: Thu, 12 Mar 2026 02:39:21 +0100 Subject: [PATCH 2/3] fix: reuse existing OrderResponse instead of duplicate PostOrderResponse --- src/client.ts | 8 ++++---- src/types.ts | 23 +++++++---------------- tests/client/types.test.ts | 16 ++++++++-------- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/src/client.ts b/src/client.ts index d530965..f967ab3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -115,12 +115,12 @@ import type { OrderBookSummary, OrderMarketCancelParams, OrderPayload, + OrderResponse, OrderScoring, OrderScoringParams, OrdersScoring, OrdersScoringParams, PaginationPayload, - PostOrderResponse, PostOrdersArgs, PriceHistoryFilterParams, ReadonlyApiKeyResponse, @@ -943,7 +943,7 @@ export class ClobClient { orderType: T = OrderType.GTC as T, deferExec = false, postOnly = false, - ): Promise { + ): Promise { const order = await this.createOrder(userOrder, options); return this.postOrder(order, orderType, deferExec, postOnly); } @@ -953,7 +953,7 @@ export class ClobClient { options?: Partial, orderType: T = OrderType.FOK as T, deferExec = false, - ): Promise { + ): Promise { const order = await this.createMarketOrder(userMarketOrder, options); return this.postOrder(order, orderType, deferExec); } @@ -1008,7 +1008,7 @@ export class ClobClient { orderType: T = OrderType.GTC as T, deferExec = false, postOnly = false, - ): Promise { + ): Promise { this.canL2Auth(); const endpoint = POST_ORDER; const orderPayload = orderToJson( diff --git a/src/types.ts b/src/types.ts index aaaea11..87c4daa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -192,13 +192,15 @@ export interface BanStatus { } export interface OrderResponse { - success: boolean; - errorMsg: string; orderID: string; - transactionsHashes: string[]; status: string; - takingAmount: string; - makingAmount: string; + transactTime?: string; + owner?: string; + success?: boolean; + errorMsg?: string; + transactionsHashes?: string[]; + takingAmount?: string; + makingAmount?: string; } export interface OpenOrder { @@ -392,17 +394,6 @@ export interface PaginationPayload { readonly data: T[]; } -export interface PostOrderResponse { - orderID: string; - status: string; - transactTime: string; - owner: string; - errorMsg?: string; - takingAmount?: string; - makingAmount?: string; - transactionsHashes?: string[]; -} - export interface SimplifiedMarket { condition_id: string; tokens: Token[]; diff --git a/tests/client/types.test.ts b/tests/client/types.test.ts index 83918ed..0e4daac 100644 --- a/tests/client/types.test.ts +++ b/tests/client/types.test.ts @@ -1,11 +1,11 @@ import { ClobClient } from "../../src/client.ts"; -import { Chain } from "../../src/types.ts"; import type { Market, + OrderResponse, PaginationPayload, - PostOrderResponse, SimplifiedMarket, } from "../../src/types.ts"; +import { Chain } from "../../src/types.ts"; class StubClient extends ClobClient { private _stub: unknown; @@ -100,9 +100,9 @@ describe("typed return values", () => { }); }); - describe("PostOrderResponse", () => { - it("postOrder returns typed PostOrderResponse", async () => { - const response: PostOrderResponse = { + describe("OrderResponse", () => { + it("postOrder returns typed OrderResponse", async () => { + const response: OrderResponse = { orderID: "0x123", status: "matched", transactTime: "1234567890", @@ -110,15 +110,15 @@ describe("typed return values", () => { }; client.setStub(response); // postOrder requires L2 auth; test the type shape directly - const typed: PostOrderResponse = response; + const typed: OrderResponse = response; expect(typed.orderID).to.equal("0x123"); expect(typed.status).to.equal("matched"); expect(typed.transactTime).to.equal("1234567890"); expect(typed.owner).to.equal("0xabc"); }); - it("PostOrderResponse optional fields are optional", () => { - const minimal: PostOrderResponse = { + it("OrderResponse optional fields are optional", () => { + const minimal: OrderResponse = { orderID: "0x456", status: "live", transactTime: "999", From 633394ac7c3314a012bfc238bc90c84d7d3999b9 Mon Sep 17 00:00:00 2001 From: Adam Boudjemaa Date: Tue, 17 Mar 2026 11:42:57 +0100 Subject: [PATCH 3/3] fix(types): preserve original OrderResponse field requirements to avoid breaking changes Keep success, errorMsg, transactionsHashes, takingAmount, and makingAmount as required fields (matching upstream) and add transactTime/owner as optional. This resolves the Bugbot finding about divergent type definitions without breaking existing consumers. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/types.ts | 10 +++++----- tests/client/types.test.ts | 19 +++++++++++++------ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/types.ts b/src/types.ts index 87c4daa..325d89a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -194,13 +194,13 @@ export interface BanStatus { export interface OrderResponse { orderID: string; status: string; + success: boolean; + errorMsg: string; + transactionsHashes: string[]; + takingAmount: string; + makingAmount: string; transactTime?: string; owner?: string; - success?: boolean; - errorMsg?: string; - transactionsHashes?: string[]; - takingAmount?: string; - makingAmount?: string; } export interface OpenOrder { diff --git a/tests/client/types.test.ts b/tests/client/types.test.ts index 0e4daac..672a00f 100644 --- a/tests/client/types.test.ts +++ b/tests/client/types.test.ts @@ -105,6 +105,11 @@ describe("typed return values", () => { const response: OrderResponse = { orderID: "0x123", status: "matched", + success: true, + errorMsg: "", + transactionsHashes: ["0xhash1"], + takingAmount: "100", + makingAmount: "50", transactTime: "1234567890", owner: "0xabc", }; @@ -113,6 +118,7 @@ describe("typed return values", () => { const typed: OrderResponse = response; expect(typed.orderID).to.equal("0x123"); expect(typed.status).to.equal("matched"); + expect(typed.success).to.equal(true); expect(typed.transactTime).to.equal("1234567890"); expect(typed.owner).to.equal("0xabc"); }); @@ -121,13 +127,14 @@ describe("typed return values", () => { const minimal: OrderResponse = { orderID: "0x456", status: "live", - transactTime: "999", - owner: "0xdef", + success: false, + errorMsg: "", + transactionsHashes: [], + takingAmount: "0", + makingAmount: "0", }; - expect(minimal.errorMsg).to.be.undefined; - expect(minimal.takingAmount).to.be.undefined; - expect(minimal.makingAmount).to.be.undefined; - expect(minimal.transactionsHashes).to.be.undefined; + expect(minimal.transactTime).to.be.undefined; + expect(minimal.owner).to.be.undefined; }); });