pmquant 0.4.1__tar.gz → 0.4.3__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.
Files changed (53) hide show
  1. pmquant-0.4.3/.github/dependabot.yml +13 -0
  2. {pmquant-0.4.1 → pmquant-0.4.3}/.github/workflows/canary.yml +8 -4
  3. {pmquant-0.4.1 → pmquant-0.4.3}/.github/workflows/publish.yml +3 -3
  4. pmquant-0.4.3/.github/workflows/scorecard.yml +28 -0
  5. {pmquant-0.4.1 → pmquant-0.4.3}/.github/workflows/test.yml +5 -2
  6. {pmquant-0.4.1 → pmquant-0.4.3}/.gitignore +1 -0
  7. pmquant-0.4.3/.hypothesis/.gitignore +9 -0
  8. pmquant-0.4.3/.hypothesis/constants/07a2a0eac57d1dd0 +4 -0
  9. pmquant-0.4.3/.hypothesis/constants/1720e64af9235558 +4 -0
  10. pmquant-0.4.3/.hypothesis/constants/6c9ffb0a1efc27b6 +4 -0
  11. pmquant-0.4.3/.hypothesis/constants/855d9c2e5b4693f1 +4 -0
  12. pmquant-0.4.3/.hypothesis/constants/ef909bf87e6ac33f +4 -0
  13. pmquant-0.4.3/.hypothesis/unicode_data/15.0.0/charmap.json.gz +0 -0
  14. pmquant-0.4.3/.hypothesis/unicode_data/15.0.0/codec-utf-8.json.gz +0 -0
  15. {pmquant-0.4.1 → pmquant-0.4.3}/CHANGELOG.md +42 -0
  16. {pmquant-0.4.1 → pmquant-0.4.3}/CLAUDE.md +9 -1
  17. {pmquant-0.4.1 → pmquant-0.4.3}/PKG-INFO +63 -10
  18. {pmquant-0.4.1 → pmquant-0.4.3}/README.md +61 -9
  19. {pmquant-0.4.1 → pmquant-0.4.3}/SECURITY.md +15 -4
  20. pmquant-0.4.3/bot-template/bot.py +354 -0
  21. {pmquant-0.4.1 → pmquant-0.4.3}/bot-template/dash/bot_dash.py +19 -7
  22. {pmquant-0.4.1 → pmquant-0.4.3}/llms.txt +6 -0
  23. {pmquant-0.4.1 → pmquant-0.4.3}/pyproject.toml +2 -2
  24. {pmquant-0.4.1 → pmquant-0.4.3}/server.json +3 -3
  25. {pmquant-0.4.1 → pmquant-0.4.3}/src/pmq/__init__.py +1 -1
  26. {pmquant-0.4.1 → pmquant-0.4.3}/src/pmq/doctor.py +88 -58
  27. {pmquant-0.4.1 → pmquant-0.4.3}/src/pmq/executor.py +55 -14
  28. {pmquant-0.4.1 → pmquant-0.4.3}/tests/test_canary_live.py +33 -0
  29. {pmquant-0.4.1 → pmquant-0.4.3}/tests/test_executor.py +70 -0
  30. pmquant-0.4.3/tests/test_fill_fuzz.py +118 -0
  31. pmquant-0.4.3/tests/test_template_engine.py +398 -0
  32. pmquant-0.4.1/bot-template/bot.py +0 -305
  33. pmquant-0.4.1/tests/test_template_engine.py +0 -82
  34. {pmquant-0.4.1 → pmquant-0.4.3}/AGENTS.md +0 -0
  35. {pmquant-0.4.1 → pmquant-0.4.3}/CONTRIBUTING.md +0 -0
  36. {pmquant-0.4.1 → pmquant-0.4.3}/LICENSE +0 -0
  37. {pmquant-0.4.1 → pmquant-0.4.3}/bot-template/README.md +0 -0
  38. {pmquant-0.4.1 → pmquant-0.4.3}/bot-template/dash/dash.html +0 -0
  39. {pmquant-0.4.1 → pmquant-0.4.3}/bot-template/pmq-bot.service +0 -0
  40. {pmquant-0.4.1 → pmquant-0.4.3}/bot-template/strategy.py +0 -0
  41. {pmquant-0.4.1 → pmquant-0.4.3}/docs/assets/pmq-doctor.svg +0 -0
  42. {pmquant-0.4.1 → pmquant-0.4.3}/docs/recipes.md +0 -0
  43. {pmquant-0.4.1 → pmquant-0.4.3}/docs/rounding-study.md +0 -0
  44. {pmquant-0.4.1 → pmquant-0.4.3}/docs/war-story.md +0 -0
  45. {pmquant-0.4.1 → pmquant-0.4.3}/examples/fak_buy_guarded.py +0 -0
  46. {pmquant-0.4.1 → pmquant-0.4.3}/examples/read_market.py +0 -0
  47. {pmquant-0.4.1 → pmquant-0.4.3}/src/pmq/data.py +0 -0
  48. {pmquant-0.4.1 → pmquant-0.4.3}/src/pmq/exceptions.py +0 -0
  49. {pmquant-0.4.1 → pmquant-0.4.3}/src/pmq/mcp.py +0 -0
  50. {pmquant-0.4.1 → pmquant-0.4.3}/src/pmq/py.typed +0 -0
  51. {pmquant-0.4.1 → pmquant-0.4.3}/tests/test_data.py +0 -0
  52. {pmquant-0.4.1 → pmquant-0.4.3}/tests/test_doctor.py +0 -0
  53. {pmquant-0.4.1 → pmquant-0.4.3}/tests/test_mcp.py +0 -0
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: pip
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ open-pull-requests-limit: 5
9
+ - package-ecosystem: github-actions
10
+ directory: /
11
+ schedule:
12
+ interval: weekly
13
+ day: monday
@@ -12,8 +12,8 @@ jobs:
12
12
  canary:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v5
