mirror of
https://github.com/jaxperro/winning-wallet-finder.git
synced 2026-07-27 15:57:47 +00:00
476c1059c8
- H3 (burst overflow): per-wallet trade cursor + paginated activity fetch (up to 5×100, walks back to the last processed timestamp) — a gkmg-scale clip burst can no longer scroll past a single newest-100 page. Cursor only advances after a fetch; 600s overlap re-serves boundary rows into the seen-tx dedupe. offset pagination verified against the data-api. - clone-guard: Bearer auth (fine-grained PATs reject the 'token' scheme) with unauthenticated fallback + 40-hex sanity on the answer — Fly's shared egress IPs rate-limit the anonymous path. - preflight_live.py rewritten for the unified SDK: deposit-wallet auth, pUSD collateral, order-book reach, RTDS stream delivery, geo verdict. - ledger-drift ALARM floor 1c → 5c (4dp fill rounding accumulates pennies across 60+ copies; the check stays for real bugs — it caught $15.45 and $7.24. The self-heal path keeps its own threshold.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
130 lines
4.8 KiB
Python
130 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Read-only preflight for live trading — verifies credentials, balance, and
|
|
market access on the UNIFIED SDK (polymarket-client) WITHOUT placing any
|
|
order. (Rewritten 2026-07-13: the original validated the archived
|
|
py-clob-client stack, whose orders the CLOB rejects — gotcha 16.)
|
|
|
|
python3 preflight_live.py # uses config.live.json
|
|
python3 preflight_live.py --config config.live.example.json # Fly path
|
|
|
|
Checks, in order:
|
|
1. config parses; private key present (env LIVE_PRIVATE_KEY wins)
|
|
2. unified-SDK auth: SecureClient.create resolves the Deposit Wallet
|
|
3. pUSD collateral via get_balance_allowance — the balance the exchange
|
|
will actually let the bot trade with (raw USDC reads 0 — gotcha 16)
|
|
4. live order book fetch for a followed wallet's recent market
|
|
5. RTDS trade stream: connect + subscribe + first message (T0 detection)
|
|
6. geo-gate verdict (informational on a blocked box; the bot enforces
|
|
fatally at boot)
|
|
|
|
Exit code 0 = every check passed; anything else prints what to fix.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import ssl
|
|
import sys
|
|
import urllib.request
|
|
|
|
_SSL = ssl._create_unverified_context()
|
|
OK, BAD = " ✓", " ✗"
|
|
failures = []
|
|
|
|
|
|
def check(name, fn):
|
|
try:
|
|
msg = fn()
|
|
print(f"{OK} {name}" + (f" — {msg}" if msg else ""))
|
|
except Exception as e:
|
|
failures.append(name)
|
|
print(f"{BAD} {name} — {type(e).__name__}: {e}")
|
|
|
|
|
|
def get(url):
|
|
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
|
return json.load(urllib.request.urlopen(req, timeout=15, context=_SSL))
|
|
|
|
|
|
def main():
|
|
path = "config.live.json"
|
|
if "--config" in sys.argv:
|
|
path = sys.argv[sys.argv.index("--config") + 1]
|
|
cfg = json.load(open(path))
|
|
live = cfg.setdefault("live", {})
|
|
if (os.environ.get("LIVE_PRIVATE_KEY") or "").strip():
|
|
live["private_key"] = os.environ["LIVE_PRIVATE_KEY"].strip()
|
|
pk = live.get("private_key")
|
|
if not pk:
|
|
sys.exit(f"{BAD} no live.private_key in {path} and no LIVE_PRIVATE_KEY env")
|
|
print(f"{OK} config parses — key present, pct {cfg.get('bankroll_pct')}, "
|
|
f"band [{cfg['risk'].get('min_price')},{cfg['risk'].get('max_price')}]")
|
|
|
|
from polymarket import SecureClient
|
|
client = SecureClient.create(private_key=pk)
|
|
|
|
def auth():
|
|
return f"deposit wallet {client.wallet}"
|
|
check("unified-SDK auth (SecureClient.create)", auth)
|
|
|
|
def collateral():
|
|
b = client.get_balance_allowance(asset_type="COLLATERAL")
|
|
usd = b.balance / 1e6
|
|
if usd <= 0:
|
|
raise RuntimeError("exchange-view collateral is $0 — bankroll "
|
|
"not wrapped to pUSD? (gotcha 16)")
|
|
return f"${usd:,.2f} pUSD, {len(b.allowances)} spender allowances"
|
|
check("exchange-view collateral (pUSD)", collateral)
|
|
|
|
def book():
|
|
for w in [x["wallet"] for x in cfg.get("wallets", [])][:3]:
|
|
for t in get(f"https://data-api.polymarket.com/activity?user={w}"
|
|
f"&type=TRADE&limit=5"):
|
|
tok = t.get("asset")
|
|
if not tok:
|
|
continue
|
|
ob = get(f"https://clob.polymarket.com/book?token_id={tok}")
|
|
bids, asks = ob.get("bids") or [], ob.get("asks") or []
|
|
return (f"{len(bids)} bids / {len(asks)} asks on "
|
|
f"{(t.get('title') or '?')[:40]}")
|
|
raise RuntimeError("no recent market found across the follow set")
|
|
check("order-book access (followed wallet's market)", book)
|
|
|
|
def rtds():
|
|
import threading
|
|
import websocket
|
|
got = []
|
|
|
|
def on_open(ws):
|
|
ws.send(json.dumps({"action": "subscribe", "subscriptions": [
|
|
{"topic": "activity", "type": "trades", "filters": ""}]}))
|
|
|
|
def on_message(ws, raw):
|
|
got.append(raw)
|
|
ws.close()
|
|
app = websocket.WebSocketApp("wss://ws-live-data.polymarket.com",
|
|
on_open=on_open, on_message=on_message)
|
|
t = threading.Thread(target=lambda: app.run_forever(
|
|
sslopt={"cert_reqs": ssl.CERT_NONE}), daemon=True)
|
|
t.start()
|
|
t.join(timeout=15)
|
|
if not got:
|
|
raise RuntimeError("no message within 15s")
|
|
return "stream delivers (T0 detection reachable)"
|
|
check("RTDS trade stream", rtds)
|
|
|
|
def geo():
|
|
r = get("https://polymarket.com/api/geoblock")
|
|
return ("TRADABLE from here" if not r.get("blocked")
|
|
else f"BLOCKED here ({r.get('country')}) — bot must run on "
|
|
"the Fly box (informational)")
|
|
check("geo-gate", geo)
|
|
|
|
client.close()
|
|
if failures:
|
|
sys.exit(f"\n{len(failures)} check(s) failed: {', '.join(failures)}")
|
|
print("\nall checks passed — safe to arm")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|