From ee4e1750e651da9a6d888df9f3655a7e571e2c70 Mon Sep 17 00:00:00 2001 From: jaxperro Date: Thu, 2 Jul 2026 11:18:42 -0400 Subject: [PATCH] live pipeline test kit: preflight checker, runbook, tiny-cap config template preflight_live.py verifies key/auth, USDC balance, market access, and redeem gas read-only (no orders). LIVE_TEST.md is the supervised $5-trade runbook. config.live.json + copybot_state.live.json gitignored (key never committed). Co-Authored-By: Claude Fable 5 --- .gitignore | 2 + LIVE_TEST.md | 70 ++++++++++++++++++++++++++++++ preflight_live.py | 108 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 LIVE_TEST.md create mode 100644 preflight_live.py diff --git a/.gitignore b/.gitignore index 8273636d..1f39ba57 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ __pycache__/ # never commit live credentials or runtime state config.json +config.live.json +copybot_state.live.json config.json.bak config.example.json copytrade_state.json diff --git a/LIVE_TEST.md b/LIVE_TEST.md new file mode 100644 index 00000000..95b09c17 --- /dev/null +++ b/LIVE_TEST.md @@ -0,0 +1,70 @@ +# Live pipeline test — minimum-size real-money runbook + +Goal: a few **$5 trades** through the full pipe (detect → filter → size → place → +settle → redeem) with hard caps bounding worst-case loss to the deposit. + +Caps in `config.live.json` (gitignored — holds your key, never commit): +**$5/trade · $25/day · $30 max exposure · 6 open positions · 2 per event · +prices 0.05–0.95 · FAK marketable orders.** Worst case ≈ the $30 exposure cap. + +## 1. Fund a dedicated account (you) + +- Use (or create) a Polymarket account you're comfortable putting a key on this + machine for — **deposit only the test money (~$50)**. The key controls only + what's in the account; keep it that way. +- Deposit USDC to the account (the profile/deposit address = your **funder**). +- Send **~1 POL** to the signing EOA for redeem gas (skip if you set + `live.auto_redeem: false` and redeem manually in the UI). +- Export the **private key**: email-login accounts → Polymarket Settings → + Export private key (`signature_type: 1`, already set). Browser-wallet + accounts → your wallet's key, and set `signature_type: 2`. + +## 2. Fill `config.live.json` (you) + +```jsonc +"live": { + "private_key": "0x…", // the exported key + "funder_address": "0x…", // your Polymarket profile/deposit address + "signature_type": 1 // 1 = email login · 2 = browser wallet +} +``` + +## 3. Preflight (read-only, no orders) + + python3 preflight_live.py + +Green across the board = auth, USDC balance, market access, and redeem gas all +verified. Fix anything red before arming. + +## 4. Pause the paper worker (avoids the two bots fighting over the feed) + + cd ~/…/scratchpad/railway-copybot && railway down --service copybot -y + # …or pause the service in the Railway dashboard + +## 5. Arm (you type the phrase — that's the point of it) + + python3 copybot.py --config config.live.json \ + --state copybot_state.live.json --poll 60 --live + +It prints the caps, asks for the confirmation phrase, then baselines (no +retro-copying) and waits for the next conviction trade from Kruto2027 / +shisan888 / fortuneking / LSB1. Every placement pings Discord. Watch the first +fill: order accepted → shares in the account (Polymarket UI) → on resolution, +SETTLE line + auto-redeem tx hash. + +## 6. Wind down + +Ctrl-C the live bot, then redeploy the paper worker: + + cd ~/…/scratchpad/railway-copybot && railway up --service copybot --detach + +## Notes + +- **Venue**: the bot trades the international CLOB (`clob.polymarket.com`). + If your account lives on the US exchange, its API differs — confirm which + venue your funds are on before the test. +- **Neg-risk markets** can't auto-redeem — the bot warns; redeem those in the UI. +- The live book/state is separate (`copybot_state.live.json`); the paper July + test's state is untouched. +- First `redeem.py` run is untested against live — verify the first redemption + tx on Polygonscan before trusting the loop. diff --git a/preflight_live.py b/preflight_live.py new file mode 100644 index 00000000..d2e1ed2f --- /dev/null +++ b/preflight_live.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Read-only preflight for live trading — verifies credentials, balance, and +market access WITHOUT placing any order. + + python3 preflight_live.py # uses config.live.json + +Checks, in order: + 1. config.live.json parses; private key + funder present + 2. CLOB auth: derive L2 API creds from the key (proves the key signs) + 3. USDC balance + allowance on the funder (proves deposits landed and the + proxy can spend — the balance the bot will actually trade with) + 4. live order book fetch for one of the followed wallets' recent markets + (proves market-data access end to end) + 5. Polygon RPC + EOA POL gas balance (needed by auto-redeem) + +Exit code 0 = every check passed; anything else prints what to fix. +""" + +import json +import sys +import time +import urllib.request +import ssl + +CLOB = "https://clob.polymarket.com" +_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} — {e}") + + +def main(): + cfg = json.load(open("config.live.json")) + live = cfg.get("live", {}) + pk, funder = live.get("private_key"), live.get("funder_address") + if not pk or not funder: + sys.exit("fill live.private_key and live.funder_address in config.live.json first\n" + "(Polymarket profile -> the deposit/profile address is the funder;\n" + " email-login accounts export the key in Settings)") + + from py_clob_client.client import ClobClient + from py_clob_client.clob_types import BalanceAllowanceParams, AssetType + + client = ClobClient(host=CLOB, key=pk, chain_id=137, + signature_type=live.get("signature_type", 1), funder=funder) + + def auth(): + creds = client.create_or_derive_api_creds() + client.set_api_creds(creds) + return f"L2 api key {creds.api_key[:8]}… derived (signer {client.get_address()[:10]}…)" + check("CLOB auth (key signs, creds derive)", auth) + + def balance(): + r = client.get_balance_allowance( + BalanceAllowanceParams(asset_type=AssetType.COLLATERAL)) + bal = int(r.get("balance", 0)) / 1e6 + if bal <= 0: + raise RuntimeError("USDC balance is $0 — deposit test funds to the funder address first") + return f"${bal:,.2f} USDC spendable on {funder[:10]}…" + check("USDC balance on funder", balance) + + def book(): + req = urllib.request.Request( + "https://data-api.polymarket.com/activity?user=" + + cfg["watchlist"][0] + "&type=TRADE&limit=1", + headers={"User-Agent": "Mozilla/5.0"}) + t = json.loads(urllib.request.urlopen(req, timeout=15, context=_SSL).read())[0] + ob = client.get_order_book(t["asset"]) + bid = ob.bids[-1].price if ob.bids else "—" + ask = ob.asks[-1].price if ob.asks else "—" + return f"{(t.get('title') or '')[:40]}… bid {bid} / ask {ask}" + check("order book fetch (market access)", book) + + def gas(): + from web3 import Web3 + rpc = live.get("rpc_url") or ( + f"https://polygon-mainnet.g.alchemy.com/v2/{cfg.get('alchemy_key')}" + if cfg.get("alchemy_key") else None) + if not rpc: + raise RuntimeError("no live.rpc_url and no alchemy_key — auto-redeem needs a Polygon RPC") + w3 = Web3(Web3.HTTPProvider(rpc)) + acct = w3.eth.account.from_key(pk) + pol = w3.eth.get_balance(acct.address) / 1e18 + if pol < 0.05: + raise RuntimeError(f"EOA {acct.address[:10]}… holds {pol:.3f} POL — " + "send ~1 POL for redeem gas (or set live.auto_redeem false)") + return f"{pol:.2f} POL gas on EOA {acct.address[:10]}…" + check("Polygon RPC + redeem gas", gas) + + print() + if failures: + sys.exit(f"NOT ready: fix {len(failures)} item(s) above.") + print("ALL CHECKS PASSED — ready for the supervised live test:") + print(" python3 copybot.py --config config.live.json " + "--state copybot_state.live.json --poll 60 --live") + print(' (it will ask you to type the confirmation phrase before anything is placed)') + + +if __name__ == "__main__": + main()