16
- - uses: actions/setup-python@v6
15
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
17
17
  with:
18
18
  python-version: "3.12"
19
19
  - run: pip install -e ".[dev]"
@@ -21,6 +21,10 @@ jobs:
21
21
  env:
22
22
  PMQ_CANARY: "1"
23
23
  run: pytest tests/test_canary_live.py -q
24
+ - name: Dependency vulnerability audit
25
+ run: |
26
+ pip install pip-audit
27
+ pip-audit --skip-editable
24
28
  - name: Open an issue if Polymarket drifted
25
29
  if: failure()
26
30
  env:
@@ -29,6 +33,6 @@ jobs:
29
33
  existing=$(gh issue list --label canary --state open --json number --jq length)
30
34
  if [ "$existing" = "0" ]; then
31
35
  gh issue create --label canary \
32
- --title "Canary: Polymarket surface drifted ($(date -u +%F))" \
33
- --body "The weekly live canary failed: an endpoint shape or the installed py-clob-client-v2 surface no longer matches what pmq was verified against. See the failed run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
36
+ --title "Canary failed: endpoint drift or vulnerable dependency ($(date -u +%F))" \
37
+ --body "The weekly canary failed: an endpoint shape or the installed py-clob-client-v2 surface no longer matches what pmq was verified against, the egress allowlist saw a foreign host, or pip-audit found a vulnerable dependency. See the failed run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
34
38
  fi
@@ -11,10 +11,10 @@ jobs:
11
11
  permissions:
12
12
  id-token: write
13
13
  steps:
14
- - uses: actions/checkout@v5
14
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
15
15
 
16
16
  - name: Set up Python
17
- uses: actions/setup-python@v6
17
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
18
18
  with:
19
19
  python-version: "3.12"
20
20
 
@@ -41,4 +41,4 @@ jobs:
41
41
  python -m build
42
42
 
43
43
  - name: Publish to PyPI
44
- uses: pypa/gh-action-pypi-publish@release/v1
44
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
@@ -0,0 +1,28 @@
1
+ name: scorecard
2
+ on:
3
+ schedule:
4
+ - cron: "23 7 * * 1" # weekly, monday 07:23 UTC
5
+ push:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+
9
+ permissions: read-all
10
+
11
+ jobs:
12
+ analysis:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ security-events: write # SARIF upload
16
+ id-token: write # signed publication to scorecard.dev
17
+ steps:
18
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
19
+ with:
20
+ persist-credentials: false
21
+ - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
22
+ with:
23
+ results_file: results.sarif
24
+ results_format: sarif
25
+ publish_results: true
26
+ - uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
27
+ with:
28
+ sarif_file: results.sarif
@@ -2,6 +2,9 @@ name: tests
2
2
  on:
