Skip to content

Commit 7f80ce2

Browse files
committed
feat: add authenticated account actions
1 parent f0b346e commit 7f80ce2

19 files changed

Lines changed: 1074 additions & 133 deletions

docs/api-boundary-notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
# API Boundary Notes
2+
3+
- `GET /balance-allowance` currently returns `{ balance, allowances }`, where `allowances` is keyed by spender address. `clob-client` still models a single `allowance` string.

docs/open-questions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ Use this document to capture unresolved SDK questions that should be revisited l
99
- CLOB docs say: "All query endpoints require L2 authentication. Builder-authenticated clients can also query orders attributed to their builder account using the same methods." Does builder authentication alone suffice for any standard private query endpoints, or is it only additive to L2 auth? Current backend and `clob-client` behavior appear to require L2 first.
1010
- What are the intended use cases for deriving or creating more than one API key per wallet? Current docs and `clob-client` guidance seem to suggest repeated key creation should generally not be needed.
1111
- How many existing builders or integrators rely on `clob-client` with `throwOnError: true`, given that it changes the effective behavior of helpers like `createOrDeriveApiKey()`?
12+
- What are the intended product and SDK use cases for readonly user API keys, and should they be part of the first `@polymarket/client` release at all?

docs/sdk-direction.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ The first shipping target is `@polymarket/client`. Its job is to make Polymarket
1717
- The SDK should feel pragmatic and typed, staying close to real integration needs without forcing consumers to understand how current services are divided.
1818
- The SDK can still expose lower-level controls when they are useful, but the default experience should feel unified.
1919

20+
## Current Decisions
21+
22+
- Omit readonly API key management from the first `@polymarket/client` surface for now.
23+
- Keep phase 2 focused on standard authenticated account reads that are clearly part of the primary trading workflow.
24+
- Revisit readonly API keys later if there is a concrete SDK use case and clearer public documentation.
25+
26+
## TODO
27+
28+
- In this case, make CLOB responses use camelCased fields.
29+
2030
## Package Direction
2131

