Skip to content

Commit bc77cdd

Browse files
committed
release: 2.50.10 — fix 8 HIGH SDK/docs code-correctness bugs (incl. 2.50.6 regressions)
1 parent 4b0e34d commit bc77cdd

9 files changed

Lines changed: 57 additions & 55 deletions

File tree

changelog.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [2.50.10] - 2026-06-18
6+
7+
### Fixed
8+
9+
A code-correctness audit against the SDK source found 8 HIGH-severity bugs in published docs — code blocks that would TypeError, AttributeError, or silently no-op. Two of them I introduced myself in 2.50.6 by rewriting against the wrong SDK truth. All fixed in parallel with three agents, each verifying against the SDK source line they cite.
10+
11+
- **`docs/authentication.mdx:107` + `docs/guides/self-hosted.mdx:93`**: Python `create_order(type="limit", ...)` would raise `TypeError: unexpected keyword argument 'type'`. The parameter is `order_type=`, not `type=` (`sdks/python/pmxt/client.py:2892`). Fixed both call sites.
12+
- **`docs/guides/escrow-lifecycle.mdx:118,124`**: Self-introduced regression from 2.50.6. After fixing `fetch_balance` to return a list, I documented `usdc.free / .used / .total` — those fields don't exist. The Balance dataclass exposes `available` / `locked` / `total` (`sdks/python/pmxt/models.py:596-603`; `sdks/typescript/pmxt/models.ts:387-393`). `.free` and `.used` would AttributeError on first access. Updated both Python and TypeScript examples to use the real field names.
13+
- **`docs/router/prices.mdx:206`**: `poly.fetch_order_book(market_id=clusters[0].markets[0].market_id)``fetch_order_book` takes `outcome_id`, not `market_id` (`sdks/python/pmxt/client.py:1441`). The `_compat_id` resolver at line 1446 would catch the missing required arg and raise `TypeError: Missing required argument: 'outcome_id'`. Updated to pull an outcome ID from `clusters[0].markets[0].outcomes[0].outcome_id`.
14+
- **`docs/concepts/catalog-uuid-vs-venue-id.mdx:48-56`**: The "reverse-resolve a venue ID to a catalog UUID" example showed `router.fetch_matched_market_clusters(venue="polymarket", venue_market_id="0x...")` then read `clusters[0].market_id`. None of those exist: the method signature (`sdks/python/pmxt/router.py:328-350`) accepts only `market_id`, `slug`, `url`, `query`, `venues`/`exclude_venues`, and `MatchedMarketCluster` (`models.py:939-964`) has `cluster_id` + `markets[]` with no top-level `market_id`. There is no clean SDK reverse-resolution API for the venue→catalog mapping. Replaced the broken example with a `POST /v0/sql` query against `prediction_markets.markets` filtered by `venue` + `venue_market_id` — which is what the next paragraph of the page already pointed at.
15+
- **`docs/guides/signing.mdx``## Advanced: bring your own signer`**: The 2.50.6 rewrite of this section shipped a Python pattern that doesn't work and a TypeScript pattern that throws on the first call. Two distinct bugs:
16+
- **Python:** the pattern was `built = client.build_order(...); built.signed_order = my_signer.sign_typed_data(...); client.submit_order(built)`. In hosted mode, `submit_order` ignores `built.signed_order` and re-signs internally with `self.signer` (`client.py:875-895` in `_call_hosted_signer`; `client.py:911` reads `signer or self.signer`; `client.py:3203` `_hosted_submit_body(..., signer=None)` falls back). The custom signature was silently discarded and the user's `private_key` was used instead. Anyone following the doc would think their custom signer worked when in fact only the `private_key` path was active.
17+
- **TypeScript:** worse — `submitOrder` is explicitly disabled in hosted mode and throws `PmxtError("submitOrder is not available in hosted mode. Use createOrder instead.")` (`client.ts:1134-1137`). The TS recipe could never run.
18+
- **Correct pattern:** constructor injection. Python `pmxt.Polymarket(..., signer=my_signer)` (`client.py:336` — constructor accepts `signer: Optional[Any] = None`). TS `new Polymarket({ ..., signer: mySigner })` (`client.ts:288,335`).
19+
- **Signer protocol:** the Python signer must implement `sign_typed_data(typed_data: dict) -> hex` — taking the WHOLE typed-data dict (`client.py:875-895` iterates `("sign_typed_data", "sign")` and passes the full dict). The 2.50.6 docs incorrectly showed a split `domain=/types=/message=` signature; that was wrong. The TS Signer protocol (`signers.ts:33-35`) is `{ address: string; signTypedData(typedData: TypedData): Promise<string> }`. Rewrote both examples to constructor-inject and call `create_order` / `createOrder`. Removed the build/sign/submit dance entirely — it's not a hosted-mode pattern.
20+
- **`docs/guides/signing.mdx` — TS field names**: TS `buildOrder`/`createOrder` examples used `orderType:` and `slippagePct:`. The `CreateOrderParams` interface (`models.ts:533-545`) uses `type:` (not `orderType`). Slippage is even more unusual — TS forwards it through the params extension dict as the snake_case key `slippage_pct:` (`client.ts:2429-2430`). The TS field name really is snake_case here; that's not a typo. Updated all TS examples.
21+
- **`docs/guides/hosted-errors.mdx` — TS `NoLiquidity` recovery (`line 214`)**: same `orderType:``type:` fix.
22+
- **`docs/guides/hosted-errors.mdx``BuiltOrderExpired` retry pattern (`lines 150-187`)**: same build/sign/submit pattern problem as `signing.mdx`. The retry example showed `built.signed_order = ...; submit_order(built)`. Same issue: Python hosted ignores the assignment and re-signs; TS hosted throws. Rewrote both examples to call `create_order` / `createOrder` directly inside the try/except — the SDK's convenience wrapper handles build → sign → submit atomically, so a `BuiltOrderExpired` retry is just calling `create_order` again.
23+
- **`docs/api-reference/fetch-ohlcv.mdx:7`**: Broken internal link `/dashboard` (an in-docs path that doesn't exist) replaced with the external `https://pmxt.dev/dashboard`. The only broken internal link in the docs tree per the link-integrity sweep.
24+
525
## [2.50.9] - 2026-06-18
626

727
### Docs

docs/api-reference/fetch-ohlcv.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ openapi: GET /api/{exchange}/fetchOHLCV
44
---
55

66
<Info>
7-
**More data with an API key** — Without an API key, OHLCV is limited to Polymarket's native API: short history windows, no volume, and venue-specific rate limits. With a PMXT API key you get volume, 1+ years of history, and normalized candles across venues. Kalshi, Limitless, and more are coming soon. [Get your API key](/dashboard).
7+
**More data with an API key** — Without an API key, OHLCV is limited to Polymarket's native API: short history windows, no volume, and venue-specific rate limits. With a PMXT API key you get volume, 1+ years of history, and normalized candles across venues. Kalshi, Limitless, and more are coming soon. [Get your API key](https://pmxt.dev/dashboard).
88
</Info>
99

1010
## Use cases

docs/authentication.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ directly:
104104
order = poly.create_order(
105105
outcome=market.yes,
106106
side="buy",
107-
type="limit",
107+
order_type="limit",
108108
price=0.42,
109109
amount=100,
110110
)

docs/concepts/catalog-uuid-vs-venue-id.mdx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,16 @@ In [self-hosted mode](/guides/self-hosted) — `POST /api/{exchange}/createOrder
4545

4646
If you already have a venue-native id (e.g. a saved database of Polymarket `conditionId`s) and want the catalog UUID for cross-venue work:
4747

48-
```python
49-
clusters = router.fetch_matched_market_clusters(
50-
venue="polymarket",
51-
venue_market_id="0xc704f74e2f9dfae70f770cb253ffadde10768eeab41233098bf5ac67995a94b5",
52-
)
53-
catalog_uuid = clusters[0].market_id
48+
The SDK does not expose a single-shot `venue_market_id` -> catalog UUID kwarg today. For batch or one-off reverse lookups, query the catalog directly via `POST /v0/sql`:
49+
50+
```sql
51+
SELECT market_id
52+
FROM prediction_markets.markets
53+
WHERE venue = 'polymarket'
54+
AND venue_market_id = '0xc704f74e2f9dfae70f770cb253ffadde10768eeab41233098bf5ac67995a94b5';
5455
```
5556

56-
For batch workflows, `POST /v0/sql` gives you direct access to `prediction_markets.markets` and `prediction_markets.outcomes` with the canonical UUID alongside the venue-native fields (`venue_market_id`, `venue_outcome_id` / `token_id`).
57+
`prediction_markets.markets` and `prediction_markets.outcomes` carry the canonical UUID alongside the venue-native fields (`venue_market_id`, `venue_outcome_id` / `token_id`), so the same query shape covers outcomes too.
5758

5859
## Examples (the two ids for one market)
5960

docs/guides/escrow-lifecycle.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,13 @@ After the deposit transaction confirms on-chain, the balance shows up in escrow.
115115
```python Python
116116
balances = client.fetch_balance()
117117
usdc = balances[0]
118-
print(f"Free: {usdc.free}, Used: {usdc.used}, Total: {usdc.total}")
118+
print(f"Available: {usdc.available}, Locked: {usdc.locked}, Total: {usdc.total}")
119119
```
120120

121121
```typescript TypeScript
122122
const balances = await client.fetchBalance();
123123
const usdc = balances[0];
124-
console.log(`Free: ${usdc.free}, Used: ${usdc.used}, Total: ${usdc.total}`);
124+
console.log(`Available: ${usdc.available}, Locked: ${usdc.locked}, Total: ${usdc.total}`);
125125
```
126126
</CodeGroup>
127127

docs/guides/hosted-errors.mdx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,12 @@ Hardware-wallet signing is the most common cause — Ledger confirmations can ta
151151
```python Python
152152
from pmxt._hosted_errors import BuiltOrderExpired
153153

154-
def submit_with_retry(client, *, market_id, outcome_id, **kwargs):
154+
def submit_with_retry(client, **kwargs):
155+
# In hosted mode, create_order handles build -> sign -> submit atomically.
156+
# On BuiltOrderExpired the internal submit raced the 30s TTL; just call again.
155157
for attempt in range(2):
156-
built = client.build_order(market_id=market_id, outcome_id=outcome_id, **kwargs)
157-
built.signed_order = signer.sign_typed_data(built.raw["typed_data"])
158158
try:
159-
return client.submit_order(built)
159+
return client.create_order(**kwargs)
160160
except BuiltOrderExpired:
161161
if attempt == 1:
162162
raise
@@ -165,23 +165,20 @@ def submit_with_retry(client, *, market_id, outcome_id, **kwargs):
165165

166166
```typescript TypeScript
167167
import { BuiltOrderExpired } from "pmxtjs";
168+
import type { CreateOrderParams, Order } from "pmxtjs";
168169

169-
async function submitWithRetry(client, params) {
170+
async function submitWithRetry(client, params: CreateOrderParams): Promise<Order> {
171+
// In hosted mode, createOrder handles build -> sign -> submit atomically.
172+
// On BuiltOrderExpired the internal submit raced the 30s TTL; just call again.
170173
for (let attempt = 0; attempt < 2; attempt++) {
171-
const built = await client.buildOrder(params);
172-
const typedData = built.raw.typed_data;
173-
built.signedOrder = await signer.signTypedData(
174-
typedData.domain,
175-
typedData.types,
176-
typedData.message,
177-
);
178174
try {
179-
return await client.submitOrder(built);
175+
return await client.createOrder(params);
180176
} catch (e) {
181177
if (e instanceof BuiltOrderExpired && attempt === 0) continue;
182178
throw e;
183179
}
184180
}
181+
throw new Error("unreachable");
185182
}
186183
```
187184
</CodeGroup>
@@ -211,7 +208,7 @@ except NoLiquidity:
211208
import { NoLiquidity } from "pmxtjs";
212209

213210
try {
214-
await client.createOrder({ orderType: "market", ... });
211+
await client.createOrder({ type: "market", ... });
215212
} catch (e) {
216213
if (e instanceof NoLiquidity) {
217214
// Wait, retry against a different outcome, or post limits via self-hosted.

docs/guides/self-hosted.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ poly = pmxt.Polymarket(private_key="0xYourPrivateKey...")
9090
order = poly.create_order(
9191
outcome=market.yes,
9292
side="buy",
93-
type="limit",
93+
order_type="limit",
9494
price=0.42,
9595
amount=100,
9696
)

docs/guides/signing.mdx

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ const order = await client.createOrder({
4343
marketId: "2eeb03dc-404b-41d5-bc57-6aeb37927ae6",
4444
outcomeId: "a114f052-1fd1-4bcd-b9cf-de019db81b67",
4545
side: "buy",
46-
orderType: "market",
46+
type: "market",
4747
amount: 5,
4848
denom: "usdc",
49-
slippagePct: 30,
49+
slippage_pct: 30,
5050
});
5151
```
5252
</CodeGroup>
@@ -72,17 +72,19 @@ Because reads don't require a signature, anyone with the `pmxt_api_key` can read
7272

7373
## Advanced: bring your own signer
7474

75-
If your key lives in a hardware wallet, HSM, MPC service, or anything that isn't a raw hex private key, skip `private_key` and use the lower-level `build_order` / `submit_order` flow. Any object that implements `sign_typed_data(domain, types, message) -> hex` works.
75+
If your key lives in a hardware wallet, HSM, MPC service, or anything that isn't a raw hex private key, skip `private_key` and inject a custom signer at construction. The SDK uses it transparently for every hosted write — you keep calling `create_order` as usual.
76+
77+
The signer protocol is one method: `sign_typed_data(typed_data: dict) -> hex` (Python) or `signTypedData(typedData): Promise<string>` plus a readonly `address` (TypeScript).
7678

7779
<CodeGroup>
7880
```python Python
7981
client = pmxt.Polymarket(
8082
pmxt_api_key="pmxt_live_...",
8183
wallet_address="0xYourWallet...",
82-
# no private_key
84+
signer=my_custom_signer, # exposes sign_typed_data(typed_data) -> hex
8385
)
8486

85-
built = client.build_order(
87+
order = client.create_order(
8688
market_id="2eeb03dc-...",
8789
outcome_id="a114f052-...",
8890
side="buy",
@@ -91,43 +93,25 @@ built = client.build_order(
9193
denom="usdc",
9294
slippage_pct=30.0,
9395
)
94-
95-
typed_data = built.raw["typed_data"]
96-
signature = my_custom_signer.sign_typed_data(
97-
domain=typed_data["domain"],
98-
types=typed_data["types"],
99-
message=typed_data["message"],
100-
)
101-
built.signed_order = signature # attach signature for submit
102-
order = client.submit_order(built)
10396
```
10497

10598
```typescript TypeScript
10699
const client = new Polymarket({
107100
pmxtApiKey: "pmxt_live_...",
108101
walletAddress: "0xYourWallet...",
109-
// no privateKey
102+
signer: myCustomSigner, // { address, signTypedData(typedData) -> Promise<hex> }
110103
});
111104

112-
const built = await client.buildOrder({
105+
const order = await client.createOrder({
113106
marketId: "2eeb03dc-...",
114107
outcomeId: "a114f052-...",
115108
side: "buy",
116-
orderType: "market",
109+
type: "market",
117110
amount: 5,
118111
denom: "usdc",
119-
slippagePct: 30,
112+
slippage_pct: 30,
120113
});
121-
122-
const typedData = built.raw.typed_data;
123-
const signature = await myCustomSigner.signTypedData(
124-
typedData.domain,
125-
typedData.types,
126-
typedData.message,
127-
);
128-
built.signedOrder = signature;
129-
const order = await client.submitOrder(built);
130114
```
131115
</CodeGroup>
132116

133-
For Opinion, `built` carries two payloads (`typed_data` and `pull_typed_data`); sign both and pass `signature` + `pull_signature` to `submit_order`. The SDK's `EthAccountSigner` / `EthersSigner` are reference implementations of the signer protocol — ethers v6 supports Ledger out of the box; for MPC see Fireblocks / Privy / Turnkey docs.
117+
For Opinion's dual-signature flow, the same injected signer is invoked twice (once per payload) — no extra wiring. The SDK's `EthAccountSigner` (Python) / `EthersSigner` (TypeScript) are reference implementations; ethers v6 supports Ledger out of the box, and for MPC see Fireblocks / Privy / Turnkey docs.

docs/router/prices.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ poly = pmxt.Polymarket(private_key="0x...")
203203
clusters = router.fetch_matched_market_clusters(relation="identity", limit=20)
204204

205205
# 2. Inspect the order book on a specific venue
206-
book = poly.fetch_order_book(market_id=clusters[0].markets[0].market_id)
206+
book = poly.fetch_order_book(outcome_id=clusters[0].markets[0].outcomes[0].outcome_id)
207207

208208
# 3. Place an order locally (never proxied through PMXT)
209209
# poly.create_order(...)

0 commit comments

Comments
 (0)