3
3
  push:
4
4
  pull_request:
5
+
6
+ permissions:
7
+ contents: read
5
8
  jobs:
6
9
  test:
7
10
  runs-on: ubuntu-latest
@@ -9,8 +12,8 @@ jobs:
9
12
  matrix:
10
13
  python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
11
14
  steps:
12
- - uses: actions/checkout@v5
13
- - uses: actions/setup-python@v6
15
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
14
17
  with:
15
18
  python-version: ${{ matrix.python-version }}
16
19
  - run: pip install -e ".[dev]"
@@ -7,3 +7,4 @@ build/
7
7
  .pytest_cache/
8
8
  .env
9
9
  .coverage
10
+ .pyscn/
@@ -0,0 +1,9 @@
1
+ # This .gitignore file was automatically created by Hypothesis. Hypothesis gitignores
2
+ # .hypothesis by default, because we generally recommend that .hypothesis not be checked
3
+ # into version control.
4
+ #
5
+ # If you *would* like to check .hypothesis into version control, you should delete this
6
+ # file. Hypothesis will not re-create this .gitignore unless .hypothesis is deleted (and
7
+ # if it does, that's a bug - please report it!)
8
+
9
+ *
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/pmq/pmq/src/pmq/executor.py
2
+ # hypothesis_version: 6.156.1
3
+
4
+ [0.0, 1000000.0, 137, 300, 400, 500, '0', '0.01', '0x[0-9a-fA-F]{64}', '; ', 'AssetType', 'BUY', 'FAILED', 'FAK', 'MAKER', 'MarketOrderArgs', 'MarketOrderArgsV2', 'OpenOrderParams', 'OrderArgs', 'OrderArgsV2', 'OrderType', 'POLY_BUILDER_CODE', 'POLY_FUNDER', 'POLY_PRIVATE_KEY', 'POLY_SIG_TYPE', 'SELL', 'TradeParams', '_pmq_taker4', 'amount', 'balance', 'builder_code', 'cancel_market_orders', 'crypto', 'error_msg', 'fd', 'get_open_orders', 'get_trades', 'makingAmount', 'off', 'on', 'orderID', 'order_args', 'order_type', 'params', 'payload', 'pmq', 'price', 'r', 'side', 'size', 'status', 'status_code', 'success', 'takingAmount', 'token_id', 'trader_side']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/pmq/pmq/src/pmq/__init__.py
2
+ # hypothesis_version: 6.156.1
3
+
4
+ ['0.4.3', 'DEFAULT_BUILDER_CODE', 'FEE_RATES', 'Fill', 'OrderUncertain', 'PmqError', 'PolymarketExecutor', '__version__', 'band_ask_depth_usd', 'best_bid_ask', 'book_inferred_winner', 'book_meta', 'event_markets', 'fee', 'get_book', 'get_market', 'get_tape', 'http_get_json', 'parse_market', 'positions', 'resolved_winner']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/pmq/pmq/src/pmq/exceptions.py
2
+ # hypothesis_version: 6.156.1
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/pmq/pmq/src/pmq/data.py
2
+ # hypothesis_version: 6.156.1
3
+
4
+ [0.0, 0.03, 0.04, 0.05, 0.07, 0.9, 0.99, 1.0, 1.5, 200, '%Y-%m-%dT%H:%M:%SZ', 'Mozilla/5.0', 'User-Agent', 'a', 'asks', 'b', 'bids', 'clobTokenIds', 'conditionId', 'crypto', 'culture', 'economics', 'endDate', 'endDateIso', 'finance', 'geopolitics', 'idx_a', 'last_trade_price', 'markets', 'mentions', 'min_order_size', 'neg_risk', 'outcomePrices', 'outcome_a', 'outcome_b', 'outcome_prices_raw', 'outcomes', 'politics', 'price', 'size', 'slug', 'sports', 'tech', 'tick_size', 'timestamp', 'weather']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/pmq/pmq/src/pmq/doctor.py
2
+ # hypothesis_version: 6.156.1
3
+
4
+ [1000000.0, 120, 160, 400, '\nverdict:', ', ', '--market', '0', '0x', '0x70a08231', '0x8da5cb5b', '2.0', 'CLOB auth/collateral', 'Content-Type', 'FAK', 'Mozilla/5.0', 'OrderType.FAK', 'POLY_FUNDER', 'POLY_PRIVATE_KEY', 'POLY_SIG_TYPE', 'User-Agent', '[!!]', '[??]', '[ok]', '__main__', 'application/json', 'data', 'drifted: ', 'error', 'eth_call', 'eth_getCode', 'funder', 'id', 'jsonrpc', 'latest', 'method', 'min_order_size', 'params', 'result', 'to', 'token_a']
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.3 (2026-07-04)
4
+
5
+ * Fix: py-clob-client-v2 1.0.2 reuses its limit-order rounding table for
6
+ MARKET orders, so on markets whose tick size is finer than 0.01 it signs
7
+ taker amounts with 5-6 decimals; the V2 exchange rejects those with
8
+ "invalid amounts ... taker amount a max of 4 decimals" and every FAK on
9
+ such a market fails. The executor now clamps the market-order path to 4
10
+ decimals at client level (round-down: the never-exceed-budget contract is
11
+ intact, the dust given up is under 0.0001 share per order). Found in
12
+ production: a fine-tick market rejected 10 consecutive buys and the
13
+ fail-closed halt fired exactly as designed; nothing was booked, nothing
14
+ was lost. Regression test pins maker 2dp / taker 4dp on both sides for
15
+ ticks 0.01, 0.001 and 0.0001.
16
+
17
+ * Trust batch: executable egress proof in the canary suite (records every
18
+ DNS resolution during a full session incl. a signed zero-fund order;
19
+ fails on any host outside polymarket.com; weekly CI prints the list in
20
+ public logs), a test pinning that the private key never reaches logs,
21
+ OpenSSF Scorecard workflow + badge, GitHub Actions pinned by commit SHA,
22
+ explicit workflow permissions, Dependabot (pip + actions), weekly
23
+ pip-audit wired into the canary alarm, README sections "Verify the
24
+ claims yourself" (egress, PEP 740 provenance, dependency watch) and
25
+ "Stability and maintenance" (pre-1.0 SemVer contract, the stated bar for
26
+ 1.0, bus-factor honesty, precisely scoped help-wanted).
27
+ * Quality pass driven by pyscn (CFG complexity, dead code, clones): the
28
+ template engine loop and pmq-doctor were split into single-purpose phase
29
+ functions (worst cyclomatic complexity 36 -> 10 across the repo, zero dead
30
+ code, behavior identical), and the previously untested template main loop
31
+ is now pinned by 17 end-to-end tests (fake clock, stubbed exchange):
32
+ paper fills at the real ask, budget headroom, halts, poisoning,
33
+ consecutive-failure exit, exchange-truth scoring. 113 tests total.
34
+ `pyscn check src/pmq bot-template` passes at default thresholds.
35
+
36
+ ## 0.4.2 (2026-07-03)
37
+
38
+ * Fix: `buy_fak`/`sell_fak` cent-rounding used `int(x * 100) / 100`, which
39
+ floors a binary-drifted float (`16.90` stored as `16.8999…` became
40
+ `16.89`), silently shaving a cent off intended-clean amounts. Replaced with
41
+ a `Decimal`-based `_floor_cents` that rounds down without the drift, keeping
42
+ the never-exceed-budget contract. Matches the behavior documented in
43
+ docs/rounding-study.md.
44
+
3
45
  ## 0.4.1 (2026-07-03)
