pax-api 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pax_api-2.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PredictAsiaX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pax_api-2.0.0/PKG-INFO ADDED
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: pax-api
3
+ Version: 2.0.0
4
+ Summary: Official Python SDK for the PredictAsiaX Trader Track API — Web3-native prediction markets
5
+ Author-email: PredictAsiaX <support@predictasiax.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://predictasiax.com/developer
8
+ Project-URL: Documentation, https://docs.predictasiax.com
9
+ Project-URL: Repository, https://github.com/predictasiax/pax-python-sdk
10
+ Project-URL: Issues, https://github.com/predictasiax/pax-python-sdk/issues
11
+ Project-URL: Changelog, https://docs.predictasiax.com/changelog
12
+ Keywords: predictasiax,pax,prediction-market,trading-api,web3,hmac,polymarket-compatible
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Financial and Insurance Industry
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: Office/Business :: Financial :: Investment
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: requests>=2.28
30
+ Requires-Dist: websocket-client>=1.5
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7; extra == "dev"
33
+ Requires-Dist: pytest-cov>=4; extra == "dev"
34
+ Requires-Dist: responses>=0.23; extra == "dev"
35
+ Requires-Dist: ruff>=0.1; extra == "dev"
36
+ Requires-Dist: mypy>=1; extra == "dev"
37
+ Requires-Dist: build>=1; extra == "dev"
38
+ Requires-Dist: twine>=4; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # pax-api — Official Python SDK for PredictAsiaX
42
+
43
+ Web3-native prediction market REST + WebSocket API client. Polymarket-compatible HMAC signing pattern.
44
+
45
+ **Version 1.0.0** · MIT license · Python 3.8+
46
+
47
+ - **Docs**: https://docs.predictasiax.com
48
+ - **Developer landing**: https://predictasiax.com/developer
49
+ - **Get API key**: https://predictasiax.com/settings/api-keys
50
+ - **Support**: support@predictasiax.com
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ # Direct install from PredictAsiaX-hosted wheel
56
+ pip install https://docs.predictasiax.com/downloads/sdk/python/pax_api-1.0.0-py3-none-any.whl
57
+
58
+ # Or download tarball + install locally
59
+ curl -O https://docs.predictasiax.com/downloads/sdk/python/pax_api-1.0.0.tar.gz
60
+ pip install pax_api-1.0.0.tar.gz
61
+ ```
62
+
63
+ (PyPI publish coming soon — install methods above work today.)
64
+
65
+ ## Quickstart (sandbox — no real money)
66
+
67
+ ```python
68
+ from pax_api import PaxClient
69
+
70
+ with PaxClient(api_key="sk_test_YOUR_KEY", env="sandbox") as pax:
71
+ faucet = pax.faucet() # get 10k test USDT
72
+ print(faucet["data"]["balance_free"]) # → "10000.000000"
73
+
74
+ templates = pax.list_templates()
75
+ markets = pax.list_markets(category="crypto", limit=10)
76
+
77
+ market = pax.create_market(
78
+ template_id="crypto_price_binary_60s",
79
+ params={"asset": "BTC"},
80
+ )
81
+ print(market["data"]["market"]["market_id"]) # → "m_..."
82
+ ```
83
+
84
+ ## HMAC-signed requests (production)
85
+
86
+ Machine-to-machine trading bots should use HMAC signing (Polymarket-compatible 5-header pattern).
87
+
88
+ ```python
89
+ from pax_api import PaxClient
90
+
91
+ pax = PaxClient(
92
+ api_key="sk_live_YOUR_KEY",
93
+ secret="<64-hex secret>",
94
+ passphrase="<passphrase>",
95
+ env="production",
96
+ )
97
+
98
+ pax.place_order(
99
+ market_id="m_...",
100
+ outcome_id="yes",
101
+ side="buy",
102
+ order_type="limit",
103
+ size="100",
104
+ price="0.55",
105
+ client_order_id="unique-per-intent-id", # retry-safe within 24h
106
+ )
107
+ ```
108
+
109
+ ## WebSocket streams
110
+
111
+ ```python
112
+ from pax_api import PaxWSClient
113
+
114
+ ws = PaxWSClient(
115
+ api_key="sk_test_...",
116
+ env="sandbox",
117
+ subscribe_on_connect=["fast_tick", "trade_executed", "account"],
118
+ )
119
+ ws.on("fast_tick", lambda e: print("tick:", e))
120
+ ws.on("trade_executed", lambda e: print("trade:", e))
121
+ ws.on("account", lambda e: print("balance:", e.get("balance_free")))
122
+ ws.run_forever() # blocks; Ctrl+C to exit
123
+ ```
124
+
125
+ Auto-reconnect + exponential backoff built-in. All 4 client methods supported:
126
+ `subscribe`, `unsubscribe`, `auth`, `set_locale`.
127
+
128
+ ## Error handling
129
+
130
+ Every response error becomes a typed exception. Catch the specific type
131
+ you want to handle:
132
+
133
+ ```python
134
+ from pax_api import (
135
+ PaxClient,
136
+ PaxRateLimitError,
137
+ PaxReadOnlyModeError,
138
+ PaxValidationError,
139
+ PaxWrongEnvKeyError,
140
+ PaxError, # base class — catch-all
141
+ )
142
+
143
+ try:
144
+ pax.place_order(...)
145
+ except PaxRateLimitError as e:
146
+ time.sleep(e.retry_after or 5)
147
+ # then retry
148
+ except PaxValidationError as e:
149
+ print(f"Bad request: {e.details}") # {'field': 'size', ...}
150
+ except PaxReadOnlyModeError:
151
+ print("Trading paused by ops")
152
+ except PaxWrongEnvKeyError:
153
+ print("Wrong environment key")
154
+ except PaxError as e:
155
+ print(f"[{e.code}] {e.message} (request_id={e.request_id})")
156
+ ```
157
+
158
+ ## Automatic retry
159
+
160
+ Built-in exponential backoff on `429`, `500`, `502`, `504` responses. `Retry-After`
161
+ header respected on rate limits. Non-idempotent creates are safe when you send
162
+ `client_order_id`.
163
+
164
+ ```python
165
+ pax = PaxClient(api_key="sk_test_...", env="sandbox", max_retries=5)
166
+ # max_retries=0 disables retries entirely
167
+ ```
168
+
169
+ ## Environments
170
+
171
+ | Env | Base URL | Keys |
172
+ |---|---|---|
173
+ | `production` | `https://api.predictasiax.com/v1` | `sk_live_*` |
174
+ | `sandbox` | `https://api.predictasiax.com/v1` | `sk_test_*` |
175
+
176
+ `sk_test_*` on production returns `401 WRONG_ENV_KEY`. Same the other way. See
177
+ [docs auth guide](https://docs.predictasiax.com/auth#env-separation).
178
+
179
+ ## Custom base URL
180
+
181
+ ```python
182
+ pax = PaxClient(api_key="...", base_url="https://your-mirror/api")
183
+ ```
184
+
185
+ ## Development
186
+
187
+ ```bash
188
+ # Get source (tarball)
189
+ curl -O https://docs.predictasiax.com/downloads/sdk/python/pax_api-1.0.0.tar.gz
190
+ tar -xzf pax_api-1.0.0.tar.gz && cd pax_api-1.0.0
191
+ pip install -e ".[dev]"
192
+ pytest # run all tests
193
+ ruff check src tests # lint
194
+ mypy src # type-check
195
+ ```
196
+
197
+ ## Links
198
+
199
+ - [OpenAPI spec](https://docs.predictasiax.com/openapi)
200
+ - [AsyncAPI (WebSocket) spec](https://docs.predictasiax.com/asyncapi)
201
+ - [Auth guide](https://docs.predictasiax.com/auth)
202
+ - [Error codes](https://docs.predictasiax.com/errors)
203
+ - [Rate limits](https://docs.predictasiax.com/rate-limits)
204
+ - [FAQ](https://docs.predictasiax.com/faq)
205
+ - [API Terms](https://docs.predictasiax.com/api-terms)
206
+
207
+ ## License
208
+
209
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,169 @@
1
+ # pax-api — Official Python SDK for PredictAsiaX
2
+
3
+ Web3-native prediction market REST + WebSocket API client. Polymarket-compatible HMAC signing pattern.
4
+
5
+ **Version 1.0.0** · MIT license · Python 3.8+
6
+
7
+ - **Docs**: https://docs.predictasiax.com
8
+ - **Developer landing**: https://predictasiax.com/developer
9
+ - **Get API key**: https://predictasiax.com/settings/api-keys
10
+ - **Support**: support@predictasiax.com
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ # Direct install from PredictAsiaX-hosted wheel
16
+ pip install https://docs.predictasiax.com/downloads/sdk/python/pax_api-1.0.0-py3-none-any.whl
17
+
18
+ # Or download tarball + install locally
19
+ curl -O https://docs.predictasiax.com/downloads/sdk/python/pax_api-1.0.0.tar.gz
20
+ pip install pax_api-1.0.0.tar.gz
21
+ ```
22
+
23
+ (PyPI publish coming soon — install methods above work today.)
24
+
25
+ ## Quickstart (sandbox — no real money)
26
+
27
+ ```python
28
+ from pax_api import PaxClient
29
+
30
+ with PaxClient(api_key="sk_test_YOUR_KEY", env="sandbox") as pax:
31
+ faucet = pax.faucet() # get 10k test USDT
32
+ print(faucet["data"]["balance_free"]) # → "10000.000000"
33
+
34
+ templates = pax.list_templates()
35
+ markets = pax.list_markets(category="crypto", limit=10)
36
+
37
+ market = pax.create_market(
38
+ template_id="crypto_price_binary_60s",
39
+ params={"asset": "BTC"},
40
+ )
41
+ print(market["data"]["market"]["market_id"]) # → "m_..."
42
+ ```
43
+
44
+ ## HMAC-signed requests (production)
45
+
46
+ Machine-to-machine trading bots should use HMAC signing (Polymarket-compatible 5-header pattern).
47
+
48
+ ```python
49
+ from pax_api import PaxClient
50
+
51
+ pax = PaxClient(
52
+ api_key="sk_live_YOUR_KEY",
53
+ secret="<64-hex secret>",
54
+ passphrase="<passphrase>",
55
+ env="production",
56
+ )
57
+
58
+ pax.place_order(
59
+ market_id="m_...",
60
+ outcome_id="yes",
61
+ side="buy",
62
+ order_type="limit",
63
+ size="100",
64
+ price="0.55",
65
+ client_order_id="unique-per-intent-id", # retry-safe within 24h
66
+ )
67
+ ```
68
+
69
+ ## WebSocket streams
70
+
71
+ ```python
72
+ from pax_api import PaxWSClient
73
+
74
+ ws = PaxWSClient(
75
+ api_key="sk_test_...",
76
+ env="sandbox",
77
+ subscribe_on_connect=["fast_tick", "trade_executed", "account"],
78
+ )
79
+ ws.on("fast_tick", lambda e: print("tick:", e))
80
+ ws.on("trade_executed", lambda e: print("trade:", e))
81
+ ws.on("account", lambda e: print("balance:", e.get("balance_free")))
82
+ ws.run_forever() # blocks; Ctrl+C to exit
83
+ ```
84
+
85
+ Auto-reconnect + exponential backoff built-in. All 4 client methods supported:
86
+ `subscribe`, `unsubscribe`, `auth`, `set_locale`.
87
+
88
+ ## Error handling
89
+
90
+ Every response error becomes a typed exception. Catch the specific type
91
+ you want to handle:
92
+
93
+ ```python
94
+ from pax_api import (
95
+ PaxClient,
96
+ PaxRateLimitError,
97
+ PaxReadOnlyModeError,
98
+ PaxValidationError,
99
+ PaxWrongEnvKeyError,
100
+ PaxError, # base class — catch-all
101
+ )
102
+
103
+ try:
104
+ pax.place_order(...)
105
+ except PaxRateLimitError as e:
106
+ time.sleep(e.retry_after or 5)
107
+ # then retry
108
+ except PaxValidationError as e:
109
+ print(f"Bad request: {e.details}") # {'field': 'size', ...}
110
+ except PaxReadOnlyModeError:
111
+ print("Trading paused by ops")
112
+ except PaxWrongEnvKeyError:
113
+ print("Wrong environment key")
114
+ except PaxError as e:
115
+ print(f"[{e.code}] {e.message} (request_id={e.request_id})")
116
+ ```
117
+
118
+ ## Automatic retry
119
+
120
+ Built-in exponential backoff on `429`, `500`, `502`, `504` responses. `Retry-After`
121
+ header respected on rate limits. Non-idempotent creates are safe when you send
122
+ `client_order_id`.
123
+
124
+ ```python
125
+ pax = PaxClient(api_key="sk_test_...", env="sandbox", max_retries=5)
126
+ # max_retries=0 disables retries entirely
127
+ ```
128
+
129
+ ## Environments
130
+
131
+ | Env | Base URL | Keys |
132
+ |---|---|---|
133
+ | `production` | `https://api.predictasiax.com/v1` | `sk_live_*` |
134
+ | `sandbox` | `https://api.predictasiax.com/v1` | `sk_test_*` |
135
+
136
+ `sk_test_*` on production returns `401 WRONG_ENV_KEY`. Same the other way. See
137
+ [docs auth guide](https://docs.predictasiax.com/auth#env-separation).
138
+
139
+ ## Custom base URL
140
+
141
+ ```python
142
+ pax = PaxClient(api_key="...", base_url="https://your-mirror/api")
143
+ ```
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ # Get source (tarball)
149
+ curl -O https://docs.predictasiax.com/downloads/sdk/python/pax_api-1.0.0.tar.gz
150
+ tar -xzf pax_api-1.0.0.tar.gz && cd pax_api-1.0.0
151
+ pip install -e ".[dev]"
152
+ pytest # run all tests
153
+ ruff check src tests # lint
154
+ mypy src # type-check
155
+ ```
156
+
157
+ ## Links
158
+
159
+ - [OpenAPI spec](https://docs.predictasiax.com/openapi)
160
+ - [AsyncAPI (WebSocket) spec](https://docs.predictasiax.com/asyncapi)
161
+ - [Auth guide](https://docs.predictasiax.com/auth)
162
+ - [Error codes](https://docs.predictasiax.com/errors)
163
+ - [Rate limits](https://docs.predictasiax.com/rate-limits)
164
+ - [FAQ](https://docs.predictasiax.com/faq)
165
+ - [API Terms](https://docs.predictasiax.com/api-terms)
166
+
167
+ ## License
168
+
169
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pax-api"
7
+ version = "2.0.0"
8
+ description = "Official Python SDK for the PredictAsiaX Trader Track API — Web3-native prediction markets"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "PredictAsiaX", email = "support@predictasiax.com" },
14
+ ]
15
+ keywords = ["predictasiax", "pax", "prediction-market", "trading-api", "web3", "hmac", "polymarket-compatible"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Intended Audience :: Financial and Insurance Industry",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.8",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: Office/Business :: Financial :: Investment",
29
+ "Topic :: Software Development :: Libraries :: Python Modules",
30
+ ]
31
+ dependencies = [
32
+ "requests>=2.28",
33
+ "websocket-client>=1.5",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=7",
39
+ "pytest-cov>=4",
40
+ "responses>=0.23",
41
+ "ruff>=0.1",
42
+ "mypy>=1",
43
+ "build>=1",
44
+ "twine>=4",
45
+ ]
46
+
47
+ [project.urls]
48
+ Homepage = "https://predictasiax.com/developer"
49
+ Documentation = "https://docs.predictasiax.com"
50
+ Repository = "https://github.com/predictasiax/pax-python-sdk"
51
+ Issues = "https://github.com/predictasiax/pax-python-sdk/issues"
52
+ Changelog = "https://docs.predictasiax.com/changelog"
53
+
54
+ [tool.setuptools.packages.find]
55
+ where = ["src"]
56
+
57
+ [tool.setuptools.package-data]
58
+ pax_api = ["py.typed"]
59
+
60
+ [tool.ruff]
61
+ line-length = 120
62
+ target-version = "py38"
63
+
64
+ [tool.mypy]
65
+ python_version = "3.8"
66
+ strict = true
67
+ warn_return_any = true
68
+
69
+ [tool.pytest.ini_options]
70
+ minversion = "7.0"
71
+ addopts = "-ra -q --strict-markers"
72
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,60 @@
1
+ """PredictAsiaX (PAX) Python SDK.
2
+
3
+ Web3-native prediction market REST + WebSocket API client.
4
+ Homepage: https://predictasiax.com/developer
5
+ Docs: https://docs.predictasiax.com
6
+
7
+ Quick start (anonymous sandbox mint in 30 sec):
8
+ >>> from pax_api import PaxClient
9
+ >>> boot = PaxClient(api_key="anonymous")
10
+ >>> key = boot.mint_sandbox_key(org_name="my-app")
11
+ >>> print(key["api_key"]) # sk_live_..., shown ONCE — save it
12
+ >>> client = PaxClient(api_key=key["api_key"])
13
+ >>> client.list_markets(category="crypto", limit=10)
14
+
15
+ For HMAC signing (production trading bots):
16
+ >>> client = PaxClient(
17
+ ... api_key="sk_live_...",
18
+ ... secret="<hex-secret>",
19
+ ... passphrase="<passphrase>",
20
+ ... )
21
+ >>> client.place_order(market_id="m_...", outcome_id="yes",
22
+ ... side="buy", order_type="market", size="100")
23
+
24
+ Environment model: sandbox is a *tier* on the key (tier=self_serve, $10/$100
25
+ caps), not a separate host. Same prefix, same URL — promote to production by
26
+ upgrading the key's tier.
27
+ """
28
+
29
+ __version__ = "2.0.0"
30
+
31
+ from pax_api.client import PaxClient # noqa: E402
32
+ from pax_api.ws_client import PaxWSClient # noqa: E402
33
+ from pax_api.errors import ( # noqa: E402
34
+ PaxError,
35
+ PaxAuthError,
36
+ PaxRateLimitError,
37
+ PaxValidationError,
38
+ PaxNotFoundError,
39
+ PaxConflictError,
40
+ PaxServerError,
41
+ PaxReadOnlyModeError,
42
+ PaxSandboxOnlyError,
43
+ PaxWrongEnvKeyError,
44
+ )
45
+
46
+ __all__ = [
47
+ "PaxClient",
48
+ "PaxWSClient",
49
+ "PaxError",
50
+ "PaxAuthError",
51
+ "PaxRateLimitError",
52
+ "PaxValidationError",
53
+ "PaxNotFoundError",
54
+ "PaxConflictError",
55
+ "PaxServerError",
56
+ "PaxReadOnlyModeError",
57
+ "PaxSandboxOnlyError",
58
+ "PaxWrongEnvKeyError",
59
+ "__version__",
60
+ ]
@@ -0,0 +1,66 @@
1
+ """HMAC signing helpers — Polymarket-compatible 5-header pattern.
2
+
3
+ Signature construction:
4
+ message = timestamp_ms + method_upper + path + body
5
+ signature = base64(HMAC-SHA256(secret, message))
6
+
7
+ Headers sent:
8
+ POLY_ACCESS_KEY: <key_id>
9
+ POLY_TIMESTAMP: <ms epoch>
10
+ POLY_PASSPHRASE: <passphrase>
11
+ POLY_SIGNATURE: <base64 sig>
12
+
13
+ Server rejects with 401 INVALID_SIGNATURE if timestamp is more than
14
+ ±30 seconds from server clock. Sync your machine via NTP.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import base64
20
+ import hashlib
21
+ import hmac
22
+ import time
23
+ from typing import Dict, Optional
24
+
25
+
26
+ def sign_request(
27
+ method: str,
28
+ path: str,
29
+ body: str,
30
+ key_id: str,
31
+ secret: str,
32
+ passphrase: str,
33
+ timestamp_ms: Optional[int] = None,
34
+ ) -> Dict[str, str]:
35
+ """Build the 5 POLY_* headers for an HMAC-signed request.
36
+
37
+ Args:
38
+ method: HTTP method (GET/POST/DELETE/etc.). Case-normalized to upper.
39
+ path: URL path INCLUDING leading slash and query string, e.g.
40
+ ``/v1/markets?category=crypto``.
41
+ body: Raw request body as string (empty string for GET/DELETE
42
+ without body). Must be the exact bytes sent on the wire.
43
+ key_id: Your ``sk_live_*`` key ID.
44
+ secret: HMAC secret (hex string) obtained at key mint time.
45
+ passphrase: Passphrase obtained at key mint time.
46
+ timestamp_ms: Override timestamp for testing. Defaults to now.
47
+
48
+ Returns:
49
+ Dict of 5 headers to merge into the request.
50
+ """
51
+ ts = str(timestamp_ms if timestamp_ms is not None else int(time.time() * 1000))
52
+ method_u = method.upper()
53
+ message = ts + method_u + path + (body or "")
54
+ digest = hmac.new(secret.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).digest()
55
+ signature = base64.b64encode(digest).decode("ascii")
56
+ return {
57
+ "POLY_ACCESS_KEY": key_id,
58
+ "POLY_TIMESTAMP": ts,
59
+ "POLY_PASSPHRASE": passphrase,
60
+ "POLY_SIGNATURE": signature,
61
+ }
62
+
63
+
64
+ def build_message(method: str, path: str, body: str, timestamp_ms: int) -> str:
65
+ """Return the canonical signing message (exposed for debugging)."""
66
+ return f"{timestamp_ms}{method.upper()}{path}{body or ''}"