2232
- `@polymarket/client` is the main near-term package.
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { z } from 'zod';
2+
3+
function createCursorPageSchema<TItem extends z.ZodTypeAny>(item: TItem) {
4+
return z.object({
5+
count: z.number(),
6+
data: z.array(item),
7+
limit: z.number(),
8+
next_cursor: z.string(),
9+
});
10+
}
11+
12+
export const ClosedOnlyModeSchema = z.object({
13+
closed_only: z.boolean(),
14+
});
15+
16+
export type ClosedOnlyMode = z.infer<typeof ClosedOnlyModeSchema>;
17+
18+
export const OpenOrderSchema = z.object({
19+
asset_id: z.string(),
20+
associate_trades: z.array(z.string()),
21+
created_at: z.number(),
22+
expiration: z.string(),
23+
id: z.string(),
24+
maker_address: z.string(),
25+
market: z.string(),
26+
order_type: z.string(),
27+
original_size: z.string(),
28+
outcome: z.string(),
29+
owner: z.string(),
30+
price: z.string(),
31+
side: z.string(),
32+
size_matched: z.string(),
33+
status: z.string(),
34+
});
35+
36+
export type OpenOrder = z.infer<typeof OpenOrderSchema>;
37+
38+
export const OpenOrdersPageSchema = createCursorPageSchema(OpenOrderSchema);
39+
40+
export type OpenOrdersPage = z.infer<typeof OpenOrdersPageSchema>;
41+
42+
export const MakerOrderSchema = z.object({
43+
asset_id: z.string(),
44+
fee_rate_bps: z.string(),
45+
maker_address: z.string(),
46+
matched_amount: z.string(),
47+
order_id: z.string(),
48+
outcome: z.string(),
49+
owner: z.string(),
50+
price: z.string(),
51+
side: z.string(),
52+
});
53+
54+
export const ClobTradeSchema = z.object({
55+
asset_id: z.string(),
56+
bucket_index: z.number(),
57+
fee_rate_bps: z.string(),
58+
id: z.string(),
59+
last_update: z.string(),
60+
maker_address: z.string(),
61+
maker_orders: z.array(MakerOrderSchema),
62+
market: z.string(),
63+
match_time: z.string(),
64+
outcome: z.string(),
65+
owner: z.string(),
66+
price: z.string(),
67+
side: z.string(),
68+
size: z.string(),
69+
status: z.string(),
70+
taker_order_id: z.string(),
71+
trader_side: z.enum(['TAKER', 'MAKER']),
72+
transaction_hash: z.string(),
73+
});
74+
75+
export type ClobTrade = z.infer<typeof ClobTradeSchema>;
76+
77+
export const ClobTradesPageSchema = createCursorPageSchema(ClobTradeSchema);
78+
79+
export type ClobTradesPage = z.infer<typeof ClobTradesPageSchema>;
80+
81+
export const NotificationSchema = z.object({
82+
owner: z.string(),
83+
payload: z.unknown(),
84+
type: z.number(),
85+
});
86+
87+
export type Notification = z.infer<typeof NotificationSchema>;
88+
89+
export const NotificationsResponseSchema = z.array(NotificationSchema);
90+
91+
export type NotificationsResponse = z.infer<typeof NotificationsResponseSchema>;
92+
93+
export enum AssetType {
94+
COLLATERAL = 'COLLATERAL',
95+
CONDITIONAL = 'CONDITIONAL',
96+
}
97+
98+
export const AssetTypeSchema = z.nativeEnum(AssetType);
99+
100+
export const BalanceAllowanceResponseSchema = z.object({
101+
allowances: z.record(z.string(), z.string()),
102+
balance: z.string(),
103+
});
104+
105+
export type BalanceAllowanceResponse = z.infer<
106+
typeof BalanceAllowanceResponseSchema
107+
>;
108+
109+
export const OrderScoringResponseSchema = z.object({
110+
scoring: z.boolean(),
111+
});
112+
113+
export type OrderScoringResponse = z.infer<typeof OrderScoringResponseSchema>;
114+
115+
export const OrdersScoringResponseSchema = z.record(z.string(), z.boolean());
116+
117+
export type OrdersScoringResponse = z.infer<typeof OrdersScoringResponseSchema>;
118+
119+
export const UserEarningSchema = z.object({
120+
asset_address: z.string(),
121+
asset_rate: z.number(),
122+
condition_id: z.string(),
123+
date: z.string(),
124+
earnings: z.number(),
125+
maker_address: z.string(),
126+
});
127+
128+
export type UserEarning = z.infer<typeof UserEarningSchema>;
129+
130+
export const UserEarningsPageSchema = createCursorPageSchema(UserEarningSchema);
131+
132+
export type UserEarningsPage = z.infer<typeof UserEarningsPageSchema>;
133+
134+
export const TotalUserEarningSchema = z.object({
135+
asset_address: z.string(),
136+
asset_rate: z.number(),
137+
date: z.string(),
138+
earnings: z.number(),
139+
maker_address: z.string(),
140+
});
141+
142+
export type TotalUserEarning = z.infer<typeof TotalUserEarningSchema>;
143+
144+
export const TotalUserEarningsResponseSchema = z.array(TotalUserEarningSchema);
145+
146+
export type TotalUserEarningsResponse = z.infer<
147+
typeof TotalUserEarningsResponseSchema
148+
>;
149+
150+
export const RewardsPercentagesSchema = z.record(z.string(), z.number());
151+
152+
export type RewardsPercentages = z.infer<typeof RewardsPercentagesSchema>;
153+
154+
export const TokenSchema = z.object({
155+
outcome: z.string(),
156+
price: z.number(),
157+
token_id: z.string(),
158+
});
159+
160+
export const RewardsConfigSchema = z.object({
161+
asset_address: z.string(),
162+
end_date: z.string(),
163+
rate_per_day: z.number(),
164+
start_date: z.string(),
165+
total_rewards: z.number(),
166+
});
167+
168+
export const EarningSchema = z.object({
169+
asset_address: z.string(),
170+
asset_rate: z.number(),
171+
earnings: z.number(),
172+
});
173+
174+
export const UserRewardsEarningSchema = z.object({
175+
condition_id: z.string(),
176+
earning_percentage: z.number(),
177+
earnings: z.array(EarningSchema),
178+
event_slug: z.string(),
179+
image: z.string(),
180+
maker_address: z.string(),
181+
market_competitiveness: z.number(),
182+
market_slug: z.string(),
183+
question: z.string(),
184+
rewards_config: z.array(RewardsConfigSchema),
185+
rewards_max_spread: z.number(),
186+
rewards_min_size: z.number(),
187+
tokens: z.array(TokenSchema),
188+
});
189+
190+
export type UserRewardsEarning = z.infer<typeof UserRewardsEarningSchema>;
191+
192+
export const UserRewardsEarningsPageSchema = createCursorPageSchema(
193+
UserRewardsEarningSchema,
194+
);
195+
196+
export type UserRewardsEarningsPage = z.infer<
197+
typeof UserRewardsEarningsPageSchema
198+
>;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
export * from './account';
12
export * from './api-key';
23
export * from './fee-rate';
34
export * from './neg-risk';
5+
export * from './pagination';
6+
export * from './signature-type';
47
export * from './tick-size';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const INITIAL_CURSOR = 'MA==';
2+
export const END_CURSOR = 'LTE=';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { z } from 'zod';
2+
3+
export enum SignatureType {
4+
EOA = 0,
5+
POLY_PROXY = 1,
6+
POLY_GNOSIS_SAFE = 2,
7+
}
8+
9+
export const SignatureTypeSchema = z.enum(SignatureType);

