You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: changelog.md
+26Lines changed: 26 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,32 @@
2
2
3
3
All notable changes to this project will be documented in this file.
4
4
5
+
## [2.50.12] - 2026-06-18
6
+
7
+
A live-verification sweep across Router methods, the catalog-UUID path, fetch_my_trades, curl examples, the self-hosted path, hosted SELL, hosted limit orders, and Limitless hosted writes turned up 7 HIGH-severity bugs. Six are fixed and verified live in this patch; the seventh needs a design call before any change. Plus one security note: see the bottom.
8
+
9
+
### Fixed
10
+
11
+
-**`sdks/python/pmxt/client.py` + `sdks/typescript/pmxt/client.ts` — `fetch_my_trades` was the fourth method missing the hosted-mode routing branch.** The 2.50.11 fix covered `fetch_balance`, `fetch_positions`, `fetch_order` but I missed `fetch_my_trades`. Same class of bug, same symptom: hosted users saw `[]` even when they had real trades on the wire. Added the hosted branch in both SDKs using the existing `fetch_my_trades` route key (`GET /v0/user/{address}/trades`) and the existing `user_trade_from_v0` / `userTradeFromV0` mappers. Live verification with the test wallet `0xcb856…0cD1`: now returns **82 trades** including the recent orders 365 (Spain BUY), 366 (Spain SELL), and 367 (Limitless DOGE) with correct venue mapping. Sorry for missing this in 2.50.11.
12
+
13
+
- **`sdks/python/pmxt/_hosted_typeddata.py` — hosted limit BUY was un-submittable due to a validator/helper denom contradiction.** `_hosted_denom()` in `client.py` correctly returned `denom="shares"` for limit BUY (the user passes a share quantity at an explicit price; the server computes `max_cost_usdc = shares × price + slippage buffer`). But `_validate_polymarket_buy_economics` hardcoded `denom="usdc"` for all BUYs regardless of order type. The validator raised `InvalidSignature("economic mismatch: denom expected 'usdc' got 'shares'")` locally before any HTTP call, so `client.create_order(side="buy", order_type="limit", ...)` could never reach the server. The validator was wrong, not the helper. Patched the validator to branch on `order_type`: market BUY keeps the exact-equality `max_cost_usdc == amount` check; limit BUY accepts `denom="shares"` and floor-checks `max_cost_usdc >= shares × price` (server-side slippage buffer means strict equality isn't possible). Verified live: hosted limit BUY for 10 Spain YES shares at $0.05 returned `Order(id="368", status="queued")` — accepted by the venue, no `InvalidSignature`, no 501.
14
+
15
+
-**`docs/trading-quickstart.mdx:165` and `docs/guides/hosted-errors.mdx:199` — the "hosted limit orders return 501" claim was fictional.** Live test: hosted limit SELL reaches the venue and gets normal business errors (e.g. 400 `Insufficient escrowed tokens`); no 501 anywhere in the path. Dropped the 501 sentence from both pages. Trading-quickstart now states hosted limit orders are supported via `client.create_order(order_type="limit", price=..., amount=...)` with the same 5-share / $1 marketable-BUY minimums; flagged that limit BUY had an SDK denom mismatch (fixed in the same release above, so by the time anyone reads this changelog the flag is historical). Hosted-errors `NoLiquidity` recovery snippet had the same 501 claim — rewrote it to point at the working limit-order path.
16
+
17
+
-**`docs/router/prices.mdx` — `router.fetch_related_markets` doc example couldn't run.** Doc showed `router.fetch_related_markets(market_id="...")` returning typed dataclasses with `r.relation`, `r.venue`, `r.best_bid` attribute access. Reality: the SDK signature is `fetch_related_markets(self, params: dict, **kwargs)` (inherited from `Exchange` with no Router override at `sdks/python/pmxt/router.py`), so the kwarg form raises `TypeError`. Return is a list of plain dicts with camelCase keys (`market`, `relation`, `confidence`, `reasoning`, `bestBid`, `bestAsk`, `venue`), not typed objects. Rewrote the example to match the real SDK: positional dict argument (`{"marketId": "..."}`) and dict-key access on the results. Follow-up worth doing: add a Router-level wrapper that takes kwargs and returns a typed `RelatedMarket` dataclass, mirroring how `compare_market_prices` returns typed `PriceComparison` rows. Tracked, not in this patch.
18
+
19
+
- **`sdks/python/pmxt/router.py` + `sdks/typescript/pmxt/router.ts` — `compare_market_prices` was returning empty `venue`, `best_bid`, `best_ask`.** The wire payload from `/api/router/compareMarketPrices` carries the data, but under different keys than the SDK was reading: bid/ask are nested under `market.bestBid`/`market.bestAsk` (where `_parse_market` already maps them onto `UnifiedMarket.best_bid`/`best_ask`), and venue is `market.sourceExchange` — there is no top-level `venue` / `bestBid` / `bestAsk` field. The SDK mapper read only the top-level fields and got nulls every time. Doc's printf example `f"{p.venue:12s} bid {p.best_bid:.2f} ask {p.best_ask:.2f}"` crashed on TypeError. Added a fallback chain in both Python and TS mappers — top-level → `market.best_bid` / `market.source_exchange` → `market_payload["bestBid"]` / `market_payload["sourceExchange"]`. Live verification on the `2026 World Cup Winner - Norway` market returned three populated rows across polymarket / kalshi / limitless. Same fix applied to `fetch_hedges` which used the parallel mapper.
20
+
21
+
-**`docs/api-reference/fetch-events.mdx:125` + `docs/api-reference/fetch-markets.mdx:123` — two curl examples returned HTTP 400.** Both passed `sort=volume` which the catalog now rejects: `{"error":"unsupported_params","message":"Parameters not supported by the catalog: sort"}`. Dropped `sort=volume` from both curls and the surrounding prose/Python/JS snippets, renamed those sections to "Filter active by category" / "Filter by status". Live verification: both corrected curls return HTTP 200. NOTE: `sort` is still declared as a real param in `core/src/BaseExchange.ts:81,111` and the generated `core/src/server/openapi.yaml` still publishes it. The catalog dropped support without updating the SDK or OpenAPI. Either re-implement `sort` on the catalog or remove it from the SDK type signature — tracked separately, not in this patch.
22
+
23
+
### Investigated, no fix shipped
24
+
25
+
- **"Router leaks Kalshi-shape outcome IDs into Polymarket query results" turned out to be a mis-framing.** The IDs in question (`42220:605:N`) are not Kalshi-shape — they're Myriad-native: `{networkId=42220 (Celo)}:{marketId=605}:{outcomeId=N}`. Constructed by design in `core/src/exchanges/myriad/normalizer.ts:52` and `utils.ts:74`; negative values like `-8` are the "Not" leg of a binary outcome. The Router query that surfaced these returned `sourceExchange=myriad` rows, not `polymarket` rows; my initial diagnosis was wrong. The REAL issue: the SDK's `_looks_like_catalog_uuid` check at `sdks/python/pmxt/client.py:764` doesn't recognize venue-native composite IDs like Myriad's, so they get forwarded to the hosted backend as `(venue=myriad, venue_outcome_id="42220:605:N")` — which is technically correct but the backend resolver may or may not accept that shape. The fix needs a design call between three options: (a) widen `_looks_like_catalog_uuid` to recognize Myriad composites, (b) make the catalog assign UUIDs to Myriad outcomes (currently Myriad's normalizer uses the venue-native composite as the `outcome_id`), or (c) ensure the backend resolver accepts the composite venue-native shape. Tracked for separate work.
26
+
27
+
### Security note
28
+
29
+
During the `compare_market_prices` live-verification curl, the API key was emitted to stdout via `curl -sv` (the verbose flag echoes the `Authorization` header). The key was already in scope (loaded from `.env`), but rotating `PMXT_API_KEY` at `pmxt.dev/dashboard` would be prudent.
30
+
5
31
## [2.50.11] - 2026-06-18
6
32
7
33
End-to-end live verification of the 2.50.10 doc claims surfaced two real SDK routing bugs and one hosted-API response-shape bug. All three fixed in parallel and verified live against `trade.pmxt.dev` with the test wallet `0xcb856a79c3E6490e0cFD7934eB59326E593C0cD1`.
**Recovery:** wait for liquidity, pick a different outcome, or run [self-hosted](/guides/self-hosted) for resting limit orders (hosted limit orders are not yet available).
199
+
**Recovery:** wait for liquidity, pick a different outcome, or post a resting limit order via `client.create_order(order_type="limit", price=..., amount=...)` (limit SELL is live; limit BUY is being patched for an SDK denom mismatch).
Copy file name to clipboardExpand all lines: docs/trading-quickstart.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -162,7 +162,7 @@ The hosted trading API accepts either the venue-native identifier (returned by `
162
162
</Note>
163
163
164
164
<Note>
165
-
**What PMXT abstracts vs what leaks through from the venue.** PMXT unifies the order shape across venues, but a few Polymarket rules pass through as-is. Polymarket enforces **two independent minimums on marketable BUY orders** — a **5-share minimum** AND a **$1 notional minimum** — and the higher of the two binds. At $0.138/share the 5-share rule alone would only require $0.69, but the $1 rule raises the effective minimum to ~8 shares (~$1.10); a 5-share buy at that price is rejected by the venue with `invalid amount for a marketable BUY order ($0.69), min size: 1` and PMXT surfaces it as `OrderSizeTooSmall`. Market orders are **budget-capped** (a buy spends exactly `amount` USDC, a sell sells exactly `amount` shares — `slippage_pct` is ignored). Hosted **limit** orders are not yet available and return `501`; use [self-hosted](/guides/self-hosted)for resting limits.
165
+
**What PMXT abstracts vs what leaks through from the venue.** PMXT unifies the order shape across venues, but a few Polymarket rules pass through as-is. Polymarket enforces **two independent minimums on marketable BUY orders** — a **5-share minimum** AND a **$1 notional minimum** — and the higher of the two binds. At $0.138/share the 5-share rule alone would only require $0.69, but the $1 rule raises the effective minimum to ~8 shares (~$1.10); a 5-share buy at that price is rejected by the venue with `invalid amount for a marketable BUY order ($0.69), min size: 1` and PMXT surfaces it as `OrderSizeTooSmall`. Market orders are **budget-capped** (a buy spends exactly `amount` USDC, a sell sells exactly `amount` shares — `slippage_pct` is ignored). Hosted **limit** orders are supported via `client.create_order(order_type="limit", price=..., amount=...)`and the same 5-share and $1 marketable-BUY minimums apply; limit BUY is currently being patched for an SDK denom mismatch, limit SELL is live.
0 commit comments