Releases: pmxt-dev/pmxt
Release list
v2.51.4
Fixed
- Hyperliquid normalizer now extracts
resolutionDatefrom question prose when noexpiry:tag is present. Hyperliquid'soutcomeMetapayload exposes no structured end-time on either the Outcome or Question objects. Price-bracket markets get a cleanexpiry:YYYYMMDD-HHmmin the outcome description (parsed); everything else (World Cup teams/matches, Fed funds decisions, CPI prints) puts the deadline only in the question description's legalese, e.g. "by October 14, 2026 at 23:59 UTC". The normalizer now scans the question description for<Month> <DD>[,] <YYYY> [at HH:MM UTC]matches and takes the latest — which is always the resolution deadline (questions often mention an event date and a later cutoff). Defaults to 23:59 UTC when no time is given. Verified against all 28 live HL questions: 27 resolve to the correct deadline (World Cup matches → 2026-07-19, World Cup champion → 2026-10-14, Fed funds → 2026-09-16, CPI → 2026-08-12); the one miss is theRecurringprice-bracket parent, which still falls through to the existing outcome-levelexpiry:tag.
Installation
npm:
npm install pmxtjs@2.51.4
npm install -g @pmxt/cli@2.51.4PyPI:
pip install pmxt==2.51.4Links
Full Changelog: v2.51.3f...v2.51.4f
v2.51.3
Fixed
- Hyperliquid normalizer no longer stamps
resolutionDatewith the Unix epoch when expiry is unknown.core/src/exchanges/hyperliquid/normalizer.tsusedexpiryDate ?? new Date(0)for the Outcome Markets path, which caused HL markets without a parseableexpiry:YYYYMMDD-HHmmmetadata tag (e.g., World Cup team markets) to be serialized withresolutionDate = 1970-01-01T00:00:00Z. Downstream consumers that gate on "is this market still tradable?" viaresolutionDate <= now()(hosted-pmxt's ingest sweep is one) would then mass-close otherwise-live markets. The field is already optional inUnifiedMarket, so the fallback is now plainundefined— consumers decide policy.
Installation
npm:
npm install pmxtjs@2.51.3
npm install -g @pmxt/cli@2.51.3PyPI:
pip install pmxt==2.51.3Links
What's Changed
- fix(core): use Rain contract address for price history by @realfishsam in #1300
- fix(ts-sdk): expose Limitless watch helpers by @realfishsam in #1301
Full Changelog: v2.51.2f...v2.51.3f
v2.51.2
Fixed
- Hunch list ingestion now surfaces live binary prices instead of zero-priced markets. The Hunch list/catalog item now carries
raw.odds; the normalizer uses those YES/NO cents on the bare list path while still letting explicit detail/quote odds win. This unblocks Hunch markets from price-gated cross-venue features such as compare, arbitrage, hedge, and matched-prices. - Hunch
volume24hnow passes throughraw.volume24hUsd. The previous hard-zero made the recency/ranking signal unusable for Hunch even when the venue provided a trailing-24h figure. - Hunch category and tags now map into PMXT taxonomy. Native Hunch categories roll up to top-level PMXT categories (
Cryptoby default,event→Culture) and add granular tags such asMarket Cap,Price, and the token symbol so?category=filters and market matching work with higher confidence. - Hunch catalog crawls now follow
nextCursor.fetchRawMarkets()drains the full paginated catalogue when no explicit limit is supplied, with a 50-page safety backstop; explicit limit calls still fetch a single page. - Consumer SDK/generated surfaces were synced for Hunch. Added Hunch to the Python and TypeScript SDK exports and made the client-method generators preserve existing hosted-mode overrides while codegen catches up, so the generated-sync checks stay green without regressing hosted routing.
Installation
npm:
npm install pmxtjs@2.51.2
npm install -g @pmxt/cli@2.51.2PyPI:
pip install pmxt==2.51.2Links
What's Changed
- feat(hunch): surface live odds + 24h volume + Crypto taxonomy + cursor draining by @rajkaria in #1293
Full Changelog: v2.51.1f...v2.51.2f
v2.51.1
Followup to the Hunch venue ship in 2.51.0: the adapter was wired into the runtime (factory, registry, exports) but hunch was missing from the ExchangeParam enum in core/scripts/generate-openapi.js. That enum is the source of truth for docs/concepts/venues.mdx (auto-generated), the OpenAPI path enum, and COMPLIANCE.md, so Hunch was invisible on pmxt.dev/docs/concepts/venues and absent from the compliance matrix.
Fixed
core/scripts/generate-openapi.js— addedhunchto theExchangeParamenum. Regeneratedcore/src/server/openapi.yaml,docs/api-reference/openapi.json, anddocs/concepts/venues.mdx(now 18 venues).core/scripts/generate-compliance.js— addedhunchtoEXCHANGE_ORDER. Regeneratedcore/COMPLIANCE.md(Hunch: 15/20 methods supported).README.md— added Hunch to the supported-exchanges row.
Installation
npm:
npm install pmxtjs@2.51.1
npm install -g @pmxt/cli@2.51.1PyPI:
pip install pmxt==2.51.1Links
Full Changelog: v2.51.0f...v2.51.1f
v2.51.0
New venue: Hunch (parimutuel prediction market on Base, x402 settlement). And a full assertion-based audit of the Hyperliquid SDK surface (both Python + TS) caught ten silent bugs that prior "did the call throw?" smoke tests had hidden — several since the venue was added. Read paths now have behavioral assertions on shape AND values; trading paths are verified end-to-end against a live mainnet wallet. Final HL state: every no-creds method behaviorally green in both SDKs (43/43 assertions), credentialed reads cross-checked vs HL raw API (open orders: 7/7 exact match; user trades: 2000/2000 match; synthesized closed orders: 104/104 match), and the full createOrder → openOrders → cancelOrder → openOrders-empty lifecycle proven live.
Added — Hunch venue (#1150)
core/src/exchanges/hunch/— new adapter for playhunch.xyz, a parimutuel (pool-based, not CLOB/AMM) prediction market on Base settling in USDC via x402 / EIP-3009. Inventory is crypto-native: token market-cap ladders, up/down, launchpad, date-window markets. Modeled on the Myriad adapter — market-orders only,fetchOrderBook: 'emulated'(single level synthesized from implied price + pool),cancelOrder/ limit / sell unsupported.- Surface:
fetchMarkets/fetchEvents(single-market wrap — Hunch has no event tier) /fetchOHLCV(flat candles from the odds tape) /fetchOrderBook(emulated) /fetchTrades/fetchPositions/fetchBalance. Binary markets → YES/NO outcomes; N-way (mcap ladders, date windows) → one outcome per bucket key.outcomeIdencodes${marketId}:${side}and round-trips back to a Hunch trade side. createOrderruns Hunch's x402 loop:POST /trade→ 402 → sign EIP-3009TransferWithAuthorization(viem) → re-POST withX-PAYMENT→ 200 receipt. Orders > $10 fetch a price-locked quote first. Verified end-to-end against the live 402 challenge (no funds moved).- Registration:
core/src/index.ts(export + registry +Hunch),core/src/server/exchange-factory.ts(case "hunch"),core/src/server/openapi.yaml(ExchangeParamenum),COMPLIANCE.md. - Self-hosted creds:
HUNCH_PRIVATE_KEY/HUNCH_WALLET_ADDRESS(Base). Hosted-mode trading (Polygon escrow) is out of scope — Hunch settles on Base via x402 — so it lists as read-only hosted / tradeable self-hosted, like Kalshi. - Tests:
core/test/normalizers/hunch-normalizer.test.ts— 47 tests covering binary + N-way mapping, the money-criticaloutcomeId↔sideround-trip, position/balance/OHLCV/emulated-orderbook.tsc --noEmitclean. - Known gap:
volume24his0(Hunch reports no 24h split; totalvolumeis the pool size). Fast-follow on our side can add it.
Fixed — Hyperliquid
core/src/exchanges/hyperliquid/index.ts—fetchOHLCVparamswas typed as required but server dispatch passes a single merged object asargs[0], leavingargs[1]undefined. Result: any MCP/HTTP call to HLfetchOHLCVcrashed withCannot read properties of undefined (reading 'start'). Default to{ resolution: '1h' }so the type-check passes (resolution is required onOHLCVParams) and the call survives even without explicit params. (#1161, #1162)sdks/python/pmxt/client.py+sdks/typescript/pmxt/client.ts— SDKs sent?id=...for GET reads, but servermethod-verbs.jsonspec forfetchOHLCV/fetchTradesdeclares the first arg asoutcomeId. GET dispatcher (queryToArgsincore/src/server/app.ts) peels primitives by spec name, then bundles remaining query keys into the object arg — sooutcomeIdarrivedundefinedand the rawidended up insideparams. DownstreamfromMarketId(undefined)crashed withCannot read properties of undefined (reading 'match'). POST was unaffected (positional args). Fixed both SDKs. (#1162)core/src/exchanges/hyperliquid/normalizer.ts—volume24hwas hardcoded to0for every HL market and event. Fixed by pullingdayNtlVlmfromspotMetaAndAssetCtxs(outcome legs appear ascoin: '#NNNN'), summing Yes + No notional per outcome, and threading the map throughfetchRawMarkets/fetchEventsImpl. One extra batched call, no N+1; fallback to 0 ifspotMetaAndAssetCtxsis unreachable. (#1219)core/src/exchanges/hyperliquid/utils.ts—fromMarketIdonly matchedhl-outcome-{N}. Passing an actual outcome token (e.g.100002000, as returned byUnifiedMarket.outcomes[].outcomeId) threwInvalid Hyperliquid market ID. Now accepts either form viadecodeAssetId. AffectsfetchOrderBook/fetchOHLCV/fetchTrades. (#1252)core/src/exchanges/hyperliquid/fetcher.ts—fetchRawMarketsignoredparams.marketId, andfetchRawEventsignoredparams.eventId.fetchMarket(marketId=X)returned the first market in the venue list instead of X;fetchEvent(eventId=X)always returned the first question. Both filters now applied viafromMarketIdand direct id match. (#1254)core/src/exchanges/hyperliquid/fetcher.ts—fetchRawOHLCVandfetchRawTradesignoredparams.limit. OHLCV returned the entire window regardless; trades returned a fixed page. Now slices client-side after the venue call returns (HL'scandleSnapshot/recentTradeshave no native limit param). (#1254)core/src/exchanges/hyperliquid/normalizer.ts—normalizeUserTradeleftmarketId/outcomeId/feeundefined on every fill. Consumers couldn't tell which market a fill was on. Now populated via the existingcoinToMarketId/coinToOutcomeIdhelpers and the rawfeefield. WidenedUnifiedUserTradewith optionalmarketId,fee. Verified 2000/2000 trades fully populated on an active wallet. (#1255)core/src/exchanges/hyperliquid/normalizer.ts—normalizeBalancereadcrossMarginSummary(perp margin) and hardcoded the currency label as'USDH'. Outcome markets quote against USDC on the spot account, so users with deposited funds sawUSDH: 0and assumed empty. Now readsspotClearinghouseStatealongside the perp account; spot balances surface with their real coin label (USDC,USDH, etc.); funded perp surfaces asUSDC_PERPso callers can tell it apart. Verified $14.90 USDC deposit now correctly returnsUSDC: 14.9. (#1280)core/src/exchanges/hyperliquid/index.ts—submitOrdersilently swallowed HL rejections. When HL returnedstatuses[0].error(e.g."Order must have minimum value of 10 USDC"), the SDK reportedstatus='filled', id='unknown'— fake success with no way for the caller to know the order wasn't placed. Now raises a typedPmxtErrorcarrying HL's message; also correctly populatesprice/oidon thefilledbranch (was checked but never read). (#1281)core/src/exchanges/hyperliquid/index.ts—cancelOrderhardcodeda: 0(asset id). HL rejected every cancel withUser or API Wallet 0xfaf6... does not existbecause the action hash with the wrong asset recovers a different signer address. Now looks up the open order to derive the real asset id viaencodeAssetId. Throws a typed "Order not found" when the supplied oid isn't open. (#1281)core/src/exchanges/hyperliquid/auth.ts—msgpackrencoded positive BigInts as int64 (0xd3), HL's server (Pythonmsgpack) encodes the same values as uint64 (0xcf). Identical bit pattern, different type byte → action hash mismatch → signature recovers to wrong address. Only manifested on cancel because order actions don't carry BigInt fields; cancel does (oid). AddedfixInt64ToUint64post-processor: HL actions never carry negative ints (oids, nonces, asset ids are all ≥ 0), so flippingd3 → cfwhen the following byte is< 0x80is safe and produces byte-identical encoding to Python msgpack. (#1281)
Fixed — Python SDK (cross-venue)
sdks/python/pmxt/errors.py—NetworkError/ExchangeNotAvailablerejected thecode/retryablekwargs thatfrom_server_erroralways sets. Any 503 from Kalshi or another venue raisedTypeError: ExchangeNotAvailable.__init__() got an unexpected keyword argument 'code'instead of a typedPmxtError. Both classes now accept**kwargsand default-fill their hardcoded values. Caught by the cross-venue Trump search test where Kalshi briefly errored. (#1219)
Added — Hyperliquid
fetchEventsPaginatedwrappers in both SDKs.BaseExchange.fetchEventsPaginatedexisted but neitherclient.pynorclient.tsexposed it. Addedfetch_events_paginated/fetchEventsPaginatedmirroring the existingfetchMarketsPaginatedpattern. (#1252)fetchOrderBookson Hyperliquid. HL has no native batch order-book endpoint. Implemented asPromise.alloverfetchOrderBook; inherits the outcome-token-or-marketId resolution from the single-fetch path.has.fetchOrderBooksauto-flips becauseBaseExchange._deriveCapabilitiesintrospects overrides.ponytail:comment marks the unbounded concurrency for future capping. (#1253)fetchClosedOrders+fetchAllOrderson Hyperliquid. HL exposes no closed-orders endpoint. Synthesize by groupinguserFillsbyoidand excluding currently-open oids — VWAP price, summed size, summed fee, earliest fill time.fetchAllOrders = openOrders ∪ closedOrders.ponytail:comment marks the limitation: cancelled-with-no-fills orders aren't reconstructable from the public info API. Verified against an active wallet: 7 open + 104 derived closed = 111 all, exact-count match. (#1255)
Changed — types
core/src/types.ts—UnifiedUserTradewidened with optionalmarketIdandfeefields. Existing consumers continue to work unchanged. (#1255)
Fixed — Python SDK (test-suite rot caught at version cut)
Running the full npm test before tagging surfaced six pre-existing failures untouched by the HL audit. Two were the SDK silently broken since the 2.50.11–14 hosted-routing series; the rest were stale test fixtures from earlier intentional behavior changes.
- **`sdks/python/pmxt/client....
v2.50.16
Fixed
sdks/python/pmxt/client.py+sdks/typescript/pmxt/client.ts—fetch_open_orders/fetchOpenOrdersmissing hosted-mode routing branch. Same class of bug as the 2.50.11-14 fixes forfetch_balance/fetch_positions/fetch_order/fetch_my_trades/cancel_order: hosted callers receivedTrading operations require authentication. Initialize LimitlessExchange with credentials: ...because the SDK fell through to the sidecar/api/{exchange}/fetchOpenOrderspath instead of routing to the hostedGET /v0/orders/open?address=...endpoint. Added anis_hostedbranch in both SDKs that resolves the wallet address, calls_hosted_request("fetch_open_orders", params={"address": ...}), and maps the response throughorder_from_v0/orderFromV0. The self-hosted/sidecar branch is preserved unchanged. Verified live against hosted Limitless:fetch_open_orders()returns[]cleanly and no longer raises.
Installation
npm:
npm install pmxtjs@2.50.16
npm install -g @pmxt/cli@2.50.16PyPI:
pip install pmxt==2.50.16Links
Full Changelog: v2.50.15f...v2.50.16f
v2.50.15
Fixed
sdks/python/pmxt/constants.py+sdks/typescript/pmxt/constants.ts— Opinion SELL was fully blocked on hosted clients withInvalidSignature: typed_data schema mismatch: no allowlisted verifyingContract configured for chain 56. Per the brain (docs/engineering/architecture/Cross-Chain Settlement (Buy + Sell).md,docs/knowledge/pitfalls/cross-chain-eip712-domain.md), Opinion SELL is a dual-signed parallel flow: the Polygon "pay" leg (CrossChainSellPayParams) signs against the PolygonPreFundedEscrowdomain, and the BSC "pull" leg (CrossChainSellPullParams) signs against the BSCVenueEscrowdomain —ecrecoverruns on BSC for that leg, so the typed-data domain MUST use chainId 56 with the BSC contract's address. The SDK's_VENUE_DOMAINschema foropinion_sell_bsc_pullcorrectly references chain 56 withverifyingContractallowlistVENUE_ESCROW_ADDRESSES, but both Python and TS constants files declaredVENUE_ESCROW_ADDRESSESas an empty set with a "TODO: add the BSC VenueEscrow address" comment. Any hosted Opinion SELL hit the empty allowlist and was rejected client-side before submit. Added the BSC VenueEscrow address0x6a273643d84edbb603b808d8a724fb963c7a298ato both constants files. Verified live: order id375, position0bf83067-…on Spain market closed from 15.171726 → 0, USDC balance went 48.056457 → 49.735623 (Δ +1.679166 USDC; ~15.17 shares × ~$0.111 effective fill price).
Installation
npm:
npm install pmxtjs@2.50.15
npm install -g @pmxt/cli@2.50.15PyPI:
pip install pmxt==2.50.15Links
Full Changelog: v2.50.14f...v2.50.15f
v2.50.14
Fixed
core/src/BaseExchange.ts+core/src/router/Router.ts— Router silently dropped the venue filter. Discovered while verifying the Bug #6 fix on hosted-pmxt:router.fetch_markets(query="...", exchange="polymarket")was returning Myriad rows (or anything else, depending on what the catalog ranked first by volume). Root cause:MarketFilterParams(andEventFetchParams) did not declaresourceExchange/exchangefields at all. So when a caller passedexchange="polymarket"as a kwarg, the value reachedRouter.fetchMarketsImplbut was dropped —searchMarkets/searchEventsacceptsourceExchangeas a query param, butfetchMarketsImplonly forwardedquery,category,limit,offset,closed. The filter was effectively a no-op.- Added
sourceExchange?: stringandexchange?: string(alias) to bothMarketFilterParams(whichMarketFetchParamsextends) andEventFetchParams. - Updated
Router.fetchMarketsImplandRouter.fetchEventsImplto forwardparams?.sourceExchange ?? params?.exchangeto the underlyingsearchMarkets/searchEventscalls. - hosted-pmxt's
/v0/marketsand/v0/eventsraw REST routes only accepted?sourceExchange=; updated on branchfix/v0-emit-catalog-uuid-as-outcomeidto also accept?exchange=as an alias. Same Cloud Build trigger that deployed the Bug #6 fix will redeploy with this change. - Per
RouterMarketSearchParams/RouterEventSearchParams(types.ts:152-168), the search client interface already declaredsourceExchange— the wiring was just missing in the Router layer. - The bug was masked until 2.50.13 + hosted-pmxt Bug #6 fix: before the catalog-UUID emission fix, every Polymarket-shaped wire response looked Polymarket-shaped, so a missing filter just returned irrelevant Polymarket-ish rows. After the fix, the wire surfaces the true
sourceExchange, and the filter being broken became visible (Polymarket-filter query returned Myriad rows tagged as such).
- Added
Installation
npm:
npm install pmxtjs@2.50.14
npm install -g @pmxt/cli@2.50.14PyPI:
pip install pmxt==2.50.14Links
Full Changelog: v2.50.13f...v2.50.14f
v2.50.13
A second live-verification round + brain-reading session caught 11 more issues across SDK and docs. The brain-reading reframed Bug #6 (catalog UUID emission) and corrected the docs' settlement story — Opinion is NOT uniformly dual-signature; BUY is single sig + oracle DvP, SELL is dual-signed parallel.
Fixed (Python SDK)
sdks/python/pmxt/client.py:1492—cancel_ordermissing hosted-mode routing branch. Same class of bug as 2.50.11/12 fixed forfetch_balance,fetch_positions,fetch_order,fetch_my_trades. Added the hosted branch; it dispatches via the existing_hosted_cancel_orderhelper (which has been there atclient.py:998all along). Cancel now goes through the hosted route in hosted mode instead of accidentally working via the legacy sidecar.sdks/python/pmxt/_exchanges.py:78-110—Limitlessconstructor rejectedwallet_address=kwarg. Polymarket accepts it; Limitless threwTypeError: unexpected keyword argument 'wallet_address'. Addedwallet_addressandsignerparams toLimitless.__init__and forwarded tosuper().__init__(), matching the Polymarket shape. The Python exchange-class generator template (core/scripts/generate-python-exchanges.js) needs the same fix or this regression returns on the next regen.sdks/python/pmxt/errors.py:66-73—MarketNotFound.__init__()raisedTypeError: unexpected keyword argument 'code'. Every Limitlessfetch_order_book(outcome_id=...)call hit this and crashed.from_server_errorat line 165 passescode=andretryable=to whatever class it instantiates, butMarketNotFound/OrderNotFound/EventNotFoundall had strict 2-arg__init__. Added**_ignoredto absorb the extras (the classes hardcode their own code internally). All three NotFound classes patched preemptively.sdks/python/pmxt/client.py:1395-1401—fetch_market("market_id_string")raisedAttributeError: 'str' object has no attribute 'items'. Signature now accepts a string positional arg and coerces to{"market_id": params}before camelCase conversion. Doc examples that pass a string id no longer crash. TS strict typing already prevents this.sdks/python/pmxt/_hosted_mappers.py:80-103— Limitless tradefeefield returned as raw micro-USDC. Polymarket trades reportfee=0.0012(USDC); Limitless reportedfee=6136(raw 6-decimal). Normalized inuser_trade_from_v0whenvenue == "limitless": divide by 1e6. Inverse multiply added touser_trade_to_v0at line 106-128. Limitless core normalizer doesn't setfeeat all onUserTrade— the raw 6136 was coming from the hosted v0 wire (trade.pmxt.dev), so the fix lives in the hosted mapper, not the sidecar normalizer.
Fixed (TypeScript SDK)
sdks/typescript/pmxt/client.ts:1163-1170,2344—cancelOrdermissing hosted-mode routing branch. Same fix as Python; addedif (this.isHosted) return this._hostedCancelOrder(orderId);and a new_hostedCancelOrdermethod mirroring the Python helper (build → sign → cancel usingcancelOrderBuild/cancelOrderroutes fromhosted-routing.ts). TSLimitlessalready acceptswalletAddressviaExchangeOptions— no change needed there.
Docs — settlement architecture rewrite (Opinion + Limitless)
The brain (docs/engineering/architecture/Cross-Chain Settlement (Buy + Sell).md, docs/engineering/decisions/oracle-attested-dvp-settlement.md) is the source of truth. The docs were uniformly wrong about Opinion ("dual-signature cross-chain" applied to both directions) and oversimplified for Limitless.
docs/concepts/hosted-trading.mdx— Settle step (line 51). Rewrote to per-venue model with correct trust gates:- Polymarket — same-chain Polygon, single EIP-712
OrderParamsonPreFundedEscrow, CTF exchange. - Opinion BUY — Polygon→BSC, single
CrossChainOrderParams+ oracle-attested DvP viasettleCrossChainBuy. Operator fronts BSC, settlement oracle signsDeliveryAttestation, contract checks bind/oracle sig/tokensDelivered != 0/worst-price before releasing the user's USDC. Trust assumption: honest oracle. - Opinion SELL — BSC→Polygon, dual-signed parallel (
CrossChainSellPayParamson Polygon +CrossChainSellPullParamson BSCVenueEscrow).settleCrossChainSellUSDC‖settleCrossChainSellTokens. 2×2 outcome matrix: row 3 (pay✗, pull✓) is the operator-trusted gap today, bond-backstopped later. - Limitless — Polygon→Base, ERC-7683 single sig, v1 signature-gated front-and-reimburse with explicit trust asterisk; v2 will use delivery proof.
- Polymarket — same-chain Polygon, single EIP-712
docs/concepts/hosted-trading.mdx— Custody section (lines 65-72). Rewrote from a Polygon-only frame to a three-chain custody story: PolygonPreFundedEscrow(USDC + CTF), BSCVenueEscrow(Opinion tokens), Base escrow (Limitless tokens). Notes that Opinion BUYs need no BSC user signature (user is the recipient on thedepositForUserleg) while Opinion SELLs require a BSC-domain pull signature.docs/guides/signing.mdx— added a "Single-signature vs dual-signature flows" subsection. Single-sig: Polymarket buy+sell, Opinion BUY, Limitless buy+sell — one payload atbuilt.raw["typed_data"]. Dual-sig: Opinion SELL ONLY —built.raw["typed_data"](PolygonCrossChainSellPayParamsonPreFundedEscrow) plusbuilt.raw["pull_typed_data"](BSCCrossChainSellPullParamsonVenueEscrow). Same EVM key signs both; domains distinct (chainId 137 vs 56). Custom-signer note at line 117 scoped to Opinion SELL with the two legs named explicitly.
Docs — operational fixes from the verification round
docs/rate-limits.mdx— replaced the single-row 60/min table with the actual three-tier table fromdocs/engineering/repos/hosted-pmxt.md(verified 2026-05-29 against pmxt.dev/pricing): Free (60/min, 25K credits), Starter (300/min, 250K credits, $29.99/mo), Pro (1000/min, 1M credits, $99.99/mo), Enterprise (custom). Added the credit accounting rule (1 REST = 1 credit; 1 WS message = 0.1 credits). This explains why our test key didn't trigger 429 at 120 req/42.7s — it's on a higher tier than Free.docs/authentication.mdx:141-146— the documentedInvalidApiKeySDK exception class doesn't actually exist; live test confirmed both 401 cases raise the basepmxt.errors.PmxtErrorwith message "missing api key" / "invalid api key". Updated the error table to reflect reality + added a<Note>flagging this as a temporary doc accommodation pending a future SDK release that may add the subclass.docs/guides/escrow-lifecycle.mdx+docs/trading-quickstart.mdx— escrow tx builders (approve_tx,deposit_tx,withdraw_tx) return{"tx": {...}}, not a flat tx dict. Every code block that accessedtx.to/tx.valuedirectly was rewritten to unwrap viaresult["tx"](Python) orconst { tx } = await client.escrow.X()(TS). Comments now show the envelope shape includingchainId,gas,maxFeePerGas,maxPriorityFeePerGas,nonce. ~10 examples fixed across the two files.docs/trading-quickstart.mdx— added a Note thatoutcome=requires aMarketOutcomeinstance (the object you get fromclient.fetch_markets()[0].outcomes[i]). Passing a bare dict raisesAttributeError. Pointed at the string-id alternative:client.create_order(market_id="...", outcome_id="...", side="buy", ...).docs/guides/hosted-errors.mdx— added a Warning clarifying the marketable-limit price gates. Marketable BUY:price = best_ask(small +1-tick buffer works). Marketable SELL:price = best_ask(at or above ask), NOTbest_bidorbest_bid - 0.01. The SDK's_validate_worst_priceenforcesworst_price ≥ best_bid × 0.8 + 0.029at_hosted_typeddata.py:519; the practical floor for a marketable SELL on Spain @ $0.138 is at the ask, not below the bid.
Skipped / in flight
- Bug #6 (catalog UUID emission for Myriad rows in
/v0/markets) — background agent stopped because the brain doc's/opt/data/repos/hosted-pmxtpath doesn't exist on the actual server (65.109.107.152). hosted-pmxt is deployed to GCP Cloud Run and there's no Hetzner checkout. A local clone at/Users/samueltinnerholm/Documents/GitHub/hosted-pmxtexists but the agent's sandbox can't operate there. Needs a different working location to proceed — tracked for a follow-up.
Installation
npm:
npm install pmxtjs@2.50.13
npm install -g @pmxt/cli@2.50.13PyPI:
pip install pmxt==2.50.13Links
Full Changelog: v2.50.12f...v2.50.13f
v2.50.12
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.
Fixed
-
sdks/python/pmxt/client.py+sdks/typescript/pmxt/client.ts—fetch_my_tradeswas the fourth method missing the hosted-mode routing branch. The 2.50.11 fix coveredfetch_balance,fetch_positions,fetch_orderbut I missedfetch_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 existingfetch_my_tradesroute key (GET /v0/user/{address}/trades) and the existinguser_trade_from_v0/userTradeFromV0mappers. Live verification with the test wallet0xcb856…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. -
sdks/python/pmxt/_hosted_typeddata.py— hosted limit BUY was un-submittable due to a validator/helper denom contradiction._hosted_denom()inclient.pycorrectly returneddenom="shares"for limit BUY (the user passes a share quantity at an explicit price; the server computesmax_cost_usdc = shares × price + slippage buffer). But_validate_polymarket_buy_economicshardcodeddenom="usdc"for all BUYs regardless of order type. The validator raisedInvalidSignature("economic mismatch: denom expected 'usdc' got 'shares'")locally before any HTTP call, soclient.create_order(side="buy", order_type="limit", ...)could never reach the server. The validator was wrong, not the helper. Patched the validator to branch onorder_type: market BUY keeps the exact-equalitymax_cost_usdc == amountcheck; limit BUY acceptsdenom="shares"and floor-checksmax_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 returnedOrder(id="368", status="queued")— accepted by the venue, noInvalidSignature, no 501. -
docs/trading-quickstart.mdx:165anddocs/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. 400Insufficient escrowed tokens); no 501 anywhere in the path. Dropped the 501 sentence from both pages. Trading-quickstart now states hosted limit orders are supported viaclient.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-errorsNoLiquidityrecovery snippet had the same 501 claim — rewrote it to point at the working limit-order path. -
docs/router/prices.mdx—router.fetch_related_marketsdoc example couldn't run. Doc showedrouter.fetch_related_markets(market_id="...")returning typed dataclasses withr.relation,r.venue,r.best_bidattribute access. Reality: the SDK signature isfetch_related_markets(self, params: dict, **kwargs)(inherited fromExchangewith no Router override atsdks/python/pmxt/router.py), so the kwarg form raisesTypeError. 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 typedRelatedMarketdataclass, mirroring howcompare_market_pricesreturns typedPriceComparisonrows. Tracked, not in this patch. -
sdks/python/pmxt/router.py+sdks/typescript/pmxt/router.ts—compare_market_priceswas returning emptyvenue,best_bid,best_ask. The wire payload from/api/router/compareMarketPricescarries the data, but under different keys than the SDK was reading: bid/ask are nested undermarket.bestBid/market.bestAsk(where_parse_marketalready maps them ontoUnifiedMarket.best_bid/best_ask), and venue ismarket.sourceExchange— there is no top-levelvenue/bestBid/bestAskfield. The SDK mapper read only the top-level fields and got nulls every time. Doc's printf examplef"{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 the2026 World Cup Winner - Norwaymarket returned three populated rows across polymarket / kalshi / limitless. Same fix applied tofetch_hedgeswhich used the parallel mapper. -
docs/api-reference/fetch-events.mdx:125+docs/api-reference/fetch-markets.mdx:123— two curl examples returned HTTP 400. Both passedsort=volumewhich the catalog now rejects:{"error":"unsupported_params","message":"Parameters not supported by the catalog: sort"}. Droppedsort=volumefrom 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:sortis still declared as a real param incore/src/BaseExchange.ts:81,111and the generatedcore/src/server/openapi.yamlstill publishes it. The catalog dropped support without updating the SDK or OpenAPI. Either re-implementsorton the catalog or remove it from the SDK type signature — tracked separately, not in this patch.
Investigated, no fix shipped
- "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 incore/src/exchanges/myriad/normalizer.ts:52andutils.ts:74; negative values like-8are the "Not" leg of a binary outcome. The Router query that surfaced these returnedsourceExchange=myriadrows, notpolymarketrows; my initial diagnosis was wrong. The REAL issue: the SDK's_looks_like_catalog_uuidcheck atsdks/python/pmxt/client.py:764doesn'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_uuidto recognize Myriad composites, (b) make the catalog assign UUIDs to Myriad outcomes (currently Myriad's normalizer uses the venue-native composite as theoutcome_id), or (c) ensure the backend resolver accepts the composite venue-native shape. Tracked for separate work.
Security note
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.
Installation
npm:
npm install pmxtjs@2.50.12
npm install -g @pmxt/cli@2.50.12PyPI:
pip install pmxt==2.50.12Links
Full Changelog: v2.50.11f...v2.50.12f