packages/bindings/src/gamma/market.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import {
1111
TagReferenceSchema,
1212
} from './common';
1313

14+
const TwoStringTupleSchema = z
15+
.string()
16+
.transform((val) => JSON.parse(val))
17+
.pipe(z.tuple([z.string(), z.string()]));
18+
1419
export const MarketSchema = z.looseObject({
1520
id: MarketIdSchema,
1621
question: z.string().nullish(),
@@ -34,8 +39,8 @@ export const MarketSchema = z.looseObject({
3439
lowerBound: z.string().nullish(),
3540
upperBound: z.string().nullish(),
3641
description: z.string().nullish(),
37-
outcomes: z.string().nullish(),
38-
outcomePrices: z.string().nullish(),
42+
outcomes: TwoStringTupleSchema.nullish(),
43+
outcomePrices: TwoStringTupleSchema.nullish(),
3944
volume: z.string().nullish(),
4045
active: z.boolean().nullish(),
4146
marketType: z.string().nullish(),
@@ -87,7 +92,7 @@ export const MarketSchema = z.looseObject({
8792
volume1yr: z.number().nullish(),
8893
gameStartTime: z.string().nullish(),
8994
secondsDelay: z.number().int().nullish(),
90-
clobTokenIds: z.string().nullish(),
95+
clobTokenIds: TwoStringTupleSchema.nullish(),
9196
disqusThread: z.string().nullish(),
9297
shortOutcomes: z.string().nullish(),
9398
teamAID: z.string().nullish(),
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { z } from 'zod';
22

3-
export enum WalletTypeKind {
3+
export enum WalletType {
44
EOA = 0,
55
POLY_PROXY = 1,
66
POLY_GNOSIS_SAFE = 2,
77
}
88

99
export const WalletTypeSchema = z.object({
10-
type: z.enum(WalletTypeKind),
10+
type: z.enum(WalletType),
1111
typeName: z.enum(['EOA', 'POLY_PROXY', 'POLY_GNOSIS_SAFE']),
1212
});
13-
14-
export type WalletType = z.infer<typeof WalletTypeSchema>;

packages/client/src/ServiceClient.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class ServiceClient {
3434
RateLimitError | RequestRejectedError | TransportError
3535
> {
3636
return this.#toResult(
37-
this.#client.get(path, {
37+
this.#client.get(this.#normalizePath(path), {
3838
headers: options.headers,
3939
searchParams: options.params,
4040
}),
@@ -49,7 +49,7 @@ export class ServiceClient {
4949
RateLimitError | RequestRejectedError | TransportError
5050
> {
5151
return this.#toResult(
52-
this.#client.post(path, {
52+
this.#client.post(this.#normalizePath(path), {
5353
headers: options.headers,
5454
json: options.json,
5555
}),
@@ -60,6 +60,10 @@ export class ServiceClient {
6060
return never('ServiceClient.del is not implemented yet');
6161
}
6262

63+
#normalizePath(path: string) {
64+
return path.startsWith('/') ? path.slice(1) : path;
65+
}
66+
6367
#toResult(
6468
promise: Promise<Response>,
6569
): ResultAsync<

0 commit comments

Comments
 (0)