From 58b89c106a0df0d4ddabc537beba624bc0b0a0d9 Mon Sep 17 00:00:00 2001 From: jaxperro Date: Wed, 8 Jul 2026 23:17:35 -0400 Subject: [PATCH] real-money worker scaffolding (LIVE_ROLLOUT Phase 3 prep) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - start.sh: COPYBOT_ROLE=live — unarmed (missing LIVE_PRIVATE_KEY / LIVE_FUNDER_ADDRESS / LIVE_CONFIRM) idles in geocheck, no clone, no book, no orders; armed runs config.live.example.json + own state file copybot_state.live.json, --live, poll mode (webhook stays on paper). - confirm_live: accepts the phrase from LIVE_CONFIRM env — the USER types it into flyctl secrets set (rule 0.7 human checkpoint, headless); wrong value aborts, unset disarms at next boot. - config.live.example.json: Set E + pinned floors (mirrors paper), bankroll $50 @ 10% = $5 stakes, caps untouched (rule 0.6), leaked Discord webhook URL removed (ROTATE it — was committed publicly). - fly.live.toml: separate app wwf-copybot-live, arn, no inbound, no checks, role env baked. Co-Authored-By: Claude Fable 5 --- config.live.example.json | 81 ++++++++++++++++++++-------------------- copytrade.py | 12 ++++++ fly.live.toml | 20 ++++++++++ host/start.sh | 26 +++++++++++++ 4 files changed, 99 insertions(+), 40 deletions(-) create mode 100644 fly.live.toml diff --git a/config.live.example.json b/config.live.example.json index b78c36e2..ac0af9d2 100644 --- a/config.live.example.json +++ b/config.live.example.json @@ -1,26 +1,28 @@ { "mode": "live", - "_comment": "Live-test config, SECRET-FREE and committed. Two uses: (1) cp -> config.live.json, fill live.private_key/funder_address, run on a geo-TRADABLE Mac; (2) the Fly live worker reads THIS file directly and gets the key from LIVE_PRIVATE_KEY/LIVE_FUNDER_ADDRESS env vars (copybot 1.2). $5/trade test caps \u2014 see LIVE_ROLLOUT.md before raising anything.", - "bankroll_usd": 250.0, - "bankroll_pct": 0.02, + "_comment": "Live-test config, SECRET-FREE and committed. The Fly live worker (wwf-copybot-live, COPYBOT_ROLE=live) reads THIS file and takes secrets from env (LIVE_PRIVATE_KEY/LIVE_FUNDER_ADDRESS + LIVE_CONFIRM to arm). Mac use: cp -> config.live.json and fill live.*. RULES: bankroll_usd MUST equal the actual USDC deposit at arm time (the 1.4 cash-anchor checks state cash vs chain); risk caps are LIVE_ROLLOUT rule 0.6 \u2014 never raise before Phase 6; follow set mirrors copybot.paper.json (Set E) so paper and live books stay comparable.", + "bankroll_usd": 50.0, + "bankroll_pct": 0.1, "price_guard_pct": 0.05, "taker_fee_rate": 0.03, - "discord_webhook": "https://discord.com/api/webhooks/1515187084409704660/VbKWKIIo2ks1sSs2r1cr9M3OSIj_6oKDOBRp7Z5DneQCyk8AnPiyxVKelxensLP-aHaH", + "discord_webhook": "", "follow": { "buy_only": true, "min_their_usd": 25.0, "per_wallet_min_usd": { - "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb": 123.27, - "0xf3488e52ac2d7f0628b04481db5a5b0446f0e543": 727.08, - "0x86c878cde72660ec52f5e6f0f0438b76de8fc867": 990.45, - "0x41558102a796ba971c7567cad41c307e59f8fa41": 231.0, - "0xd96750bf8d941a8186e592b0ae6e096da66aa266": 115.27 + "0x41558102a796ba971c7567cad41c307e59f8fa41": 258.51, + "0xd96750bf8d941a8186e592b0ae6e096da66aa266": 122.32, + "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb": 126.84, + "0xbadaf319415c17f28824a43ae0cd912b9d84d874": 44.98, + "0xd7d36345c4aab150e59577e360696b01d01d698b": 304.34, + "0x921433c93558b9a4ba807ec824d02aad7ea2ddbf": 295.32, + "0xa1d57d329227c75b12b09f927fb3d6d6ef8f1343": 320.95 }, "min_entry": 0.0, "max_entry": 0.95, "class_pct": { - "volume": 0.02, - "whale": 0.02 + "volume": 0.1, + "whale": 0.1 } }, "risk": { @@ -42,50 +44,49 @@ "auto_redeem": true, "rpc_url": "" }, - "stake_cap_usd": 250.0, + "stake_cap_usd": 50.0, "wallets": [ - { - "wallet": "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb", - "name": "Kruto2027", - "class": "volume" - }, - { - "wallet": "0x86c878cde72660ec52f5e6f0f0438b76de8fc867", - "name": "fortuneking", - "class": "volume" - }, { "wallet": "0x41558102a796ba971c7567cad41c307e59f8fa41", "name": "LSB1", - "class": "volume" + "class": "volume", + "floor": 258.51 }, { "wallet": "0xd96750bf8d941a8186e592b0ae6e096da66aa266", "name": "imwalkinghere", - "class": "volume" - }, - { - "wallet": "0xfc81760d44a21acc9fd4b749a5bf9a9b2eeae072", - "name": "iohihoo", - "class": "volume" - }, - { - "wallet": "0xf5fe759cece500f58a431ef8dacea321f6e3e23d", - "name": "Stavenson", "class": "volume", - "floor": 4759.62 + "floor": 122.32 }, { - "wallet": "0x73afc8160c17830c0c7281a7bf570c871455b880", - "name": "0x4bFb-whale", + "wallet": "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb", + "name": "Kruto2027", "class": "volume", - "floor": 1090.46 + "floor": 126.84 }, { - "wallet": "0x72e1597864456eda62878413cf3e60c332e4a45d", - "name": "ArbTrader", + "wallet": "0xbadaf319415c17f28824a43ae0cd912b9d84d874", + "name": "0xbadaf319", "class": "volume", - "floor": 168.28 + "floor": 44.98 + }, + { + "wallet": "0xd7d36345c4aab150e59577e360696b01d01d698b", + "name": "gkmgkldfmg", + "class": "volume", + "floor": 304.34 + }, + { + "wallet": "0x921433c93558b9a4ba807ec824d02aad7ea2ddbf", + "name": "AIcAIc", + "class": "volume", + "floor": 295.32 + }, + { + "wallet": "0xa1d57d329227c75b12b09f927fb3d6d6ef8f1343", + "name": "1kto1m", + "class": "volume", + "floor": 320.95 } ], "feed_path": "live/copybot_live_real.json", diff --git a/copytrade.py b/copytrade.py index dea6128b..c3fec774 100644 --- a/copytrade.py +++ b/copytrade.py @@ -599,6 +599,18 @@ def confirm_live(cfg): f" · daily cap ${cfg['risk']['daily_spend_cap_usd']:.0f}") print(f" Watching {len(cfg['watchlist'])} wallets.") print("=" * 64) + # Headless arm (Fly live worker): the USER types the exact phrase into + # `flyctl secrets set LIVE_CONFIRM="…"` — still a human checkpoint, never + # baked into config or code (LIVE_ROLLOUT rule 0.7). Known property: while + # the secret stays set, restarts RE-ARM automatically (desired during the + # days-long Phase 5 matrix); `flyctl secrets unset LIVE_CONFIRM` disarms + # at the next boot, and any wrong value aborts instead of prompting. + env_phrase = os.environ.get("LIVE_CONFIRM") + if env_phrase is not None: + if env_phrase.strip() == CONFIRM_PHRASE: + print("confirmed via LIVE_CONFIRM env — armed.") + return + sys.exit("Aborted — LIVE_CONFIRM is set but does not match the phrase.") typed = input(f'Type "{CONFIRM_PHRASE}" to proceed (anything else aborts): ') if typed.strip() != CONFIRM_PHRASE: sys.exit("Aborted — not confirmed.") diff --git a/fly.live.toml b/fly.live.toml new file mode 100644 index 00000000..7884c280 --- /dev/null +++ b/fly.live.toml @@ -0,0 +1,20 @@ +# Fly.io config for the REAL MONEY copybot worker — a SEPARATE app from the +# paper worker (wwf-copybot) so the two books never share a process, state +# file, or failure mode (LIVE_ROLLOUT Phase 3.3). Region rules: see fly.toml. +# +# Poll mode only (60s; the Alchemy push webhook stays on the paper app), no +# inbound services, no health checks — the unarmed machine idles in +# geocheck --idle until the USER sets LIVE_PRIVATE_KEY + LIVE_FUNDER_ADDRESS +# + LIVE_CONFIRM (the typed confirmation phrase) via flyctl secrets set. +app = "wwf-copybot-live" +primary_region = "arn" + +[build] + dockerfile = "fly.Dockerfile" + +[env] + COPYBOT_ROLE = "live" + +[[vm]] + size = "shared-cpu-1x" + memory = "512mb" diff --git a/host/start.sh b/host/start.sh index 4c620920..012456d9 100755 --- a/host/start.sh +++ b/host/start.sh @@ -39,6 +39,21 @@ fi python3 "$(dirname "$0")/geocheck.py" \ || echo "⚠ geo-gate BLOCKED/unknown here — fine for paper, do NOT go live from this box" +# COPYBOT_ROLE=live -> the REAL MONEY worker (separate Fly app +# wwf-copybot-live; NEVER convert the paper worker — the two roles must never +# share a process or a state file). Unarmed = missing any of +# LIVE_PRIVATE_KEY / LIVE_FUNDER_ADDRESS / LIVE_CONFIRM -> idle harmlessly +# (no clone, no book, no orders) so the machine can exist safely before +# Phase 2 funding. Arming is three `flyctl secrets set` calls by the USER; +# copybot.py additionally re-checks the geo-gate fatally and validates the +# phrase itself (LIVE_ROLLOUT 0.7, 1.5). +if [ "${COPYBOT_ROLE:-paper}" = "live" ]; then + if [ -z "${LIVE_PRIVATE_KEY:-}" ] || [ -z "${LIVE_FUNDER_ADDRESS:-}" ] || [ -z "${LIVE_CONFIRM:-}" ]; then + echo "live role UNARMED (need LIVE_PRIVATE_KEY + LIVE_FUNDER_ADDRESS + LIVE_CONFIRM) — idling" + exec python3 "$(dirname "$0")/geocheck.py" --idle + fi +fi + : "${GITHUB_TOKEN:?set GITHUB_TOKEN (PAT with repo contents read+write)}" REPO_URL="https://x-access-token:${GITHUB_TOKEN}@github.com/jaxperro/winning-wallet-finder.git" DIR="${COPYBOT_DIR:-/tmp/wwf}" @@ -49,6 +64,17 @@ cd "$DIR" git config user.name "copybot[bot]" git config user.email "copybot@users.noreply.github.com" +# live role, ARMED: own config (committed template + env secrets), own state +# file, own feed/fills (config paths), ALWAYS poll mode — the Alchemy push +# webhook stays pointed at the paper app (LIVE_ROLLOUT Phase 3.3). +if [ "${COPYBOT_ROLE:-paper}" = "live" ]; then + exec python3 copybot.py \ + --config config.live.example.json \ + --state copybot_state.live.json \ + --live \ + --poll "${POLL_SECONDS:-60}" +fi + # paper config is committed (no secrets); state resumes from the last commit. # ALCHEMY_SIGNING_KEY set -> push mode (webhook server); else 60s poll mode. if [ -n "${ALCHEMY_SIGNING_KEY:-}" ]; then