4
46
 
5
47
  * Introspection guard now also verifies `OrderArgsV2` fields (including
@@ -32,7 +32,10 @@ agents EDITING it. Read both before changing code.)
32
32
 
33
33
  ## Working rules
34
34
 
35
- * Tests green (`pytest -q`) and `ruff check .` clean before any push; add
35
+ * Tests green (`pytest -q`) and `ruff check .` clean before any push;
36
+ `pyscn check src/pmq bot-template` (complexity <= 10, no dead code)
37
+ must stay green too; clone warnings are informational (the template
38
+ dash deliberately duplicates helpers to stay stdlib-standalone). Add
36
39
  tests with every behavior change. Network-touching tests go to
37
40
  `tests/test_canary_live.py` behind `PMQ_CANARY=1`, never in default CI.
38
41
  * Exchange rules (min size, tick, fee rate) are READ from the venue
@@ -42,6 +45,11 @@ agents EDITING it. Read both before changing code.)
42
45
  update CHANGELOG.md, push, then `gh release create vX.Y.Z`: PyPI publish
43
46
  is automatic via trusted publishing (no tokens anywhere). PyPI name is
44
47
  `pmquant`, import name `pmq`: keep the README line explaining it.
48
+ * GitHub Actions stay pinned by commit SHA (dependabot bumps them); new
49
+ workflows get an explicit least-privilege permissions block. The egress
50
+ test and pip-audit ride the weekly canary: never move them to default CI
51
+ (they need network) and never widen the egress allowlist beyond
52
+ polymarket.com without updating SECURITY.md and the README section.
45
53
  * The weekly canary workflow is the drift alarm: if it opens an issue, the
