polymach 0.1.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.
@@ -0,0 +1,875 @@
1
+ # PolyMach Documentation
2
+
3
+ This file is the single-file operational reference for the public `polymach` SDK.
4
+
5
+ It is written for agentic ingestion:
6
+
7
+ - it focuses on the actual public API surface
8
+ - it describes the startup and payment state machine
9
+ - it includes concrete examples
10
+ - it distinguishes payment-wallet concerns from trading-wallet concerns
11
+
12
+ ## Purpose
13
+
14
+ `polymach` is the public Python SDK for the private PolyMach execution engine on Polymarket.
15
+
16
+ What happens at runtime:
17
+
18
+ 1. the Python package is installed with `uv add polymach`
19
+ 2. on first use, the SDK resolves or downloads the correct signed runtime binary
20
+ 3. the SDK verifies the signed release manifest and artifact hash
21
+ 4. the runtime is launched locally
22
+ 5. the SDK checks for a machine-bound license
23
+ 6. if needed, the SDK returns payment instructions or submits the payment directly
24
+ 7. after license activation, the runtime is used for low-latency discovery, quotes, streaming, and trading
25
+
26
+ Important payment model:
27
+
28
+ - the SDK returns the exact payment instructions for the current machine when payment is needed
29
+ - the user or agent supplies the payer wallet or signer if payment is automated
30
+
31
+ The SDK is public. The Rust engine source is not in this repo.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ uv add polymach
37
+ ```
38
+
39
+ Default runtime bootstrap URL:
40
+
41
+ ```text
42
+ https://github.com/polymach-dev/build/releases/latest/download/index.json
43
+ ```
44
+
45
+ In the normal case, users do not need a local Rust toolchain or a local checkout of the private engine repo.
46
+
47
+ ## Supported Machines And Builds
48
+
49
+ The SDK bootstrap selects a runtime artifact from the signed release catalog based on:
50
+
51
+ - host platform
52
+ - host architecture
53
+ - Linux libc family
54
+ - x86_64 CPU level
55
+
56
+ PolyMach ships native runtime builds for each supported target class. The release process produces target-specific artifacts instead of relying on one generic cross-platform binary.
57
+
58
+ Current release targets:
59
+
60
+ - Linux `x86_64` glibc `v2`
61
+ Artifact id: `x86_64-unknown-linux-gnu-v2`
62
+ - Linux `x86_64` glibc `v3`
63
+ Artifact id: `x86_64-unknown-linux-gnu-v3`
64
+ - Linux `x86_64` glibc `v4`
65
+ Artifact id: `x86_64-unknown-linux-gnu-v4`
66
+ - Linux `aarch64` glibc generic ARM64
67
+ Artifact id: `aarch64-unknown-linux-gnu`
68
+ - macOS `aarch64` native Apple Silicon
69
+ Artifact id: `aarch64-apple-darwin`
70
+
71
+ Why there are multiple Linux `x86_64` artifacts:
72
+
73
+ - `v2` is the broad baseline build
74
+ - `v3` is compiled for AVX2-class machines
75
+ - `v4` is compiled for AVX-512-capable machines
76
+ - the SDK chooses the fastest compatible build for the current host instead of forcing every machine onto the same binary
77
+
78
+ Selection behavior:
79
+
80
+ - `v4` hosts prefer `v4`
81
+ - `v3` hosts prefer `v3`
82
+ - `v2` hosts use `v2`
83
+ - the SDK will not select an artifact that requires a higher CPU level than the current machine
84
+ - Linux `aarch64` hosts use `aarch64-unknown-linux-gnu`
85
+ - macOS Apple Silicon hosts use `aarch64-apple-darwin`
86
+
87
+ Operationally, that means the public release channel is intended to contain individually built native artifacts for:
88
+
89
+ - modern Linux x86_64 fleets at multiple CPU tiers
90
+ - Linux ARM64 systems such as Graviton
91
+ - Apple Silicon macOS systems
92
+
93
+ If you need to pin a specific published build instead of letting the SDK choose automatically, set:
94
+
95
+ - `POLYMACH_ARTIFACT_ID`
96
+
97
+ Example:
98
+
99
+ ```bash
100
+ export POLYMACH_ARTIFACT_ID=x86_64-unknown-linux-gnu-v2
101
+ ```
102
+
103
+ If you run on a target outside the published release set, provide a compatible local runtime binary with `POLYMACH_BIN`.
104
+
105
+ ## Primary Entry Point
106
+
107
+ The main entry point is `PolyMach`.
108
+
109
+ ```python
110
+ from polymach import PolyMach
111
+
112
+ engine = PolyMach()
113
+ ```
114
+
115
+ Constructor options:
116
+
117
+ - `binary`: optional explicit path to a local `polymach` runtime binary
118
+ - `license_path`: optional explicit path to the local machine license file
119
+ - `env`: optional mapping of environment variables used by the runtime and SDK
120
+ - `persistent`: optional override for persistent-session behavior
121
+
122
+ The instance exposes these domain clients:
123
+
124
+ - `engine.system`
125
+ - `engine.session`
126
+ - `engine.license`
127
+ - `engine.account`
128
+ - `engine.discover`
129
+ - `engine.market`
130
+ - `engine.orders`
131
+ - `engine.trades`
132
+
133
+ It also exposes convenience wrappers around the most important flows.
134
+
135
+ ## Public State Machine
136
+
137
+ The important startup states are:
138
+
139
+ 1. runtime missing
140
+ 2. runtime available but no valid local license
141
+ 3. payment required
142
+ 4. payment submitted and waiting for confirmation
143
+ 5. license activated
144
+ 6. runtime ready for trading
145
+
146
+ The SDK presents that state machine through `start()`, `ensure_ready()`, `pay()`, and `pay_and_start()`.
147
+
148
+ ### `start()`
149
+
150
+ `start()` is the recommended first-run flow.
151
+
152
+ Behavior:
153
+
154
+ - if a valid local license already exists, it returns `StartResult(ready=True, ...)`
155
+ - if no valid license exists and no `tx_hash` is provided, it returns `StartResult(ready=False, payment=...)`
156
+ - if `tx_hash` is provided, it activates the machine license and returns `StartResult(ready=True, activation=...)`
157
+
158
+ ### `ensure_ready()`
159
+
160
+ `ensure_ready()` is the same as `start()`, except it raises `PaymentRequiredError` instead of returning `ready=False`.
161
+
162
+ Use it when your agent wants exception-driven control flow.
163
+
164
+ ### `pay()`
165
+
166
+ `pay()` fetches a machine-specific payment request and submits the on-chain Polygon payment.
167
+
168
+ It returns `SubmittedPayment`.
169
+
170
+ ### `pay_and_start()`
171
+
172
+ `pay_and_start()` is the one-shot path.
173
+
174
+ Behavior:
175
+
176
+ - if a valid license already exists, it starts normally
177
+ - if `tx_hash` is provided, it resumes activation without submitting a new payment
178
+ - otherwise it submits the payment, activates the license, and optionally prewarms the runtime
179
+
180
+ This is the shortest path for fully automated agents.
181
+
182
+ ## Core Startup Flows
183
+
184
+ ## Flow 1: Manual Payment Then Activate
185
+
186
+ This is the safest and most explicit first-run path.
187
+
188
+ ```python
189
+ from polymach import PolyMach
190
+
191
+ engine = PolyMach()
192
+
193
+ start = engine.start()
194
+ if not start.ready:
195
+ payment = start.payment
196
+ print(payment.amount_display)
197
+ print(payment.token_symbol)
198
+ print(payment.token_address)
199
+ print(payment.chain_id)
200
+ raise SystemExit("Send that exact Polygon payment, then call start(tx_hash=...)")
201
+
202
+ start = engine.start(tx_hash="0xYOUR_PAYMENT_TX_HASH")
203
+ print(start.ready)
204
+ print(start.license_status.valid)
205
+ print(start.activation.tx_hash if start.activation else None)
206
+ ```
207
+
208
+ What to inspect when `ready=False`:
209
+
210
+ - `start.payment.amount_display`
211
+ - `start.payment.token_symbol`
212
+ - `start.payment.token_address`
213
+ - `start.payment.chain_id`
214
+ - `start.payment.min_confirmations`
215
+
216
+ Important:
217
+
218
+ - the payment request is machine-specific
219
+ - do not reuse a payment request from another machine
220
+ - follow the exact payment instructions returned by the SDK
221
+
222
+ ## Flow 2: Exception-Driven Manual Payment
223
+
224
+ ```python
225
+ from polymach import PaymentRequiredError, PolyMach
226
+
227
+ engine = PolyMach()
228
+
229
+ try:
230
+ engine.ensure_ready()
231
+ except PaymentRequiredError as exc:
232
+ payment = exc.payment
233
+ print(exc.message)
234
+ print(payment.amount_display, payment.token_symbol)
235
+ ```
236
+
237
+ This is useful when your orchestrator already handles retries and exceptions centrally.
238
+
239
+ ## Flow 3: Automated Payment
240
+
241
+ `pay()` submits the Polygon payment directly.
242
+
243
+ ```python
244
+ from polymach import LocalAccountSigner, PolyMach
245
+
246
+ engine = PolyMach()
247
+ signer = LocalAccountSigner.from_private_key("0xYOUR_PAYMENT_WALLET_PRIVATE_KEY")
248
+
249
+ submitted = engine.pay(
250
+ signer=signer,
251
+ cycles=1,
252
+ )
253
+
254
+ print(submitted.tx_hash)
255
+ print(submitted.sender_address)
256
+ print(submitted.amount_display)
257
+ print(submitted.confirmations)
258
+ ```
259
+
260
+ The payment wallet must hold:
261
+
262
+ - the required payment token on Polygon
263
+ - a small amount of POL for gas
264
+
265
+ ## Flow 4: Automated Payment And Start
266
+
267
+ ```python
268
+ from polymach import LocalAccountSigner, PaymentPendingError, PolyMach
269
+
270
+ engine = PolyMach()
271
+ signer = LocalAccountSigner.from_private_key("0xYOUR_PAYMENT_WALLET_PRIVATE_KEY")
272
+
273
+ try:
274
+ start = engine.pay_and_start(
275
+ signer=signer,
276
+ prewarm_tokens=["TOKEN_ID"],
277
+ )
278
+ except PaymentPendingError as exc:
279
+ start = engine.start(tx_hash=exc.tx_hash)
280
+
281
+ print(start.ready)
282
+ print(start.license_status.valid)
283
+ ```
284
+
285
+ This is the preferred path for agents that are allowed to control the payment wallet directly.
286
+
287
+ ## Flow 5: Resume After A Broadcast But Slow Confirmation
288
+
289
+ If the payment transaction is broadcast but confirmation polling times out, the SDK raises `PaymentPendingError`.
290
+
291
+ Useful fields:
292
+
293
+ - `exc.tx_hash`
294
+ - `exc.confirmations`
295
+ - `exc.required_confirmations`
296
+ - `exc.attempted_tx_hashes`
297
+
298
+ Recovery path:
299
+
300
+ ```python
301
+ from polymach import LocalAccountSigner, PaymentPendingError, PolyMach
302
+
303
+ engine = PolyMach()
304
+ signer = LocalAccountSigner.from_private_key("0xYOUR_PAYMENT_WALLET_PRIVATE_KEY")
305
+
306
+ try:
307
+ start = engine.pay_and_start(signer=signer)
308
+ except PaymentPendingError as exc:
309
+ # wait, monitor, or persist the tx hash externally
310
+ start = engine.pay_and_start(tx_hash=exc.tx_hash)
311
+
312
+ print(start.ready)
313
+ ```
314
+
315
+ ## Cold Start, Warm Start, And Bootstrap
316
+
317
+ On `PolyMach()` creation, the runtime is not necessarily started yet.
318
+
319
+ The first meaningful command will:
320
+
321
+ - look for an explicitly configured binary
322
+ - look for package, repo, or `PATH` binaries unless those paths are disabled
323
+ - if no local binary is suitable, download the correct artifact from the signed release catalog
324
+ - cache the runtime locally
325
+
326
+ Relevant bootstrap environment variables:
327
+
328
+ - `POLYMACH_RELEASE_INDEX_URL`
329
+ - `POLYMACH_RELEASE_BASE_URL`
330
+ - `POLYMACH_RELEASE_PUBLIC_KEY`
331
+ - `POLYMACH_RELEASE_TOKEN`
332
+ - `POLYMACH_ARTIFACT_ID`
333
+ - `POLYMACH_BIN`
334
+ - `POLYMACH_CACHE_DIR`
335
+
336
+ Most users do not need to set any of these.
337
+
338
+ ## Prewarming And Session Management
339
+
340
+ The runtime can prewarm token-specific state for lower latency.
341
+
342
+ ```python
343
+ from polymach import PolyMach
344
+
345
+ engine = PolyMach()
346
+ engine.ensure_ready(tx_hash="0xYOUR_PAYMENT_TX_HASH")
347
+
348
+ prewarm = engine.prewarm(
349
+ ["TOKEN_ID_1", "TOKEN_ID_2"],
350
+ quotes=True,
351
+ order_metadata=True,
352
+ last_trade=True,
353
+ )
354
+
355
+ print(prewarm)
356
+ print(engine.session_status())
357
+ ```
358
+
359
+ Why prewarm:
360
+
361
+ - hot quote path
362
+ - hot midpoint path
363
+ - cached order metadata
364
+ - lower latency before the first live order
365
+
366
+ If your agent targets a small set of active markets, prewarm them before entering the main trading loop.
367
+
368
+ ## Market Data
369
+
370
+ ### Midpoint
371
+
372
+ ```python
373
+ mid = engine.midpoint("TOKEN_ID")
374
+ print(mid.token_id, mid.midpoint)
375
+ ```
376
+
377
+ Returned type:
378
+
379
+ - `MidpointResponse`
380
+
381
+ Fields:
382
+
383
+ - `token_id`
384
+ - `midpoint`
385
+
386
+ ### Quote
387
+
388
+ ```python
389
+ quote = engine.quote("TOKEN_ID")
390
+ print(quote.midpoint, quote.bid, quote.ask)
391
+ ```
392
+
393
+ Returned type:
394
+
395
+ - `MarketQuote`
396
+
397
+ Fields:
398
+
399
+ - `midpoint`
400
+ - `bid`
401
+ - `ask`
402
+
403
+ ### Streaming Quotes
404
+
405
+ ```python
406
+ for snapshot in engine.stream_quotes(
407
+ ["TOKEN_ID"],
408
+ interval_ms=100,
409
+ include_last_trade=True,
410
+ count=5,
411
+ ):
412
+ print(snapshot.timestamp)
413
+ for q in snapshot.quotes:
414
+ print(q.token_id, q.midpoint, q.bid, q.ask, q.last_trade)
415
+ ```
416
+
417
+ Returned stream item type:
418
+
419
+ - `QuoteStreamSnapshot`
420
+
421
+ ## Discovery
422
+
423
+ ### Search Markets
424
+
425
+ ```python
426
+ markets = engine.discover_markets(
427
+ query="btc 5m",
428
+ limit=10,
429
+ with_live=True,
430
+ )
431
+
432
+ for market in markets:
433
+ print(market.slug, market.question)
434
+ ```
435
+
436
+ ### Get A Single Market
437
+
438
+ ```python
439
+ market = engine.discover_market(slug="btc-updown-5m-...")
440
+ print(market.slug)
441
+ print(market.question)
442
+ for outcome in market.outcomes:
443
+ print(outcome.outcome, outcome.token_id)
444
+ ```
445
+
446
+ ### Search Across Entities
447
+
448
+ ```python
449
+ results = engine.search(
450
+ "btc",
451
+ limit=10,
452
+ include_profiles=False,
453
+ with_live=True,
454
+ )
455
+
456
+ print(len(results.markets), len(results.events))
457
+ ```
458
+
459
+ Relevant return types:
460
+
461
+ - `DiscoveryMarket`
462
+ - `DiscoveryEvent`
463
+ - `DiscoverySearchResults`
464
+
465
+ Use `with_live=True` when you want live quote fields embedded into discovery results.
466
+
467
+ ## Account And Portfolio
468
+
469
+ ### Healthcheck
470
+
471
+ ```python
472
+ health = engine.healthcheck(token_id="TOKEN_ID")
473
+ print(health.collateral_balance_usdc)
474
+ print(health.token_balance)
475
+ print(health.midpoint)
476
+ print(health.bid, health.ask)
477
+ ```
478
+
479
+ This is the best compact account-state call when you want balances plus current market context.
480
+
481
+ ### Portfolio
482
+
483
+ ```python
484
+ portfolio = engine.portfolio("TOKEN_ID")
485
+ print(portfolio.token_id)
486
+ print(portfolio.position_qty)
487
+ print(portfolio.avg_entry_price)
488
+ ```
489
+
490
+ ## Orders
491
+
492
+ Live order methods require the live-trading environment to be configured.
493
+
494
+ ### Limit Order
495
+
496
+ ```python
497
+ receipt = engine.post_limit(
498
+ "buy",
499
+ "TOKEN_ID",
500
+ 5.0,
501
+ 0.01,
502
+ order_type="GTC",
503
+ )
504
+
505
+ print(receipt.order_id)
506
+ print(receipt.qty, receipt.limit_price)
507
+ ```
508
+
509
+ Returned type:
510
+
511
+ - `LimitOrderReceipt`
512
+
513
+ ### Timed Limit Order
514
+
515
+ ```python
516
+ timed = engine.post_limit(
517
+ "buy",
518
+ "TOKEN_ID",
519
+ 5.0,
520
+ 0.01,
521
+ order_type="GTC",
522
+ timed=True,
523
+ )
524
+
525
+ print(timed.receipt.order_id)
526
+ print(timed.timing.total_seconds)
527
+ print(timed.timing.http_seconds)
528
+ ```
529
+
530
+ Returned type:
531
+
532
+ - `TimedLimitResponse`
533
+
534
+ Use `timed=True` when the agent wants latency breakdowns for submissions.
535
+
536
+ ### Market Buy
537
+
538
+ ```python
539
+ fill = engine.market_buy("TOKEN_ID", 25.0)
540
+ print(fill.order_id, fill.qty, fill.fill_price)
541
+ ```
542
+
543
+ ### Market Sell
544
+
545
+ ```python
546
+ fill = engine.market_sell("TOKEN_ID", 5.0)
547
+ print(fill.order_id, fill.qty, fill.fill_price)
548
+ ```
549
+
550
+ ### Inspect And Cancel Orders
551
+
552
+ ```python
553
+ orders = engine.list_orders()
554
+ for order in orders:
555
+ print(order.order_id, order.status, order.remaining_size)
556
+
557
+ if orders:
558
+ result = engine.cancel_order(orders[0].order_id)
559
+ print(result.order_id, result.canceled)
560
+ ```
561
+
562
+ Also available:
563
+
564
+ - `engine.get_order(order_id)`
565
+ - `engine.cancel_open_orders(token_id)`
566
+ - `engine.cancel_all_open_orders()`
567
+
568
+ ## Trades
569
+
570
+ ```python
571
+ page = engine.list_trades()
572
+ for trade in page.trades:
573
+ print(trade.trade_id, trade.side, trade.price, trade.size)
574
+ ```
575
+
576
+ You can also fetch a single trade:
577
+
578
+ ```python
579
+ trade = engine.get_trade("TRADE_ID")
580
+ print(trade)
581
+ ```
582
+
583
+ ## License And Machine Identity
584
+
585
+ ### Machine Fingerprint
586
+
587
+ ```python
588
+ fingerprint = engine.fingerprint()
589
+ print(fingerprint.fingerprint)
590
+ ```
591
+
592
+ Use this if you need to reason explicitly about machine-bound licensing.
593
+
594
+ ### License Status
595
+
596
+ ```python
597
+ status = engine.license_status()
598
+ print(status.valid, status.message)
599
+ ```
600
+
601
+ ### Verify The Installed License
602
+
603
+ ```python
604
+ claims = engine.verify_license()
605
+ print(claims.license_id)
606
+ print(claims.machine_fingerprint)
607
+ print(claims.expires_at)
608
+ ```
609
+
610
+ ### Install An Existing License File
611
+
612
+ ```python
613
+ installed_path = engine.install_license("/path/to/license.json")
614
+ print(installed_path)
615
+ ```
616
+
617
+ ## Pricing And Payment Requests
618
+
619
+ ### Read Catalog Pricing
620
+
621
+ ```python
622
+ catalog = engine.pricing()
623
+ print(catalog.cycle_price_usdc)
624
+ print(catalog.token_address)
625
+ print(catalog.min_confirmations)
626
+ ```
627
+
628
+ ### Get The Exact Machine-Specific Payment Request
629
+
630
+ ```python
631
+ payment = engine.payment_request(cycles=2)
632
+ print(payment.machine_fingerprint)
633
+ print(payment.amount_display)
634
+ print(payment.amount_raw)
635
+ ```
636
+
637
+ This is the object that should drive manual or automated payment execution.
638
+
639
+ ## Live Trading Environment
640
+
641
+ The payment wallet and the trading wallet are different concerns.
642
+
643
+ Venue availability is a separate concern from SDK install, runtime bootstrap,
644
+ payment, and license activation. Those steps can succeed while Polymarket still
645
+ refuses live trading from the current IP address, network, or jurisdiction.
646
+ Before depending on live order flow, confirm venue access from the actual host
647
+ that will run the strategy.
648
+
649
+ ### Payment Wallet
650
+
651
+ Used by:
652
+
653
+ - `pay()`
654
+ - `pay_and_start()` when the SDK submits the payment
655
+
656
+ Relevant inputs:
657
+
658
+ - `signer=LocalAccountSigner.from_private_key(...)`
659
+ - `private_key=...`
660
+ - `POLYMACH_WALLET_PRIVATE_KEY` as a fallback
661
+
662
+ ### Trading Wallet
663
+
664
+ Used by the runtime for actual Polymarket trading.
665
+
666
+ Relevant environment variables:
667
+
668
+ - `POLYMACH_LIVE_ENABLED=true`
669
+ - `POLYMACH_LIVE_ACK=I_UNDERSTAND_REAL_MONEY_RISK`
670
+ - `POLYMACH_POLYMARKET_PRIVATE_KEY=0x...`
671
+ - `POLYMACH_POLYMARKET_SIGNATURE_TYPE=0`
672
+
673
+ `POLYMACH_POLYMARKET_SIGNATURE_TYPE` values:
674
+
675
+ - `0` = EOA
676
+ - `1` = proxy
677
+ - `2` = Gnosis Safe
678
+
679
+ If the signer and funded trading address differ, also set:
680
+
681
+ - `POLYMACH_POLYMARKET_FUNDER`
682
+
683
+ If you use Polymarket API credentials, set all three:
684
+
685
+ - `POLYMACH_POLYMARKET_API_KEY`
686
+ - `POLYMACH_POLYMARKET_API_SECRET`
687
+ - `POLYMACH_POLYMARKET_API_PASSPHRASE`
688
+
689
+ Minimal live-trading shell setup:
690
+
691
+ ```bash
692
+ export POLYMACH_LIVE_ENABLED=true
693
+ export POLYMACH_LIVE_ACK=I_UNDERSTAND_REAL_MONEY_RISK
694
+ export POLYMACH_POLYMARKET_PRIVATE_KEY=0xYOUR_TRADING_WALLET_PRIVATE_KEY
695
+ export POLYMACH_POLYMARKET_SIGNATURE_TYPE=0
696
+ ```
697
+
698
+ ## Payment Configuration
699
+
700
+ Relevant payment environment variables:
701
+
702
+ - `POLYMACH_LICENSE_BASE_URL`
703
+ - `POLYMACH_LICENSE_TOKEN`
704
+ - `POLYMACH_POLYGON_RPC_URL`
705
+ - `POLYMACH_POLYGON_RPC_URLS`
706
+ - `POLYMACH_WALLET_PRIVATE_KEY`
707
+ - `POLYMACH_PAYMENT_GAS_LIMIT`
708
+ - `POLYMACH_PAYMENT_GAS_PRICE_WEI`
709
+ - `POLYMACH_PAYMENT_REPLACEMENT_ATTEMPTS`
710
+ - `POLYMACH_PAYMENT_REPLACEMENT_GAS_BUMP_PERCENT`
711
+
712
+ Default Polygon RPC fallback order:
713
+
714
+ ```text
715
+ https://polygon.publicnode.com
716
+ https://1rpc.io/matic
717
+ https://polygon-rpc.com
718
+ ```
719
+
720
+ ### Automated Payment With Replacement Controls
721
+
722
+ ```python
723
+ from polymach import LocalAccountSigner, PolyMach
724
+
725
+ engine = PolyMach()
726
+ signer = LocalAccountSigner.from_private_key("0xYOUR_PAYMENT_WALLET_PRIVATE_KEY")
727
+
728
+ submitted = engine.pay(
729
+ signer=signer,
730
+ replacement_attempts=2,
731
+ replacement_gas_bump_percent=25,
732
+ timeout_seconds=180.0,
733
+ )
734
+
735
+ print(submitted.tx_hash)
736
+ ```
737
+
738
+ Use this if you want limited same-nonce replacement attempts for pending payment transactions.
739
+
740
+ ## Recommended Agent Recipes
741
+
742
+ ## Recipe 1: Minimal Install Then Manual Payment
743
+
744
+ ```python
745
+ from polymach import PolyMach
746
+
747
+ engine = PolyMach()
748
+ result = engine.start()
749
+
750
+ if result.ready:
751
+ print("ready")
752
+ else:
753
+ print(result.payment.token_address)
754
+ print(result.payment.amount_display)
755
+ ```
756
+
757
+ ## Recipe 2: Automated Agent Startup
758
+
759
+ ```python
760
+ from polymach import LocalAccountSigner, PaymentPendingError, PolyMach
761
+
762
+ engine = PolyMach()
763
+ payment_signer = LocalAccountSigner.from_private_key("0xPAYMENT_WALLET_KEY")
764
+
765
+ try:
766
+ start = engine.pay_and_start(
767
+ signer=payment_signer,
768
+ prewarm_tokens=["TOKEN_ID"],
769
+ prewarm_last_trade=True,
770
+ )
771
+ except PaymentPendingError as exc:
772
+ start = engine.start(tx_hash=exc.tx_hash)
773
+
774
+ quote = engine.quote("TOKEN_ID")
775
+ print(quote.midpoint)
776
+ ```
777
+
778
+ ## Recipe 3: Discover Then Trade
779
+
780
+ ```python
781
+ from polymach import PolyMach
782
+
783
+ engine = PolyMach()
784
+ engine.ensure_ready(tx_hash="0xYOUR_PAYMENT_TX_HASH")
785
+
786
+ market = engine.discover_market(slug="btc-updown-5m-...")
787
+ token_id = market.outcomes[0].token_id
788
+
789
+ engine.prewarm([token_id], last_trade=True)
790
+ quote = engine.quote(token_id)
791
+
792
+ if quote.ask is not None and quote.ask <= 0.10:
793
+ receipt = engine.post_limit("buy", token_id, 5.0, 0.10)
794
+ print(receipt.order_id)
795
+ ```
796
+
797
+ ## Recipe 4: Measure Order Submission Time
798
+
799
+ ```python
800
+ timed = engine.post_limit(
801
+ "buy",
802
+ "TOKEN_ID",
803
+ 5.0,
804
+ 0.01,
805
+ timed=True,
806
+ )
807
+
808
+ print(timed.timing.total_seconds)
809
+ print(timed.timing.http_seconds)
810
+ print(timed.timing.build_sign_seconds)
811
+ ```
812
+
813
+ ## Common Errors
814
+
815
+ ### `PaymentRequiredError`
816
+
817
+ Raised by:
818
+
819
+ - `ensure_ready()`
820
+
821
+ Meaning:
822
+
823
+ - no valid local license exists yet
824
+ - payment instructions are attached on `.payment`
825
+
826
+ ### `PaymentPendingError`
827
+
828
+ Raised by:
829
+
830
+ - `pay()`
831
+ - `pay_and_start()`
832
+
833
+ Meaning:
834
+
835
+ - a payment transaction was broadcast or attempted
836
+ - confirmation polling did not finish cleanly before timeout
837
+ - the caller should inspect `.tx_hash` and possibly `.attempted_tx_hashes`
838
+
839
+ ### `BinaryNotFoundError`
840
+
841
+ Meaning:
842
+
843
+ - the SDK could not find a usable local runtime binary
844
+ - and auto-download was disabled or failed before a valid binary was installed
845
+
846
+ ### `CommandError`
847
+
848
+ Meaning:
849
+
850
+ - the local runtime command failed
851
+
852
+ Useful fields:
853
+
854
+ - `.command`
855
+ - `.exit_code`
856
+ - `.stdout`
857
+ - `.stderr`
858
+
859
+ ### `PolyMachError`
860
+
861
+ Base runtime error class for SDK-level failures.
862
+
863
+ ## Security And Operational Notes
864
+
865
+ - Prefer a dedicated low-balance payment wallet for automated subscription payments.
866
+ - Keep payment-wallet and trading-wallet responsibilities separate unless you have a reason not to.
867
+ - Keep private keys out of shell history, logs, prompts, and chat transcripts.
868
+ - Prewarm active token ids before latency-sensitive trading.
869
+ - Persist `PaymentPendingError.tx_hash` externally if your agent needs reliable restart recovery.
870
+ - Use `timed=True` on limit orders when you want latency telemetry for benchmarking or control logic.
871
+
872
+ ## Legal
873
+
874
+ - MIT license: [`LICENSE`](LICENSE)
875
+ - legal disclaimer: [`LEGAL.md`](LEGAL.md)