hardening trio: listeners start after seed (closes #5), rpc-down heartbeat flag (closes #8), publish-failure alert (closes #10)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-19 16:29:47 -04:00
parent a745ba9158
commit ca0e0bbd80
+41 -17
View File
@@ -82,6 +82,8 @@ CTF_ADDR = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
_SEL_DEN = "0xdd34de67" # payoutDenominator(bytes32)
_SEL_NUM = "0x0504c814" # payoutNumerators(bytes32,uint256)
_RPC_URL = None # resolved once in main() (env/config), stays None without a key
_RPC_FAILS = 0 # consecutive onchain_payouts failures (closes #8: a dead
# key silently stalled tier-2 settlement forever)
_PAYOUTS = {} # cond -> [p0, p1], cached once resolved (immutable)
@@ -105,9 +107,11 @@ def onchain_payouts(cond):
return None
if cond in _PAYOUTS:
return _PAYOUTS[cond]
global _RPC_FAILS
try:
c = cond[2:].rjust(64, "0")
den = int(_eth_call(_SEL_DEN + c), 16)
_RPC_FAILS = 0
if not den:
return None
nums = [int(_eth_call(_SEL_NUM + c + hex(i)[2:].rjust(64, "0")), 16)
@@ -115,6 +119,7 @@ def onchain_payouts(cond):
_PAYOUTS[cond] = [n / den for n in nums]
return _PAYOUTS[cond]
except Exception:
_RPC_FAILS += 1
return None
@@ -1409,8 +1414,21 @@ class Copybot:
pass
p = subprocess.run(["git", "-C", repo, "push", "-q", "origin", "main"],
capture_output=True, text=True)
log("published live feed → dashboard" if p.returncode == 0
else "feed push failed (will retry next change)")
if p.returncode == 0:
self._pub_fails = 0
log("published live feed → dashboard")
else:
# closes #10: a dead GITHUB_TOKEN ends durability SILENTLY —
# after 10 straight failures, say so where someone will look
self._pub_fails = getattr(self, "_pub_fails", 0) + 1
log("feed push failed (will retry next change)")
if self._pub_fails == 10:
self.engine.alert(
"⚠ feed publish failing repeatedly — book durability "
"is OFF (check GITHUB_TOKEN)",
discord_text="🚨 **PUBLISH FAILING** — 10 straight "
"push failures; the book is not being backed up "
"(GITHUB_TOKEN dead?)")
except Exception as e:
log(f"feed publish error: {e}")
@@ -1811,6 +1829,8 @@ class Copybot:
if uw is not None:
driftstr += (" · userws up" if uw.state == "up"
else f" · ⚠ userws {uw.state}")
if _RPC_URL and _RPC_FAILS >= 5:
driftstr += f" · ⚠ rpc down ({_RPC_FAILS} fails)"
log(f"[{cycle}] open {n} · deployed ${exp:,.0f} · free ${cash:,.0f}/${bank:,.0f}"
f"{bankstr} · realized ${realized:+,.2f}{lagstr}{driftstr}"
+ (f" · CAN'T OPEN (free < ${stake:,.0f} stake — bets missed)"
@@ -2520,21 +2540,6 @@ def main():
# boot on origin at publish time and yields instead of overwriting it.
engine.state["boot"] = {"id": uuid.uuid4().hex[:12], "ts": int(time.time())}
# T0 detection — the RTDS trade stream (~1s, wallet-attributed). Paper
# runs it by default (the 24h shadow run, 2026-07-10); the REAL-MONEY
# role stays on the proven Alchemy+poll stack until the shadow validates,
# then: flyctl secrets set RTDS_DETECT=1 -a wwf-copybot-live.
rtds_default = "0" if os.environ.get("COPYBOT_ROLE", "paper") == "live" else "1"
if (os.environ.get("RTDS_DETECT") or rtds_default) == "1":
bot.rtds = RtdsListener(bot)
bot.rtds.start()
else:
log("rtds: T0 listener disabled for this role (RTDS_DETECT=1 enables)")
if want_live:
# own-fill push (2026-07-13): in-play holds adopt the moment they
# match instead of on the next 60s resolver tick
bot.userws = UserFillsListener(bot)
bot.userws.start()
# on-chain resolution RPC (payout vectors for operator-resolved markets):
# env ALCHEMY_RPC_URL wins (the Fly worker has no config.json), else the
@@ -2563,6 +2568,25 @@ def main():
# if the drift matches it exactly (HANDOFF proper fix, Option A)
bot.check_book(heal_cash=True)
# T0 detection — started AFTER seed()/check_book (closes #5: a trade
# arriving before their_pos is seeded made an ADD look like a fresh
# OPEN and broke sell-fraction basis). Backstops cover the boot gap.
# The RTDS trade stream (~1s, wallet-attributed). Paper
# runs it by default (the 24h shadow run, 2026-07-10); the REAL-MONEY
# role stays on the proven Alchemy+poll stack until the shadow validates,
# then: flyctl secrets set RTDS_DETECT=1 -a wwf-copybot-live.
rtds_default = "0" if os.environ.get("COPYBOT_ROLE", "paper") == "live" else "1"
if (os.environ.get("RTDS_DETECT") or rtds_default) == "1":
bot.rtds = RtdsListener(bot)
bot.rtds.start()
else:
log("rtds: T0 listener disabled for this role (RTDS_DETECT=1 enables)")
if want_live:
# own-fill push (2026-07-13): in-play holds adopt the moment they
# match instead of on the next 60s resolver tick
bot.userws = UserFillsListener(bot)
bot.userws.start()
# one-shot pipeline test: no server, just push a wallet's latest trade through
if args.test_wallet:
log(f"--test-wallet: routing {args.test_wallet[:10]}'s latest activity "