46
54
  fix starts by re-running the introspection against the new surface, not
47
55
  by loosening the checks.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pmquant
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Fail-closed execution and market-data layer for Polymarket CLOB V2: local signing, confirmed fills only, fee-correct math, working deposit-wallet (POLY_1271) support.
5
5
  Project-URL: Homepage, https://github.com/crp4222/pmq
6
6
  Project-URL: Issues, https://github.com/crp4222/pmq/issues
@@ -23,6 +23,7 @@ Classifier: Typing :: Typed
23
23
  Requires-Python: >=3.10
24
24
  Requires-Dist: py-clob-client-v2<2,>=1.0.2
25
25
  Provides-Extra: dev
26
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
26
27
  Requires-Dist: mcp>=1.2; extra == 'dev'
27
28
  Requires-Dist: mypy>=1.10; extra == 'dev'
28
29
  Requires-Dist: pytest-cov>=5; extra == 'dev'
@@ -41,6 +42,7 @@ Description-Content-Type: text/markdown
41
42
  [![canary](https://github.com/crp4222/pmq/actions/workflows/canary.yml/badge.svg)](https://github.com/crp4222/pmq/actions/workflows/canary.yml)
42
43
  [![coverage gate](https://img.shields.io/badge/coverage-%E2%89%A585%25%20enforced%20in%20CI-blue)](.github/workflows/test.yml)
43
44
  [![typed](https://img.shields.io/badge/types-mypy%20strict-blue)](pyproject.toml)
45
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/crp4222/pmq/badge)](https://scorecard.dev/viewer/?uri=github.com/crp4222/pmq)
44
46
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
45
47
 
46
48
  Fail-closed execution and market data for **Polymarket CLOB V2**, in Python.
@@ -81,13 +83,15 @@ a real error in live trading:
81
83
 
82
84
  The full write-up with reproduction details: [docs/war-story.md](docs/war-story.md).
83
85
 
84
- ## Runs in production
86
+ ## Runs in production: my own money, daily
85
87
 
86
- The maintainer's own bot trades through this exact executor 24/7 with real
87
- money. Example receipt (2026-07-03): settlement transaction
88
+ I built pmq for my own trading. It executes real volume with my funds every
89
+ day, and it has never booked a fill the exchange did not confirm. If you
90
+ want to see it on-chain, here is a settlement from one of my wallets
91
+ (2026-07-03):
88
92
  [`0x387f5f09...100d88a8`](https://polygonscan.com/tx/0x387f5f09c031bb36a71c54adc978b1ed4d50c67f6dd3f0c2c8068391100d88a8)
89
93
  on the CTF Exchange V2: a FAK market buy built by this library, matched and
90
- settled, with the builder code visible in the calldata. Additionally, a weekly
94
+ settled, with the builder code visible in the calldata. A weekly
91
95
  [canary workflow](.github/workflows/canary.yml) exercises the real endpoints
92
96
  and the installed client surface, and opens an issue by itself if Polymarket
93
97
  drifts.
@@ -160,9 +164,10 @@ else:
160
164
  print(fill.matched_shares, "shares at", fill.price, "order", fill.order_id)
161
165
  ```
162
166
 
163
- `sell_fak` and `limit_gtc` follow the same contract. The buy path has carried
164
- live volume; treat the sell path as following the same documented semantics
165
- with less battle time.
167
+ `sell_fak` and `limit_gtc` follow the same contract. Both FAK paths have
168
+ carried real volume: a production round trip (buy 5.149 @ 0.94, sell back
169
+ 5.14 @ 0.94, cross-checked via `get_trades`) confirmed the mirrored
170
+ `makingAmount`/`takingAmount` semantics on 2026-07-03.
166
171
 
167
172
  ## The signature_type table nobody gives you
168
173
 
@@ -247,12 +252,60 @@ shipped demo strategy is an API illustration meant to be replaced.
247
252
  * Keys are read from the environment, used to instantiate the signer, and
248
253
  never logged. No custody, no backend, no telemetry, zero network calls
249
254
  besides Polymarket endpoints.
250
- * Beware of the documented wave of fake "polymarket bot" repositories that
251
- steal private keys. Read the source: pmq is small on purpose.
255
+ * A documented wave of fake "polymarket bot" repositories steals private
256
+ keys; pmq is deliberately small so the entire execution path stays
257
+ readable in minutes by anyone who wants to look.
252
258
  * Fund the trading wallet with what you can afford to lose. Nothing here is
253
259
  financial advice; prediction-market access is restricted in some
254
260
  jurisdictions and compliance is on you.
255
261
 
262
+ ## If you feel like checking any of it
263
+
264
+ None of the claims above require taking my word; each one comes with a
265
+ handle you can pull, whenever you care to:
266
+
267
+ * **Egress.** `PMQ_CANARY=1 pytest tests/test_canary_live.py -k egress -s`
268
+ records every DNS resolution during a full session (market data, auth
269
+ derivation, one signed order) and fails on any host outside
270
+ `polymarket.com`. Last observed list: `clob.polymarket.com`,
271
+ `gamma-api.polymarket.com`, nothing else. The weekly
272
+ [canary](../../actions/workflows/canary.yml) prints that list in public
273
+ CI logs. One designed exception: `pmq-doctor`'s optional on-chain checks
274
+ use the public Polygon RPCs named in its source.
275
+ * **Provenance.** Releases carry a signed PEP 740 attestation (Sigstore,
276
+ via PyPI trusted publishing): click "provenance" next to any file on the
277
+ [PyPI files page](https://pypi.org/project/pmquant/#files), or fetch it
278
+ raw from PyPI's integrity API. The signing identity is this repository's
279
+ `publish.yml` workflow.
280
+ * **Dependencies.** Dependabot files weekly bump PRs (Python and
281
+ SHA-pinned GitHub Actions), and the weekly canary runs `pip-audit`; a
282
+ hit opens an issue by itself.
283
+ * **The source.** Five small modules; the whole execution path reads in
284
+ minutes. The grep targets that answer the important questions fastest
285
+ are listed in [SECURITY.md](SECURITY.md).
286
+
287
+ ## Stability and maintenance
288
+
289
+ * Pre-1.0 SemVer: PATCH releases only fix, MINOR releases may change the
290
+ public API with the migration named in [CHANGELOG.md](CHANGELOG.md).
291
+ Nothing changes silently.
292
+ * Deprecated APIs keep working and warn for at least one MINOR release
293
+ before removal.
294
+ * The bar for 1.0, stated in advance: months of green weekly canaries, the
295
+ maker path (`limit_gtc`) production-proven with real volume the way the
296
+ FAK paths already are, and external production users.
297
+ * Bus-factor honesty: one maintainer, who trades real money through this
298
+ exact code daily (strongest available incentive to keep it correct). The
299
+ mitigations are structural, not promises: five small modules, the
300
+ executable fill-contract test table, a weekly canary that opens issues by
301
+ itself, SHA-pinned CI. Operational rule: if the canary badge goes red and
302
+ stays red, treat the project as unmaintained and pin your last known-good
303
+ version.
304
+ * Help wanted, precisely scoped: production receipts for `signature_type`
305
+ 1 and 2 accounts (legacy Magic/email and browser-wallet proxies). Both
306
+ paths are introspection-tested but have never carried real money through
307
+ this library; the maintainer's own accounts are all types 0 and 3.
308
+
256
309
  ## License
257
310
 
258
311
  MIT
@@ -7,6 +7,7 @@
7
7
  [![canary](https://github.com/crp4222/pmq/actions/workflows/canary.yml/badge.svg)](https://github.com/crp4222/pmq/actions/workflows/canary.yml)
8
8
  [![coverage gate](https://img.shields.io/badge/coverage-%E2%89%A585%25%20enforced%20in%20CI-blue)](.github/workflows/test.yml)
9
9
  [![typed](https://img.shields.io/badge/types-mypy%20strict-blue)](pyproject.toml)
10
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/crp4222/pmq/badge)](https://scorecard.dev/viewer/?uri=github.com/crp4222/pmq)
10
11
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
11
12
 
12
13
  Fail-closed execution and market data for **Polymarket CLOB V2**, in Python.
@@ -47,13 +48,15 @@ a real error in live trading:
47
48
 
48
49
  The full write-up with reproduction details: [docs/war-story.md](docs/war-story.md).
49
50
 
50
- ## Runs in production
51
+ ## Runs in production: my own money, daily
51
52
 
52
- The maintainer's own bot trades through this exact executor 24/7 with real
53
- money. Example receipt (2026-07-03): settlement transaction
53
+ I built pmq for my own trading. It executes real volume with my funds every
54
+ day, and it has never booked a fill the exchange did not confirm. If you
55
+ want to see it on-chain, here is a settlement from one of my wallets
56
+ (2026-07-03):
54
57
  [`0x387f5f09...100d88a8`](https://polygonscan.com/tx/0x387f5f09c031bb36a71c54adc978b1ed4d50c67f6dd3f0c2c8068391100d88a8)
55
58
  on the CTF Exchange V2: a FAK market buy built by this library, matched and
56
- settled, with the builder code visible in the calldata. Additionally, a weekly
59
+ settled, with the builder code visible in the calldata. A weekly
57
60
  [canary workflow](.github/workflows/canary.yml) exercises the real endpoints
58
61
  and the installed client surface, and opens an issue by itself if Polymarket
59
62
  drifts.
@@ -126,9 +129,10 @@ else:
126
129
  print(fill.matched_shares, "shares at", fill.price, "order", fill.order_id)
127
130
  ```
128
131
 
129
- `sell_fak` and `limit_gtc` follow the same contract. The buy path has carried
130
- live volume; treat the sell path as following the same documented semantics
131
- with less battle time.
132
+ `sell_fak` and `limit_gtc` follow the same contract. Both FAK paths have
133
+ carried real volume: a production round trip (buy 5.149 @ 0.94, sell back
134
+ 5.14 @ 0.94, cross-checked via `get_trades`) confirmed the mirrored
135
+ `makingAmount`/`takingAmount` semantics on 2026-07-03.
132
136
 
133
137
  ## The signature_type table nobody gives you
134
138
 
@@ -213,12 +217,60 @@ shipped demo strategy is an API illustration meant to be replaced.
213
217
  * Keys are read from the environment, used to instantiate the signer, and
214
218
  never logged. No custody, no backend, no telemetry, zero network calls
215
219
  besides Polymarket endpoints.
216
- * Beware of the documented wave of fake "polymarket bot" repositories that
217
- steal private keys. Read the source: pmq is small on purpose.
220
+ * A documented wave of fake "polymarket bot" repositories steals private
221
+ keys; pmq is deliberately small so the entire execution path stays
222
+ readable in minutes by anyone who wants to look.
218
223
  * Fund the trading wallet with what you can afford to lose. Nothing here is
219
224
  financial advice; prediction-market access is restricted in some
220
225
  jurisdictions and compliance is on you.
221
226
 
227
+ ## If you feel like checking any of it
228
+
229
+ None of the claims above require taking my word; each one comes with a
230
+ handle you can pull, whenever you care to:
231
+
232
+ * **Egress.** `PMQ_CANARY=1 pytest tests/test_canary_live.py -k egress -s`
233
+ records every DNS resolution during a full session (market data, auth
234
+ derivation, one signed order) and fails on any host outside
235
+ `polymarket.com`. Last observed list: `clob.polymarket.com`,
236
+ `gamma-api.polymarket.com`, nothing else. The weekly
237
+ [canary](../../actions/workflows/canary.yml) prints that list in public
238
+ CI logs. One designed exception: `pmq-doctor`'s optional on-chain checks
239
+ use the public Polygon RPCs named in its source.
240
+ * **Provenance.** Releases carry a signed PEP 740 attestation (Sigstore,
241
+ via PyPI trusted publishing): click "provenance" next to any file on the
242
+ [PyPI files page](https://pypi.org/project/pmquant/#files), or fetch it
243
+ raw from PyPI's integrity API. The signing identity is this repository's
244
+ `publish.yml` workflow.
245
+ * **Dependencies.** Dependabot files weekly bump PRs (Python and
246
+ SHA-pinned GitHub Actions), and the weekly canary runs `pip-audit`; a
247
+ hit opens an issue by itself.
248
+ * **The source.** Five small modules; the whole execution path reads in
249
+ minutes. The grep targets that answer the important questions fastest
250
+ are listed in [SECURITY.md](SECURITY.md).
251
+
252
+ ## Stability and maintenance
253
+
254
+ * Pre-1.0 SemVer: PATCH releases only fix, MINOR releases may change the
255
+ public API with the migration named in [CHANGELOG.md](CHANGELOG.md).
256
+ Nothing changes silently.
257
+ * Deprecated APIs keep working and warn for at least one MINOR release
258
+ before removal.
259
+ * The bar for 1.0, stated in advance: months of green weekly canaries, the
260
+ maker path (`limit_gtc`) production-proven with real volume the way the
261
+ FAK paths already are, and external production users.
262
+ * Bus-factor honesty: one maintainer, who trades real money through this
263
+ exact code daily (strongest available incentive to keep it correct). The
264
+ mitigations are structural, not promises: five small modules, the
265
+ executable fill-contract test table, a weekly canary that opens issues by
266
+ itself, SHA-pinned CI. Operational rule: if the canary badge goes red and
267
+ stays red, treat the project as unmaintained and pin your last known-good
268
+ version.
269
+ * Help wanted, precisely scoped: production receipts for `signature_type`
270
+ 1 and 2 accounts (legacy Magic/email and browser-wallet proxies). Both
271
+ paths are introspection-tested but have never carried real money through
272
+ this library; the maintainer's own accounts are all types 0 and 3.
273
+
222
274
  ## License
223
275
 
224
276
  MIT
@@ -14,14 +14,25 @@
14
14
  matches the API surface pmq was verified against (introspection at startup),
15
15
  rather than signing through changed semantics.
16
16
 
17
- ## Reading the source before trusting it
17
+ ## Small enough to read
18
18
 
19
- A documented wave of fake "polymarket bot" repositories steals private keys.
20
- pmq is deliberately small (five modules) so you can audit the entire execution
21
- path in minutes. Grep targets that settle the important questions fast:
19
+ A documented wave of fake "polymarket bot" repositories steals private keys;
20
+ pmq is deliberately small (five modules) so the entire execution path reads
21
+ in minutes. For whoever wants the fast route, the grep targets that settle
22
+ the important questions:
22
23
  `POLY_PRIVATE_KEY` (read once, passed to the official client), `builder_code`
23
24
  (the disclosure and the opt-out), `http` (every host contacted).
24
25
 
26
+ ## Automated watch
27
+
28
+ * Weekly canary CI runs the egress test (every DNS resolution during a full
29
+ session must stay inside polymarket.com) and `pip-audit` over the
30
+ dependency tree; any failure opens a labeled issue automatically.
31
+ * Dependabot files weekly update PRs for Python dependencies and for the
32
+ GitHub Actions, which are pinned by commit SHA.
33
+ * PyPI releases carry signed PEP 740 attestations (see "Verify the claims
34
+ yourself" in the README).
35
+
25
36
  ## Reporting a vulnerability
26
37
 
27
38
  Open a GitHub security advisory on this repository (Security tab, "Report a