Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions core/scripts/generate-python-exchanges.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ const OVERRIDES = {
polymarket: {
// The Python SDK defaults to gnosis-safe to match historical behaviour.
defaults: { signature_type: '"gnosis-safe"' },
methods: [
[
' def init_auth(self) -> None:',
' """Initialize L2 API credentials for Polymarket implicit API signing."""',
' self._call_method("initAuth")',
' return None',
].join('\n'),
],
},
myriad: {
// Myriad uses privateKey as the wallet address, not a signing key.
Expand Down Expand Up @@ -108,6 +116,7 @@ function generateClass(exchange) {
const aliases = ov.paramAliases || {};
const defaults = ov.defaults || {};
const paramDocs = ov.paramDocs || {};
const methods = ov.methods || [];

const constructorParams = [];
const superArgs = [`exchange_name="${name}"`];
Expand Down Expand Up @@ -207,6 +216,10 @@ function generateClass(exchange) {
);
}

if (methods.length) {
lines.push('', ...methods);
}

return lines.join('\n');
}

Expand Down
3 changes: 3 additions & 0 deletions sdks/python/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1927,6 +1927,7 @@ similarity_threshold: float # For semantic search (used by Limitless)
class EventFetchParams:
query: str # For keyword search
limit: float # Maximum number of results to return
cursor: str # Opaque venue pagination cursor, where supported.
offset: float # Pagination offset — number of results to skip
sort: str # Sort order for results
status: str # Filter by event status (default: 'active', 'inactive' and 'closed' are interchangeable)
Expand Down Expand Up @@ -3367,6 +3368,8 @@ Update Order Group Limit *(Auth required)*

Get Balance *(Auth required)*

**Parameters:**
- `subaccount` (query, integer) — Subaccount number (0 for primary, 1-32 for subaccounts). When provided, returns only that subaccount's balance.

---
##### `CreateSubaccount`
Expand Down
5 changes: 5 additions & 0 deletions sdks/python/pmxt/_exchanges.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ def _get_credentials_dict(self) -> Optional[Dict[str, Any]]:
creds["passphrase"] = self.passphrase
return creds if creds else None

def init_auth(self) -> None:
"""Initialize L2 API credentials for Polymarket implicit API signing."""
self._call_method("initAuth")
return None


class Limitless(Exchange):
"""Limitless exchange client."""
Expand Down
28 changes: 14 additions & 14 deletions sdks/python/pmxt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ def fetch_markets(self, params: Optional[dict] = None, **kwargs) -> List[Unified
if kwargs:
params = {**(params or {}), **kwargs}
if params is not None:
args.append(_convert_params_to_camel(params))
args.append(params)
body: dict = {"args": args}
creds = self._get_credentials_dict()
if creds:
Expand All @@ -774,7 +774,7 @@ def fetch_markets_paginated(self, params: Optional[dict] = None, **kwargs) -> Pa
if kwargs:
params = {**(params or {}), **kwargs}
if params is not None:
args.append(_convert_params_to_camel(params))
args.append(params)
body: dict = {"args": args}
creds = self._get_credentials_dict()
if creds:
Expand All @@ -801,7 +801,7 @@ def fetch_events(self, params: Optional[dict] = None, **kwargs) -> List[UnifiedE
if kwargs:
params = {**(params or {}), **kwargs}
if params is not None:
args.append(_convert_params_to_camel(params))
args.append(params)
body: dict = {"args": args}
creds = self._get_credentials_dict()
if creds:
Expand All @@ -824,7 +824,7 @@ def fetch_market(self, params: Optional[dict] = None, **kwargs) -> UnifiedMarket
if kwargs:
params = {**(params or {}), **kwargs}
if params is not None:
args.append(_convert_params_to_camel(params))
args.append(params)
body: dict = {"args": args}
creds = self._get_credentials_dict()
if creds:
Expand All @@ -847,7 +847,7 @@ def fetch_event(self, params: Optional[dict] = None, **kwargs) -> UnifiedEvent:
if kwargs:
params = {**(params or {}), **kwargs}
if params is not None:
args.append(_convert_params_to_camel(params))
args.append(params)
body: dict = {"args": args}
creds = self._get_credentials_dict()
if creds:
Expand Down Expand Up @@ -1144,7 +1144,7 @@ def close(self) -> None:
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_market_matches(self, params: Optional[dict] = None, **kwargs) -> List[MatchResult]:
def fetch_market_matches(self, params: Optional[dict] = None, **kwargs) -> List[Any]:
try:
args = []
if kwargs:
Expand All @@ -1167,7 +1167,7 @@ def fetch_market_matches(self, params: Optional[dict] = None, **kwargs) -> List[
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_matches(self, params: dict, **kwargs) -> List[MatchResult]:
def fetch_matches(self, params: dict, **kwargs) -> List[Any]:
try:
args = []
if kwargs:
Expand All @@ -1189,7 +1189,7 @@ def fetch_matches(self, params: dict, **kwargs) -> List[MatchResult]:
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_event_matches(self, params: Optional[dict] = None, **kwargs) -> List[EventMatchResult]:
def fetch_event_matches(self, params: Optional[dict] = None, **kwargs) -> List[Any]:
try:
args = []
if kwargs:
Expand All @@ -1212,7 +1212,7 @@ def fetch_event_matches(self, params: Optional[dict] = None, **kwargs) -> List[E
except ApiException as e:
raise self._parse_api_exception(e) from None

def compare_market_prices(self, params: dict, **kwargs) -> List[PriceComparison]:
def compare_market_prices(self, params: dict, **kwargs) -> List[Any]:
try:
args = []
if kwargs:
Expand All @@ -1234,7 +1234,7 @@ def compare_market_prices(self, params: dict, **kwargs) -> List[PriceComparison]
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_related_markets(self, params: dict, **kwargs) -> List[MatchResult]:
def fetch_related_markets(self, params: dict, **kwargs) -> List[Any]:
try:
args = []
if kwargs:
Expand All @@ -1256,7 +1256,7 @@ def fetch_related_markets(self, params: dict, **kwargs) -> List[MatchResult]:
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_matched_markets(self, params: Optional[dict] = None, **kwargs) -> List[MatchResult]:
def fetch_matched_markets(self, params: Optional[dict] = None, **kwargs) -> List[Any]:
try:
args = []
if kwargs:
Expand All @@ -1279,7 +1279,7 @@ def fetch_matched_markets(self, params: Optional[dict] = None, **kwargs) -> List
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_matched_prices(self, params: Optional[dict] = None, **kwargs) -> List[PriceComparison]:
def fetch_matched_prices(self, params: Optional[dict] = None, **kwargs) -> List[Any]:
try:
args = []
if kwargs:
Expand All @@ -1302,7 +1302,7 @@ def fetch_matched_prices(self, params: Optional[dict] = None, **kwargs) -> List[
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_hedges(self, params: dict, **kwargs) -> List[PriceComparison]:
def fetch_hedges(self, params: dict, **kwargs) -> List[Any]:
try:
args = []
if kwargs:
Expand All @@ -1324,7 +1324,7 @@ def fetch_hedges(self, params: dict, **kwargs) -> List[PriceComparison]:
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_arbitrage(self, params: Optional[dict] = None, **kwargs) -> List[ArbitrageOpportunity]:
def fetch_arbitrage(self, params: Optional[dict] = None, **kwargs) -> List[Any]:
try:
args = []
if kwargs:
Expand Down
27 changes: 27 additions & 0 deletions sdks/python/tests/test_public_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,30 @@ def test_legacy_polymarket_us_alias_stays_public():
assert "Polymarket_us" in exchange_imports
assert "Polymarket_us" in public_exports
assert aliases["Polymarket_us"] == "PolymarketUS"


def test_polymarket_init_auth_is_generated():
exchanges_path = Path(__file__).resolve().parents[1] / "pmxt" / "_exchanges.py"
tree = ast.parse(exchanges_path.read_text(encoding="utf-8"))

polymarket_class = next(
node
for node in tree.body
if isinstance(node, ast.ClassDef) and node.name == "Polymarket"
)
init_auth = next(
node
for node in polymarket_class.body
if isinstance(node, ast.FunctionDef) and node.name == "init_auth"
)

call = next(
node
for node in init_auth.body
if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call)
)
assert isinstance(call, ast.Expr)
assert isinstance(call.value, ast.Call)
assert isinstance(call.value.func, ast.Attribute)
assert call.value.func.attr == "_call_method"
assert call.value.args[0].value == "initAuth"
3 changes: 3 additions & 0 deletions sdks/typescript/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1927,6 +1927,7 @@ similarityThreshold?: number; // For semantic search (used by Limitless)
interface EventFetchParams {
query?: string; // For keyword search
limit?: number; // Maximum number of results to return
cursor?: string; // Opaque venue pagination cursor, where supported.
offset?: number; // Pagination offset — number of results to skip
sort?: string; // Sort order for results
status?: string; // Filter by event status (default: 'active', 'inactive' and 'closed' are interchangeable)
Expand Down Expand Up @@ -3368,6 +3369,8 @@ Update Order Group Limit *(Auth required)*

Get Balance *(Auth required)*

**Parameters:**
- `subaccount` (query, integer) — Subaccount number (0 for primary, 1-32 for subaccounts). When provided, returns only that subaccount's balance.

---
##### `CreateSubaccount`
Expand Down
41 changes: 40 additions & 1 deletion sdks/typescript/pmxt/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,28 @@ export abstract class Exchange {
return response.json();
}

/**
* Dispatch a sidecar POST method with positional args and credentials.
*
* @internal - shared transport for hand-maintained methods that should
* never use the GET read path.
*/
protected async sidecarPostRequest(methodName: string, args: unknown[]): Promise<any> {
const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/${methodName}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
body: JSON.stringify({ args, credentials: this.getCredentials() }),
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));
if (body.error && typeof body.error === "object") {
throw fromServerError(body.error);
}
throw new PmxtError(body.error?.message || response.statusText);
}
return response.json();
}

// BEGIN GENERATED METHODS

async loadMarkets(reload: boolean = false): Promise<Record<string, UnifiedMarket>> {
Expand Down Expand Up @@ -665,7 +687,7 @@ export abstract class Exchange {
}
}

async fetchMarketsPaginated(params?: MarketFetchParams): Promise<PaginatedMarketsResult> {
async fetchMarketsPaginated(params?: any): Promise<PaginatedMarketsResult> {
await this.initPromise;
try {
const args: any[] = [];
Expand Down Expand Up @@ -2446,6 +2468,23 @@ export class Polymarket extends Exchange {
};
super("polymarket", polyOptions as ExchangeOptions);
}

/**
* Initialize Polymarket L2 API credentials for implicit API signing.
*
* Call this before private Polymarket implicit-API endpoints when the
* underlying CLOB credentials have not been created yet.
*/
async initAuth(): Promise<void> {
await this.initPromise;
try {
const json = await this.sidecarPostRequest('initAuth', []);
this.handleResponse(json);
} catch (error) {
if (error instanceof PmxtError) throw error;
throw new PmxtError(`Failed to initAuth: ${error}`);
}
}
}

/**
Expand Down
Loading