Releases: pmxt-dev/pmxt
v2.49.1
Positioning-shift patch on top of 2.49.0 — the hosted trading mode shipped in 2.49.0 but the docs, READMEs, and OpenAPI schemas still defaulted to the self-hosted sidecar path. This release flips the default everywhere the SDK + docs surface a customer hits: hosted PMXT is the primary experience; self-hosting becomes the advanced escape hatch. No SDK runtime behavior changes — pure documentation, schema, and copy work. Marketing-site changes ship separately in a sibling pmxt-website PR.
Added
- Docs: 11 new MDX pages on the Mintlify site covering the hosted trading mode end-to-end —
trading-quickstart(60-second walkthrough),concepts/hosted-trading(feature landing),concepts/hosted-vs-self-hosted(one-pager comparison),concepts/catalog-uuid-vs-venue-id(the UUID/venue-id gotcha),guides/escrow-lifecycle(PreFundedEscrow walkthrough),guides/signing(EthAccountSigner / EthersSigner + EIP-712),guides/hosted-errors(the 5 most-common subclasses withtry/exceptcookbook),guides/migrate-to-hosted-trading(portedMIGRATION.mdcontent with language tabs),guides/self-hosted(consolidated local-sidecar story),api-reference/errors(fullHostedTradingErrortree with dual-parent semantic-map),api-reference/configuration(ExchangeOptions+ env vars + base-URL resolution). - Docs: New "Hosted Trading" and "Self-host" sidebar groups in
docs.json, plus a "Reference" group at the top of the API Reference tab.sdk/servermoved out of the previous "SDK" group into "Self-host" (without slug rename — link-stability preserved for this release). - Core: New
ExchangeOptionscomponent schema incore/src/server/openapi.yamldocumenting constructor-level options (pmxtApiKey,walletAddress,signer,privateKey,baseUrl, etc.) — previously onlyExchangeCredentials(per-request body credentials) existed at the schema level. - Core:
BuiltOrder.expiryfield added to the OpenAPI schema (the TTL that triggersBuiltOrderExpiredat submit time was implicit in the SDK and undocumented at the spec level).
Changed
- Docs:
introduction.mdx"It runs two ways" bullet order inverted — hosted listed first as the default, self-hosted second as the advanced path. First code block swapped from a dual-variant local/hosted snippet to a single hosted-defaultpmxt.Polymarket(pmxt_api_key=...)constructor. - Docs:
authentication.mdxvenue-credentials section reframed — "Hosted writes (recommended)" subsection added on top showing thepmxt_api_key + wallet_address + private_keyconstructor with a one-lineclient.escrow.deposit()example. The raw-private-key prose was preserved but relabeled as "Self-hosted / direct venue credentials (advanced)". Status/body/meaning error table picked up a fourth "SDK exception" column cross-linking to the new/api-reference/errorspage. - Docs:
security.mdx"Run pmxt locally" callout downgraded from<Warning>to<Note>and reworded — self-hosting is positioned as one option among several rather than the implicit "safer choice". PreFundedEscrow custody surfaced as the hosted alternative. - Docs:
sdk/server.mdxgot a top<Note>banner clarifying that the page applies to self-hosted mode only and hosted-mode users can skip it. (File location and slug intentionally not renamed in this release to preserve external links.) - Docs:
concepts/venues.mdxgained a third table at the bottom — "Hosted-trading venues" — listing Polymarket and Opinion with custody type, cross-chain support, and minimum-order-size columns. - READMEs (root, Python, TypeScript): All three flipped to hosted-default. Subtitles, "Why pmxt?" bullets, Quick Start, and Trading sections now lead with
pmxt.Polymarket(pmxt_api_key=...). Per-venue raw-credentials blocks preserved verbatim but moved into "Self-hosted trading (advanced)" subsections. Root README's "No API key required" bullet (actively anti-hosted-positioning) replaced with a "Hosted API" lead bullet. Net +176 lines across the three files.
Fixed
- Core:
Orderschema incore/src/server/openapi.yaml(and the generateddocs/api-reference/openapi.json) now includes the nullabletxHash,chain, andblockNumberfields the SDK has been returning in hosted mode since 2.49.0. Previous spec was silent on these and downstream codegen consumers missed them. - Core:
UserTradeschema gained the sametxHash/chain/blockNumbernullable trio. - Core:
Positionschema —requiredlist trimmed from[marketId, outcomeId, outcomeLabel, size, entryPrice, currentPrice, unrealizedPnL]to[marketId, outcomeId, size]. The other four became optional in 2.49.0 when the SDK stopped fabricating mark-to-market defaults for positions without a known current price; the schema kept claiming they were required, so generated clients with strict-null checking were rejecting valid responses. NewcurrentValuefield added (size * currentPricewhen available).txHash/chain/blockNumberenrichment added. - Core:
Balanceschema gained the optionalvenuefield that hosted-mode responses already carry on multi-venue queries. - Core:
ErrorDetailschema expanded from{ message: string }to the full envelope shipping in production responses —code(with a populated enum covering allHostedTradingErrorcodes plus the pre-existing tree),retryable: boolean, optionalexchange, optional free-formdetailobject. Downstream codegen can now branch oncode.
Docs
docs.json: First-timeredirectsarray added (empty for this release; reserves the structure for future slug renames).
Installation
npm:
npm install pmxtjs@2.49.1
npm install -g @pmxt/cli@2.49.1PyPI:
pip install pmxt==2.49.1Links
What's Changed
- feat(core): openapi schema for hosted-mode enrichment fields (v2.49.1) by @realfishsam in #978
- docs: hosted-default positioning + 11 new pages + READMEs + changelog (v2.49.1) by @realfishsam in #977
Full Changelog: v2.49.0f...v2.49.1f
v2.49.0
Added
- SDK (Python + TypeScript): Hosted trading mode now works end-to-end against
trade.pmxt.dev. Constructing the client with apmxt_api_key/pmxtApiKeyswitches every Group A public method —create_order/createOrder,build_order/buildOrder,submit_order/submitOrder,cancel_order/cancelOrder,fetch_balance/fetchBalance,fetch_positions/fetchPositions,fetch_open_orders/fetchOpenOrders,fetch_my_trades/fetchMyTrades,fetch_order/fetchOrder— to dispatch through PMXT's PreFundedEscrow custody ontrade.pmxt.dev/v0/*instead of the local sidecar. Read methods that require a wallet raiseMissingWalletAddresslocally before any network call when neither an explicitaddressargument norwallet_addresson the client is set.fetch_closed_ordersandfetch_all_ordersraiseNotSupportedin hosted mode (settled orders are modeled as trades; callers should usefetch_my_trades). Both SDKs auto-wrap a rawprivate_key/privateKeyinto the venue signer (EthAccountSignerfor Python viaeth-account,EthersSignerfor TypeScript via the optionalethers >= 6peer dep) so the user never has to construct a signer manually. - SDK (Python + TypeScript): New
Escrownamespace on hosted-mode Polymarket clients (client.escrow.build_approve_tx(...),build_deposit_tx,build_withdraw_tx,withdrawals(...)) for the PreFundedEscrow deposit/withdraw flow. Mirrors the/v0/escrow/*surface; only instantiated on hosted-trading-allowlisted venues. - SDK (Python + TypeScript): New hosted-mode error hierarchy (
HostedTradingError,InsufficientEscrowBalance,OrderSizeTooSmall,InvalidApiKey,OutcomeNotFound,CatalogUnavailable,BuiltOrderExpired,InvalidSignature,NoLiquidity,MissingWalletAddress). Each hosted error keeps a semantic parent so existing catch-sites still work — e.g.InsufficientEscrowBalanceextendsInsufficientFunds,OutcomeNotFoundextendsNotFoundError,CatalogUnavailableextendsExchangeNotAvailable. Python uses true multi-inheritance; TypeScript uses astatic isHostedError = trueflag plus anisHostedError(e)helper to compensate for single-inheritance. The mapper (raise_from_response/raiseFromResponse) translatestrade.pmxt.devstatus codes and detail strings to the right subclass. - SDK (Python): New
tests/e2e/hosted_driver.py— runnable live driver that proves URL routing against prod. Hitstrade.pmxt.dev/v0/*with a deliberately-bogus key so the server returns 401, captures the URL for every public method via anhttpx-level transport hook, and asserts each URL starts withhttps://trade.pmxt.dev/v0/. Also verifies local-only failure paths (MissingWalletAddress,NotSupported,InvalidOrder,InvalidSignature) raise before any network call. - SDK (TypeScript): New
tests/e2e/hosted-driver.ts— equivalent live driver (tsx-runnable) covering the same routing and local-raise assertions, usingglobal.fetchinstrumentation. - SDK (Python + TypeScript): 87 new in-process integration tests (
test_hosted_dispatch.py+test_hosted_error_mapping.pyin Python,hosted-dispatch.test.ts+hosted-error-mapping.test.tsin TypeScript). These mock the lowest reasonable HTTP layer (httpx.MockTransport/jest.spyOn(global, 'fetch')), construct a hosted client, call the public method, and assert exact URL / verb / body shape / response mapping for every Group A method plus the upstream status → SDK exception mapping. - SDK (Python + TypeScript): Feed listing surface on SDK clients — callers can now enumerate available data feeds from the unified client instead of reaching into the internal feed-client submodule. (#869)
Changed
- SDK (Python):
pmxt2.17.1 → 2.18.0. New constructor kwargswallet_address: str | Noneandsigner: Signer | Noneon every Exchange subclass; both are pass-through to the base class. Existing non-hosted (sidecar) callers see no behavior change. - SDK (TypeScript):
pmxtjs2.17.1 → 2.18.0. NewwalletAddress/signer/privateKeyfields onExchangeOptions.ethers >= 6.0.0 < 7.0.0declared as an optionalpeerDependency(only required for hosted writes; hosted reads work without it). - SDK (Python + TypeScript):
Order,UserTrade,Position, andBalancenow carry optionaltx_hash/txHash,chain, andblock_number/blockNumberfields, populated in hosted mode after the trade settles on-chain. Non-hosted callers seeNone/undefinedfor these — unchanged behavior. - SDK (Python + TypeScript):
Positionmark-to-market fields (outcome_label,entry_price,current_price,current_value) are now allOptional. Hosted endpoints populateoutcome_labelandentry_pricefrom operator-side cost-basis enrichment when available, but downstream consumers must handle the missing case rather than relying on fabricated defaults. - SDK (Python + TypeScript): Drift parity sweep across the two SDKs — model shapes, method signatures, capability flags, and generated outputs reconciled so the same call against the same venue returns identically-shaped objects regardless of which SDK you use. (#867)
- SDK (Python + TypeScript): Missing event/order parameters propagated through both SDK models so the full set of fields the core surface produces is actually reachable on the SDK objects. (#872)
- SDK (Python): Type annotations tightened across the Python SDK — narrower union types and
Optionalmarkers replacing implicitAnyin several public signatures. (#868) - Core: Cached exchange specs (the test fixtures used to detect upstream API drift) reconciled with current live payloads from each venue. (#866)
- Core: Magic chain IDs (
137,56, etc.) replaced with named constants throughout the codebase. (#878) - Deps: npm dependency refresh to clear outstanding security advisories. (#864)
- Deps: Python security dependency floors raised to clear outstanding security advisories. (#865)
Fixed
- SDK (Python):
Orderdataclass field ordering —filled_shares: Optional[float] = Nonewas declared before the non-default fieldsremaining: floatandtimestamp: int, which Python 3.13 rejects withTypeError: non-default argument 'remaining' follows default argument 'filled_shares'on first instantiation. Movedfilled_sharesbelow the required fields. - SDK (Python):
_error_detail_from_success_payloadno longer treats a successful 2xx response with a list or scalar JSON payload as an error envelope. Endpoints like/v0/user/{addr}/balancesreturn JSON arrays ([{"currency": "USDC", "amount": 12.5}]); the previous logic stringified the array and re-raised it asHostedTradingError, so every successful read crashed. Only 2xx Mappings with expliciterror/errors/success: falsemarkers now count as an error envelope. - SDK (Python): Duplicate
NotSupportedclass in_hosted_errors.pywas shadowing the canonical one inerrors.py. Tests that didfrom pmxt._hosted_errors import NotSupportedfailed to catch raises fromclient.pythat usedfrom .errors import NotSupported, because the two classes were unrelated._hosted_errors.pynow re-exports the canonicalNotSupportedfromerrors.py. - SDK (TypeScript):
_hostedBuildOrderBodywas writing the user's wallet tobody["wallet_address"], buttrade.pmxt.dev'sBuildOrderV0Reqexpects the field asuser_address. EverycreateOrder/buildOrdervia the TS SDK previously 422-ed on a "missing user_address" Pydantic validation error before reaching the chain. Python SDK was already correct. - SDK (TypeScript): Nine
HOSTED_METHOD_ROUTES.get("…")lookups inclient.tsused snake_case keys ("submit_order","fetch_balance", etc.) against a Map whose keys are camelCase ("submitOrder","fetchBalance", etc.). Every hosted call would have thrownTypeError: Cannot read properties of undefined (reading 'method')at runtime.tscand Jest didn't catch this because the existing unit tests stub out the lookup. Fixed by switching all nine sites to the camelCase keys actually defined in the map. - SDK (TypeScript): Removed the
errors.ts → hosted-errors.tsre-export block that created a circular import.tscandts-jesttolerated the cycle, buttsx/ Node CJS crashed at module load withReferenceError: Cannot access 'PmxtError' before initializationbecauseerrors.ts's body re-exports fromhosted-errors.ts, which extendsPmxtErrordefined later in the sameerrors.tsbody. Hosted error classes are now re-exported once fromindex.tsinstead of viaerrors.ts. The public surface is unchanged for consumers importing frompmxtjs. - Server (Python sidecar): Bare and overly-broad
except:handlers in the sidecar manager tightened to specific exception types, so genuine bugs surface instead of getting swallowed and reported as opaque "server failed to start" errors. Closes #813-#821. (#871) - Server: WebSocket and feed-client hygiene issues surfaced rather than swallowed — disconnects, malformed frames, and feed-side errors now propagate to the caller instead of silently dropping events. (#870)
- Core: Exchange normalizers across all venues realigned with current live payload shapes; addresses cumulative drift that had been quietly producing inconsistent unified objects between SDKs and the server. (#873)
- Kalshi: Pagination capped to prevent unbounded scrolling on
fetchMarkets/fetchEvents, andstatus=allis now serialized as a single value rather than the array form that some Kalshi endpoints reject. (#874) - Limitless: Explicit fetch timeouts on every outbound HTTP call (and the local test server) so a slow upstream can no longer hang the entire SDK request indefinitely. (#875, #876)
- Limitless: Throttler now rejects new work when its queue overflows instead of growing the queue unbounded — prevents memor...
v2.48.6
Fixed
- Opinion:
resolutionDateon Opinion markets no longer collapses to1970-01-01T00:00:00Zwhen the upstreamcutoffAtis missing or0. Root cause:toMillis(0)returned0, which the normalizer then wrapped innew Date(0)and emitted as a valid-looking past date. Categorical child markets (e.g.2026 FIFA World Cup Winner - Spain) are the common case — Opinion publishescutoffAtonly on the parent, not on each child — so every child silently inherited epoch and was filtered out by any downstreamcloses_at > now()guard. Concretely, hosted-pmxt'sfetchMarketMatcheswas dropping all Opinion ↔ Polymarket pairs (407 in the catalog, 13 FIFA-specific) because the Opinion side looked already-closed. - Opinion:
normalizeChildMarketnow inheritsparent.cutoffAtviachild.cutoffAt || parent.cutoffAt, so the fallback introduced in commit6ac8cd1actually fires for thecutoffAt = 0case (the upstream literal, not a missing field). - Core:
toMillis(ts)inopinion/utils.tsnow returnsnullfor falsy input instead of0, so callers can distinguish "no timestamp" from "epoch" and stop materializing bogus 1970 dates. Trade/order normalizers preserve old behavior with?? 0. - Core:
UnifiedMarket.resolutionDateis now?: Date. Not every venue publishes a resolution date on every market, and the optional type lets normalizers emitundefinedinstead of fabricating an epoch sentinel.BaseExchange.filterByCriteriaand the Baozi normalizer handle the optional case (markets without a known resolution date pass anactive-status filter, fail aclosed-status filter, and sort last undersort=newest).
Installation
npm:
npm install pmxtjs@2.48.6
npm install -g @pmxt/cli@2.48.6PyPI:
pip install pmxt==2.48.6Links
Full Changelog: v2.48.5f...v2.48.6f
v2.48.5
Fixed
- Opinion:
outcome.metadataon every market returned byfetchEvents/fetchMarkets/fetchMarketnow carriesopinionMarketId(Opinion's source-native integer market id), mirroring theclobTokenIdshape Polymarket already exposes. Downstream consumers (notablypmxt-trading's/trade/build-order, which keys Opinion orders by integer marketId) can now recover the id from a unified outcome without bypassing pmxt-api. (#838) - Opinion:
fetchMarkets({ marketId })now rejects non-integer values (e.g. accidentally passing a pmxt UUID) with aBAD_REQUESTinstead of silently returning an unrelated market. (#838)
Installation
npm:
npm install pmxtjs@2.48.5
npm install -g @pmxt/cli@2.48.5PyPI:
pip install pmxt==2.48.5Links
Full Changelog: v2.48.4f...v2.48.5f
v2.48.4
Fixed
- Python SDK:
FeedClientis now exported from the top-levelpmxtpackage, sofrom pmxt import FeedClient(andpmxt.FeedClient(...)) work without reaching into the internalpmxt.feed_clientsubmodule. (#835) - TypeScript SDK:
FeedClientis now exported from the top-levelpmxtjspackage alongside its related types (Ticker,Tickers,OHLCV,OracleRound,FeedClientOptions), and is exposed on the defaultpmxtobject. Consumers can nowimport { FeedClient } from 'pmxtjs'or callpmxt.FeedClient(...)directly. (#835)
Installation
npm:
npm install pmxtjs@2.48.4
npm install -g @pmxt/cli@2.48.4PyPI:
pip install pmxt==2.48.4Links
What's Changed
- fix: export FeedClient from Python SDK by @nanookclaw in #835
Full Changelog: v2.48.3f...v2.48.4f
v2.48.3
Fixed
- SDK (TS + Python):
sourceMetadatais now declared onUnifiedMarketandUnifiedEventmodel classes in both SDKs (it was previously declared only onUnifiedSeries). Closes a schema-drift gap so the venue-specific raw metadata that core already attaches viabuildSourceMetadataactually surfaces on the SDK objects rather than being dropped at the model boundary. - Core (
addBinaryOutcomes): PromotingYes/Nolabels to the market title now mutates the existing outcome object instead of replacing it with a spread copy. This restores reference identity betweenmarket.yes/market.noandmarket.outcomes[0]/market.outcomes[1]— an invariant the unified-market contract assumes. Consumers diffing by object equality no longer see split snapshots after title promotion. - Kalshi: The event-title contamination heuristic now also counts sub-market ticker tails (e.g.
PSGfromKXUCL-26-PSG) as candidate labels. Previously, titles like"Champions League Winner: PSG vs Arsenal"only matched a single full label (Arsenal) and fell short of the>= 2-match threshold, so the contaminated title was kept instead of falling back to the series title. The threshold itself is unchanged.
Installation
npm:
npm install pmxtjs@2.48.3
npm install -g @pmxt/cli@2.48.3PyPI:
pip install pmxt==2.48.3Links
What's Changed
- docs: sync hosted-pmxt custom endpoints by @realfishsam in #641
Full Changelog: v2.48.2f...v2.48.3f
v2.48.2
Fixed
- Limitless:
fetchBalanceno longer loses precision when raw on-chain balances exceedNumber.MAX_SAFE_INTEGER(≈ 9 × 10¹⁵ USDC raw units). ReplacedparseFloat(rawBalance.toString()) / Math.pow(10, decimals)with ascaledIntegerToNumberhelper that performs integer division/modulo in bigint before converting to a JS number. AffectsLimitlessExchange.fetchBalanceandLimitlessClient.getBalance. (#683) - Python SDK: Server auto-start failure message now points users at the correct package (
pmxt-core) instead of the stalepmxtjs(the TypeScript SDK package, which does not provide the sidecar). Added a regression test that scansclient.pysource for any futurepmxtjsreintroduction. Also cleaned up the same stale reference inQUICKREF.py. (#764) - Python SDK:
pmxt.SuiBetsis now exported with the matching cross-SDK casing (wasSuibets). The fix lives incore/scripts/generate-python-exchanges.jsvia a newclassNameoverride pattern so it survives regeneration;pmxt.Suibetsremains as a backwards-compatible alias. (#774)
Installation
npm:
npm install pmxtjs@2.48.2
npm install -g @pmxt/cli@2.48.2PyPI:
pip install pmxt==2.48.2Links
What's Changed
- fix: update Python sidecar package guidance by @nanookclaw in #764
- fix: avoid float precision loss in Limitless balances by @00anon0X in #683
- fix(python-sdk): rename Suibets to SuiBets for cross-SDK consistency by @PrettyFox0 in #774
New Contributors
- @nanookclaw made their first contribution in #764
- @00anon0X made their first contribution in #683
- @PrettyFox0 made their first contribution in #774
Full Changelog: v2.48.1f...v2.48.2f
v2.48.1
Fixed
- Server:
POST /api/<exchange>/<method>now tolerates flat-body requests like{"slug":"wta"}in addition to the existing{"args":[{"slug":"wta"}]}envelope. Previously, flat bodies caused all filter parameters to be silently dropped (the method was invoked with no arguments). The Python and TypeScript SDKs were not affected — they always wrap params inargs— but rawcurlcallers and documentation examples hit this. Empty bodies still behave asargs:[].
Installation
npm:
npm install pmxtjs@2.48.1
npm install -g @pmxt/cli@2.48.1PyPI:
pip install pmxt==2.48.1Links
What's Changed
- fix: tolerate flat POST bodies (treat as positional args[0]) by @realfishsam in #767
Full Changelog: v2.48.0f...v2.48.1f
v2.48.0
Added
- Core: New
UnifiedSeriestype representing recurring event groupings — the fourth tier above Event -> Market -> Outcome. Examples: KalshiKXATPMATCH(every ATP match), Polymarketwta(every WTA match). - Core:
fetchSeries(params?)method onBaseExchangewith vendor implementations for Kalshi (GetSeriesList), Polymarket and Polymarket US (Gamma/series+/series/{id}), Opinion (emulated from rawcollectionfield), and Gemini-Titan (emulated from rawseriesfield). Venues without a series concept return[]and reporthas.fetchSeries: false. - Core: New
series?: stringparameter onfetchEventsfor filtering by venue-native series id / ticker / slug. Passes through to vendor APIs where supported (Kalshi?series_ticker=, Polymarket Gamma?series_id=); venues without one return[]rather than silently ignore the filter. - Core:
Router.fetchSeries()andRouter.fetchEvents({series})for cross-venue queries by normalized PMXT series id (e.g.tennis-atp-match,nfl,crypto-btc-15m). Backed by a curated venue-id map atcore/src/router/series-map.tscovering tennis, American sports, soccer, esports, and crypto. - Core:
ExchangeHas.fetchSeriescapability flag.
Installation
npm:
npm install pmxtjs@2.48.0
npm install -g @pmxt/cli@2.48.0PyPI:
pip install pmxt==2.48.0Links
What's Changed
- feat: UnifiedSeries — fetchSeries across venues, Router cross-venue series, both SDKs by @realfishsam in #766
Full Changelog: v2.47.0f...v2.48.0f
v2.47.0
Added
- Core: Optional
sourceMetadatafield onUnifiedEventandUnifiedMarket(Record<string, unknown>) — captures venue-specific raw fields that are not promoted to first-class unified columns. Populated by every exchange normalizer (Kalshi, Polymarket, Polymarket US, Limitless, Smarkets, Opinion, Myriad, Probable, Metaculus, Baozi, Gemini-Titan, Hyperliquid, SuiBets) via a sharedbuildSourceMetadatahelper atcore/src/utils/metadata.ts. Includes recurring-series identifiers where the venue exposes them (Kalshiseries_ticker/series_title, Polymarketseries/seriesSlugwhen present, Opinioncollection, Gemini-Titanseries).
Installation
npm:
npm install pmxtjs@2.47.0
npm install -g @pmxt/cli@2.47.0PyPI:
pip install pmxt==2.47.0Links
What's Changed
- feat: add sourceMetadata to UnifiedEvent and UnifiedMarket by @realfishsam in #765
Full Changelog: v2.46.14f...v2.47.0f