MDX Limo
Quant — AI Agent for Quantum Raffle

Quant — AI Agent for Quantum Raffle

Outline · Product Requirements · Implementation Plan

Status: Draft v1 · 2026-08-10 Repo: Quant (open source, MIT) — a clone-and-deploy AI agent that plays Quantum Raffle on your behalf, built on Cloudflare Agents, with bring-your-own-keys for both the LLM and the wallet.


Executive summary

Quantum Raffle is a live, on-chain ETH raffle (Ethereum mainnet, 0xA730A436eAD33E5cf0C4Efed707A15E79F528260) where every entry resets an inactivity countdown, the last entrant always wins, additional winners sit at logarithmic positions counted back from the final ticket, and early entrants earn cohort-based "adoption bonuses" as the game grows. It is a game of timing, position, and attrition — not randomness. That makes it playable by software far better than by humans.

Quant is an always-on agent that watches the chain, decides when (and when not) to buy tickets under hard user-set spending limits, submits entries privately so it can't be back-run, claims every prize the user is owed (including mid-game bonus claims most players miss), and explains everything it does in plain English through a chat/dashboard interface.

Three design theses drive everything below:

  1. The strategy engine is deterministic code; the LLM is an advisor, not a trigger-puller. Empirical replay of all on-chain games shows timing-based "sniping" won 0 of 90 attempts, while owning a large contiguous tail block won every game analyzed (§2.4). Winning requires millisecond-consistent execution, hard spend caps, and bigint-exact math — none of which an LLM should be trusted to do inline. The LLM layers on top: strategy tuning within bounded ranges, plain-English rationales, chat control, and post-game reports.
  2. Cloudflare Agents is a near-perfect runtime fit. One Durable Object gives the agent durable identity, embedded SQLite for game history, second-precision persistent scheduling for deadline watching, WebSocket state sync for a live dashboard, and hibernation so an idle agent costs ~nothing (§5). Cloudflare's own docs demonstrate the exact "agent signs on-chain txs with viem from a Worker secret" pattern we need (x402 payments guide).
  3. Config-file defaults + a live dashboard, not either/or. Strategy defaults live in a versioned config file; runtime control (pause, budgets, mode switches, approvals) lives in a React dashboard served by the same Worker, synced over WebSockets — because "edit code and redeploy" is unacceptable UX when a game endgame is unfolding in 600-second windows (§4.6).

Part I — The game

Sources: contract QR Contract/QuantumRaffleV2/src/QuantumRaffleV2.sol (843 lines, Solidity ^0.8.22); formal math gitbook/math/specification.md; concepts gitbook/concepts/*.md; lifecycle gitbook/concepts/game-lifecycle.md.

1.1 Mechanics in one page

  • Entering is a raw ETH transfer. There is no enter() function. receive() (empty calldata) buys msg.value / entryAmount tickets with prizes credited to the sender; fallback() with exactly 20 bytes of calldata (a raw address, no selector, no padding) routes all prize payouts to that address instead — the built-in cold-wallet path, explicitly designed so "a hot wallet (e.g. an AI bot) [can] act as the entrant while prizes accrue to a more securely held cold wallet" (QuantumRaffleV2.sol:377-389). Dust (msg.value % entryAmount) is auto-refunded.
  • Every entry resets the clock. The game ends only when block.timestamp > lastTimestamp + deadline and entrantCount >= minTicketsBeforeEnd (isGameOver(), QuantumRaffleV2.sol:498-503). It is an inactivity timer, not a deadline.
  • Winners ("Power Slots") are deterministic — no randomness anywhere. Ticket k wins iff its position from the end, 1 + N − k, is an exact power of grandPrizeLogBase (including B⁰ = 1, so the last entrant always wins). Winner count W = floor(log_B(N)) + 1; each winner gets prizePool / W (isWinner, getNumWinners, QuantumRaffleV2.sol:761-795). The gitbook is explicit that this "incentivizes late-deadline sniping … the 'one-more-entry' dynamic the game is built around" (gitbook/concepts/winner-selection.md).
  • Every entry splits into two pools (after an optional host fee skim of up to 10%): a grand-prize pool and a "virality/adoption bonus" pool, in proportions locked at game start (_processEntry, QuantumRaffleV2.sol:396-479).
  • Adoption bonuses reward early entrants. Entrant k belongs to cohort floor(log_Bv(k)) + 1; cohort sizes grow geometrically. Each cohort's pool unlocks for claims the moment the next cohort starts, and is split equally among all earlier cohorts ("teams"), then equally among each team's members. Earlier cohorts therefore collect from every later pool (gitbook/math/specification.md §3). The final cohort's pool is never player-claimable — the host sweeps it (clearLeftoverAdoptionBonus).
  • Games run back-to-back. Only the host can start the next game (their seeding ETH becomes ticket #1); all 8 config parameters are locked per game at start (gitbook/concepts/game-lifecycle.md).

1.2 The live deployment (verified from repo configs and broadcast logs)

FactValueSource
ChainEthereum mainnet (chain id 1)quantum-raffle-v2-ui/.env.example
Contract0xA730A436eAD33E5cf0C4Efed707A15E79F528260same + Quantum Bot/src-tauri/src/blockchain/contract.rs
Deploy block25012088quantum-raffle-v2-ui/contracts/README.md
entryAmount0.001 ETHQuantum Bot PRD §1.4, verified on-chain
deadline600 s (10 min inactivity)same
grandPrizeLogBase / viralityBonusLogBase5 / 5same
Pool split70% grand / 30% viralitysame
depositFeeBps100 (1%)same
minTicketsBeforeEnd25same
Multicall30xcA11bde05977b3631167028862bE2a173976CA11blockchain/contract.rs
ABI (verified)Quantum Bot/reference/QuantumRaffleV2.verified.abi.json, quantum-raffle-v2-ui/lib/contracts/QuantumRaffleV2.abi.jsonSourcify / forge inspect

Contract-level traps every integration must respect:

  • getNumWinners reverts when entrantCount == 0 → batch reads need aggregate3 with failure tolerance, not aggregate.
  • Entry gas is ~27k per ticket because _processEntry loops per ticket (25 tickets ≈ 681k gas) → bound batch sizes.
  • A non-host entry into an ended game reverts OnlyHost() → never enter near game-end ambiguity without re-reading state.
  • The unfixable race: neither entry path can carry an "expected game id" guard (receive() takes no data; fallback()'s 20 bytes are fully consumed by the recipient address). If the host's next-game-start tx lands before yours, your ETH buys tickets in the new game at the new price, silently. Mitigation is procedural: re-read state at the newest head immediately before signing, and treat gameId/entryAmount drift as an abort (pattern from Quantum Bot/src-tauri/src/bot/runtime.rs).
  • GameEntered.entryAmount is the effective value (num_entries × entryAmount), not the unit price (Quantum Bot/docs/findings.md).
  • Ticket ownership has no view function — ticket IDs must be reconstructed from GameEntered logs (IDs entrantCount − num_entries + 1 … entrantCount per event), which makes an archive-capable eth_getLogs RPC a hard dependency (§5.4).

1.3 Prize math (the formulas the agent computes constantly)

From gitbook/math/specification.md (notation: N tickets, e entry price, B_g/B_v log bases, p_g/p_v proportions, f fee bps):

1per-ticket pool contribution e′ = e − e·f/10000 2Grand pool G ≈ N · e′ · p_g/(p_g+p_v) 3Winner count W = floor(log_Bg(N)) + 1 4Prize per winner P = floor(G / W) ← sawtooths: drops at every Bg^j crossing 5Winning ticket IDs k_j = N + 1 − Bg^j, j = 0…W−1 6Cohort of ticket k c(k) = floor(log_Bv(k)) + 1 7Cohort pool Pool[j] ≈ (Bv^j − Bv^(j−1)) · v (v = per-ticket virality contribution) 8Pool j claimable ⇔ current max cohort C ≥ j+1 ← claimable MID-GAME, before game over 9Per-member bonus from pool j floor(floor(Pool[j]/(j−1)) / size(d)) for member of cohort d < j

Two non-obvious consequences:

  • Adoption bonuses are claimable while the game is still running. isQualifiedAdoptionBonus (QuantumRaffleV2.sol:522-545) only requires the claimed cohort to be surpassed — not the game to be over — and a pool's balance is frozen the moment its cohort is surpassed. Claim as soon as pools unlock; capital comes back mid-game. (The gitbook's lifecycle table files claiming under "game over," so most players will miss this. Grand-prize claims do require game over.)
  • prizePerWinner sawtooths. Each entry grows G linearly, but when N crosses a power of B_g, W increments and every winner's prize drops stepwise. This drives both the dilution guard (don't push N across a boundary when you hold winners) and endgame timing (the last slot is worth most just before a boundary).

1.4 Prior art in the user's repos (what Quant inherits vs. replaces)

CodebaseWhat it isVerdict for Quant
QR Contract/QuantumRaffleV2/bot/quantum_raffle_bot.pyPython cron bot (Anvil.works hosting). Snipe-window + naive EV threshold strategy.Replace. 13 catalogued defects (Quantum Bot PRD §2.1): countdown ignores minTicketsBeforeEnd, wall-clock time instead of block.timestamp, snipe branch bypasses its own ticket cap, public-mempool submission (trivially back-runnable), no spend caps, no chain-id check. Port only its ticket-ID-from-logs reconstruction idea.
Quantum Bot/ (Tauri + Rust desktop app)The serious prior bot: alloy, Multicall3 block-pinned reads, private submission, type-enforced policy layer, 172 unit tests.Port the architecture. Its 15-step tick, Phase sum type, strategy/policy separation with an unforgeable approval token, state-scoped idempotency keys, and spend journal are the best structural ideas available (src-tauri/src/bot/{strategy,policy,runtime}.rs). Its limitation: it runs only while a desktop app is open — exactly what a Cloudflare agent fixes.
Quantum Raffle V3/quantum-raffle-v2-ui/ (Next.js dapp)Production UI with bigint-exact, unit-tested game math in TypeScript.Port the math verbatim: lib/math/{grand-prize,cohorts,pools,claims}.ts, lib/collectable.ts, lib/tickets.ts (cold-wallet-aware ownership), lib/server/logs.ts (chunked reorg-aware log scanning), plus scripts/local-fork.sh + scripts/start-new-game.sh for fork testing.

Part II — Game theory & strategy design

Derived from the contract math above, plus the on-chain replay in Quantum Bot/scripts/analyze-history.mjs and Quantum Bot/docs/findings.md.

2.1 The payoff structure: three ways to make money

  1. The last slot (position 1 from end) — deterministic if you are the final entrant when the countdown expires. Value P ≈ N·e′·0.7/W grows roughly linearly with N.
  2. Deep power slots (positions B_g^j from end) — not targetable at entry time, because they depend on the final N. But they are coverable: a contiguous block of tickets at the tail covers every power-of-5 position that falls inside it. Powers are dense near the end (1, 5, 25, 125…), so tail tickets have structurally better slot coverage than early tickets.
  3. Adoption bonuses — a claim on every future cohort's pool. Cohort-1 tickets (IDs 1–4 at B_v = 5) split every later unlocked pool among just 4 members; value scales with how far the game grows beyond your cohort. Requires a forecast of final N (buildable from full per-game on-chain history).

"Be early, be last, or be out." Mid-game tickets are the worst seats: no timing control, diluted cohort membership, and sparse power-slot coverage. A disciplined agent mostly abstains.

2.2 Micro-structure edges (each one is a rule in the strategy engine)

  • Cohort-boundary seating: the last seats of cohort d (IDs up to B_v^d − 1) strictly dominate the first seats of cohort d+1 — same price, one cohort earlier, one boundary sooner to unlock. Buy just before boundaries; never just after. Watch block purchases that straddle a boundary.
  • Dilution guard: if you hold winning-now tickets, an entry that pushes N across a power of B_g adds a winner and shrinks your own prize by W/(W+1). The Rust bot implements this as crosses_power_boundary (bot/strategy.rs); worked example: at N=24, base 5, one more ticket makes 25 = 5², winners 2→3, per-winner prize −33%.
  • Sawtooth-aware endgame: the last slot's value peaks just below a B_g^j crossing. EV of endgame action is discontinuous in N; the engine must evaluate P(N), P(N+k) explicitly, never trend it.
  • Warm-up is a distinct phase: below minTicketsBeforeEnd (live: 25) no countdown exists — any "time remaining" display is fiction (the Python bot's worst bug). Warm-up entries are pure early-bird/EV plays; someone has to reach the floor, and cohort-1/2 seats are only ever available here.
  • Mid-game claim harvesting: claim adoption bonuses the moment pools unlock (§1.3), batched via batchClaimAdoptionBonusPrize(gameId, IdPair[]) when gas is favorable. This is free money most participants leave locked until game over.

2.3 The endgame is a war of attrition — and the naive version is dead

The last-slot incentive plus countdown-reset produces Fomo3D-style attrition: every "snipe" resets the 600 s clock and adds to the pool, so between two well-run bots, sniping is mutual bleed with the pool as beneficiary. Theoretical equilibrium: the game ends only when the marginal last-slot EV (P × Pr(no counter-entry)) drops below the entry cost for every remaining player — attrition ends on attention/bankroll exhaustion, not on cleverness.

2.4 Empirical evidence: what actually wins

Quantum Bot/scripts/analyze-history.mjs replays every GameEntered log since deploy block 25012088. Measured across all 147 entries in games 1–3 (as of 2026-07-15):

MetricValue
"Last slot held near deadline" that was counter-entered within 600 s90 / 90 — snipe win rate 0%
Median inter-entry gap540 s (vs. 600 s deadline)
Gaps in the 480–600 s band97 / 144

The 540/552/600-second gap signature is a rival bot deliberately re-entering just before each expiry. And the actual winners:

  • Game 1: one address held 63 of 71 tickets → all 3 winning slots.
  • Game 2: one address held 103 of 184 → 3 of 4 slots.
  • Game 3: same address held 15 of 26 → all 3 slots.

Strategic thesis: owning the tail beats timing the clock. Because winning positions are powers-of-base counted from the end, a large contiguous late block captures several slots at once, and (unlike a snipe) a counter-entry after your block doesn't zero you out — it only shifts which of your tickets sit on slots. No existing bot implements this; it is Quant's core novelty.

2.5 Quant's strategy playbooks

The engine is a pure function evaluate(state, settings) → Decision (auditable, unit-testable, no I/O — structure from Quantum Bot/src-tauri/src/bot/strategy.rs). Playbooks compose; each is independently toggleable and parameterized:

PlaybookWhen it actsWhat it doesDefault
ClaimOnlyalwaysClaims grand prizes post-game and adoption bonuses the moment pools unlock; never enters.on (it's the safe floor)
EarlyBirdwarm-up / young gameBuys cohort-1/2 seats when the growth forecast (from historical games) clears an EV threshold; prefers last-seats-before-boundary.on, small budget
TailBlockendgame (quiet-clock threshold)Buys a contiguous block sized to cover power slots 1…B_g^j, dilution-aware (each added ticket moves every slot, including yours), competitor-concentration-aware. The empirically winning play.on, the main budget
Sentinel (reformed sniper)endgame, only when no rival bot detectedSingle late entry for the last slot. Rival-bot detector: addresses repeatedly entering inside the final window with sub-minute precision. 0/90 evidence says this rarely fires — kept because a future game without a resident rival changes the math.off
HostOps (optional)if the user is the hostAuto-start next game, sweep leftover pool, withdraw fees, queue next-game config.off

Open modeling problems (tracked as future work, LLM-advisor territory): P(counter-entry) as a function of rival stake/concentration; optimal tail-block size against the dilution sawtooth; multi-game bankroll allocation.

2.6 Risk register

RiskMitigation
Attrition-war escalationHard per-game and per-day wei caps in the policy layer, outside LLM reach; rival-bot detection triggers stand-down
Back-running (the loss condition: someone enters right after you)Private, non-OFA submission only — default https://rpc.mevblocker.io/fullprivacy; explicitly not Flashbots Protect /fast, whose MEV-Share hints invite the exact backrun we fear (Quantum Bot/docs/quantum-bot-prd.md §3.4)
Host-opens-next-game race (§1.2)Re-read at newest head immediately pre-sign; abort on gameId/entryAmount drift
RPC failure at the critical momentMulti-provider fallback (dRPC keyless, publicnode reads, user-keyed archive RPC for logs); fail-closed everywhere — a failed ticket scan returns an error, never "0 tickets" (which would re-authorize the full cap)
Reorgs near expiryN-confirmation gating on game-over decisions; reorg-window log re-scans (12-block boundary, from quantum-raffle-v2-ui/lib/server/logs.ts)
Wrong network / contractChain-id + contract-address allowlist checked every tick
LLM misbehaviorLLM cannot sign, cannot spend, cannot exceed clamped parameter ranges; all proposals pass the same policy gate as everything else
User misconfigurationSimulation mode is the default; automatic mode requires an explicit acknowledgment flag (pattern from Quantum Bot's effective_mode())

Part III — Product requirements

3.1 Product statement

Quant is an open-source repository a user clones to run their own autonomous Quantum Raffle agent on their own Cloudflare account, with their own LLM API keys and their own wallet. Nothing is hosted by the project; there is no shared backend, no telemetry, no custody.

3.2 Users

  1. The player-operator (primary): holds ETH, wants to play Quantum Raffle competitively without babysitting a 10-minute countdown 24/7. Comfortable running npm commands; not necessarily a Solidity dev.
  2. The manual player who only wants ClaimOnly + notifications ("tell me when to look, claim what I'm owed").
  3. The host running their own QuantumRaffleV2 deployment (the contract is open; anyone can deploy one) — served by HostOps and by pointing Quant at a custom address/chain.

3.3 Goals / non-goals

Goals

  • G1: Fully autonomous play under user-defined hard limits, with human-approval and simulation modes below that.
  • G2: Never miss a claim (grand prizes post-game; adoption bonuses at unlock).
  • G3: BYOK LLM: OpenAI key, Anthropic key, Vercel AI Gateway key, or Cloudflare Workers AI (zero-key default) behind one adapter.
  • G4: One-command deploy to the user's Cloudflare account; free plan sufficient to start.
  • G5: Live dashboard + chat control; every action explainable after the fact.
  • G6: Safe-by-default: simulation mode default, cold-wallet prize routing strongly encouraged, spend caps mandatory.
  • G7: Test path that never risks mainnet ETH (mainnet-fork scripts, simulation mode).

Non-goals (v1)

  • Not a hosted SaaS; no multi-tenant anything.
  • No custody innovations: one hot wallet secret + cold-wallet routing; no MPC/AA wallets.
  • No mobile app; the dashboard is responsive web.
  • No support for arbitrary other games/contracts (the engine is QuantumRaffleV2-specific; multi-deployment of that contract is supported).
  • The LLM does not discover strategy autonomously in v1 — it tunes and explains a hand-built engine.

3.4 Operating modes

ModeBehaviorGuard
Simulation (default)Full pipeline runs, decisions logged with rationale + would-have-spent accounting; no transactions signed.
ManualAgent proposes actions; each entry requires explicit approval from the dashboard (or approval-gated chat tool). Claims may auto-run (configurable — claims are risk-free).Approval token per action
AutomaticAgent acts within policy limits without confirmation.Requires automaticModeAcknowledged: true + prize recipient configured (or explicit opt-out)

3.5 BYOK LLM support

One provider adapter (AI SDK v6 — the Cloudflare Agents docs' recommended abstraction; using-ai-models):

ProviderSecretWiring
Cloudflare Workers AI (default; zero keys)none — ai bindingworkers-ai-provider; free-tier friendly
Anthropic directANTHROPIC_API_KEY@ai-sdk/anthropic
OpenAI directOPENAI_API_KEY@ai-sdk/openai
Vercel AI GatewayAI_GATEWAY_API_KEYAI SDK gateway provider or OpenAI-/Anthropic-compatible endpoints at https://ai-gateway.vercel.sh (Vercel AI Gateway quickstart); model strings like anthropic/claude-opus-5; gateway-level BYOK of provider keys also supported (BYOK docs)

Config selects llm.provider + llm.model; secrets go in .dev.vars locally and wrangler secret put in production (Workers secrets). Cloudflare's own AI Gateway is a config-only extra (caching/rate-limit/observability) users can layer in (calling LLMs).

LLM job description (bounded): scheduled strategy reviews that may propose parameter changes within configured clamp ranges (e.g., adjust tailBlockSize between 5–25, never touch wei caps); natural-language decision rationales attached to every tick log; the dashboard chat (AIChatAgent with approval-gated tools); post-game reports. The LLM never signs, never sees the private key, and its tool calls pass the same policy gate as any other action.

3.6 Wallet & key model

  • Hot wallet: a dedicated, fresh EOA whose private key lives in a Worker secret (WALLET_PRIVATE_KEY), loaded via viem privateKeyToAccount — the pattern Cloudflare's own docs use for agent payments (x402 guide). Funded with working float only (e.g., 0.05 ETH).
  • Cold-wallet prize routing on by default: entries go through fallback() with the user's PRIZE_RECIPIENT address as calldata, so winnings never touch the hot wallet. Automatic mode refuses to arm until a recipient is set or the user explicitly opts out.
  • Claims note: claim transactions can be sent by any address — payouts always go to getPrizeRecipient — so claim automation works even for tickets bought manually from other wallets the user registers as "watched addresses."

3.7 Configuration surface (quant.config.jsonc, checked in as .example)

1{ 2 "chain": { "id": 1, "contract": "0xA730A436eAD33E5cf0C4Efed707A15E79F528260", "deployBlock": 25012088, 3 "readRpcs": ["https://eth.drpc.org", "https://ethereum-rpc.publicnode.com"], 4 "logsRpc": "SECRET:ARCHIVE_RPC_URL", // archive getLogs provider (Infura/dRPC key) 5 "submitRpc": "https://rpc.mevblocker.io/fullprivacy" }, 6 "mode": "simulation", // simulation | manual | automatic 7 "automaticModeAcknowledged": false, 8 "prizeRecipient": null, // cold wallet; required for automatic mode 9 "watchedAddresses": [], // extra addresses to claim/track for 10 "limits": { "maxTicketsPerGame": 25, "maxWeiPerGame": "0.015 eth", "maxWeiPerDay": "0.05 eth", 11 "gasReserve": "0.005 eth", "maxGasCostPerTx": "0.002 eth" }, 12 "strategy": { 13 "claim": { "enabled": true, "autoClaimInManualMode": true, "maxClaimGasGwei": 20 }, 14 "earlyBird": { "enabled": true, "maxCohort": 2, "budget": "0.005 eth", "minGrowthForecast": 3.0 }, 15 "tailBlock": { "enabled": true, "blockSize": [5, 25], "quietClockSeconds": 480, "dilutionAware": true }, 16 "sentinel": { "enabled": false, "windowSeconds": 90, "maxAttemptsPerGame": 2 }, 17 "hostOps": { "enabled": false } 18 }, 19 "llm": { "provider": "workers-ai", "model": "@cf/zai-org/glm-4.7-flash", 20 "advisor": { "enabled": true, "clamps": { "tailBlock.blockSize": [5, 25], "earlyBird.budget": ["0", "0.005 eth"] } } }, 21 "notifications": { "webhook": null, "email": null, "events": ["entry", "win", "claim", "gameOver", "rivalDetected", "error"] } 22}

File = versioned defaults, applied at deploy. Runtime changes from the dashboard persist in the agent's SQLite and override the file (precedence: dashboard > file > built-ins), with a "reset to config file" action. Wei caps changed at runtime may only go down without a dashboard re-auth step.

3.8 Security & safety requirements

  • Dashboard/API auth: bearer token (ADMIN_TOKEN secret) enforced in onBeforeRequest/onBeforeConnect (routing options); docs additionally recommend Cloudflare Access in front of the Worker.
  • Policy layer structurally unavoidable: signing requires a PolicyApproval object that only policy.approve() can construct (module-private brand symbol — the TypeScript approximation of the Rust bot's compile-time guarantee).
  • Idempotency key wallet:chainId:contract:gameId:entrantCount persisted before broadcast; in-flight tracking released only when the account nonce passes (private txs are invisible to pending-tx queries by design).
  • Spend journal appended under the same critical section as signing; a corrupt/missing journal means refuse to spend (fail-closed, from Quantum Bot/src-tauri/src/bot/policy.rs).
  • Day-budget rollover keyed to chain time, not wall clock.
  • All secrets via Wrangler secrets; .dev.vars gitignored; README warns against .env in git and against reusing a personal wallet key.
  • Prominent disclaimers: this is real-money gambling-adjacent software; jurisdictional legality is the user's responsibility; no warranty; the repo never solicits funds.

3.9 Cost expectations (documented in README)

  • Cloudflare: agents run on SQLite-backed Durable Objects, available on the Workers Free plan (100k DO requests/day, 100k SQLite row-writes/day) — enough for simulation and light play; Workers Paid ($5/mo) recommended for real polling volume (DO pricing, agent limits). Hibernation keeps idle cost ~zero.
  • LLM: zero with Workers AI default; otherwise the advisor runs on events/schedules (not per tick) so typical spend is cents/day.
  • RPC: keyless endpoints suffice for reads; one free-tier archive key (dRPC/Infura) needed for log scans; Infura's 2000-block getLogs range is handled by chunking, Alchemy free's 10-block cap is documented as unsuitable (Quantum Bot/docs/findings.md provider matrix).
  • Gas + tickets: the user's stake; bounded by the policy caps.

Part IV — Architecture on Cloudflare

All platform claims sourced from the Cloudflare Agents docs; per-page links inline. SDK: agents npm package v0.17.x; chat: @cloudflare/ai-chat.

4.1 Shape: one Worker, one Agent class, one instance per wallet

1┌─ Cloudflare Worker (user's account) ────────────────────────────────┐ 2│ routeAgentRequest ──► QuantAgent (Durable Object, SQLite-backed) │ 3│ static assets ─────► dashboard SPA (React + useAgent) │ 4│ │ 5│ QuantAgent │ 6│ ├─ this.state → live GameSnapshot + AgentStatus (WS-synced) │ 7│ ├─ this.sql → entries, ticket index, spend journal, decisions,│ 8│ │ game history, chat threads │ 9│ ├─ scheduleEvery → adaptive chain polling (idle→endgame cadence) │ 10│ ├─ schedule(Date)→ wake exactly at lastTimestamp+deadline−buffer │ 11│ └─ callable() → pause/resume, setLimits, approve/reject, replay │ 12└─────────────────────────────────────────────────────────────────────┘ 13 │ fetch (eth JSON-RPC) │ AI SDK 14 read RPCs + archive logsRpc Workers AI / OpenAI / 15 submit: mevblocker fullprivacy Anthropic / Vercel AI GW
  • Each Agent instance is a globally-unique SQLite Durable Object with colocated storage; addressed as /agents/quant-agent/<name> or server-side via getAgentByName (calling agents). Default instance main; multi-wallet users create more instances — isolation for free.
  • wrangler.jsonc needs nodejs_compat, a DO binding for QuantAgent, and migrations: [{ tag: "v1", new_sqlite_classes: ["QuantAgent"] }] — SQLite classes are mandatory for agents (configuration).
  • State rules per docs: small, JSON-serializable this.state (it broadcasts to every connected client on change) for the live snapshot; everything historical/queryable in this.sql (state docs). validateStateChange blocks client-side writes to protected fields (clients get read-mostly state; mutations go through @callable methods that hit the policy gate).

4.2 The heartbeat: scheduling design

(schedule-tasks docs; schedules persist in SQLite and survive hibernation/restarts.)

  • Adaptive polling ladder, implemented with scheduleEvery (second precision; overlap-skip built in) and re-tuned every tick:
    • NoGame/Ended: every 120 s (watch for host starting next game)
    • WarmingUp: every 60 s
    • Live, clock quiet < threshold: every 30 s
    • Endgame (elapsed ≥ quietClockSeconds): every 10–15 s plus one absolute schedule(new Date(lastTimestampMs + deadline − buffer)) wake so the decisive evaluation happens at the right moment even if interval ticks drift.
  • Claim sweeps: cron schedule (minute precision is fine) + event-triggered on cohort-boundary crossings.
  • Pending-tx confirmation watching: the docs' own capped-backoff polling pattern (long-running agents).
  • 30 s compute per activation, refreshed per scheduled task, with network waits not counting against CPU — comfortably fits a tick (limits).
  • Hibernation discipline: no in-memory coordination; every cross-tick fact lives in SQL/state (class fields and timers do not survive hibernation).

4.3 The tick (ported from Quantum Bot/src-tauri/src/bot/runtime.rs, adapted to Workers)

  1. Refuse if an in-flight tx exists (nonce not yet passed) → reschedule.
  2. Pin head block hash; verify chain id + contract allowlist.
  3. Multicall3 aggregate3 batches at the pinned block (three batches — gameId is discovered inside the first; getNumWinners may revert on empty games).
  4. Derive Phase (NoGame | WarmingUp | Live | Endgame | Ended) — a discriminated union where only Live/Endgame carry a countdown field, making the warm-up-countdown bug unrepresentable.
  5. Update ticket index from GameEntered logs (chunked, reorg-aware, fail-closed).
  6. Run evaluate(state, settings)Decision (pure).
  7. policy.precheck (caps, idempotency, reserve) → eth_estimateGas as simulation → re-read at newest head, abort on gameId/entryAmount drift → policy.approve mints the approval token.
  8. Record idempotency + spend journal, sign (viem account from secret), submit via private RPC, track receipt.
  9. Persist decision + rationale to SQL; setState the new snapshot (dashboard updates in real time); fire notifications; ask the LLM (async, non-blocking) to annotate the decision for the activity feed.

4.4 Dashboard & chat

  • SPA served from Worker assets at the Worker's URL; talks to the agent via useAgent (WebSocket state sync + typed @callable RPC + onStateUpdate) (quick start).
  • Panels: live game (countdown, N, pool, current winners map, cohort progress bars), agent status (mode, phase, budgets spent/remaining, in-flight tx), holdings (tickets, winning-now / away-by-k via ported distanceToWinningSpot), P&L (spend journal vs. claims), activity feed (decisions + LLM rationales), controls (mode switch, pause, budget sliders — decrease-only without re-auth, playbook toggles, kill switch), approvals inbox (manual mode).
  • Chat: AIChatAgent from @cloudflare/ai-chat (persistent messages, resumable streaming) with three tool grades: read-only (answer from SQL), config-within-clamps, and approval-gated actions using the built-in needsApproval flow (chat agents).
  • Manual-mode approvals that must survive days: Cloudflare Workflows waitForApproval() with scheduled reminder escalations — the docs' recommended human-in-the-loop gate (HITL patterns, run-workflows). Everything else stays agent-internal (schedules/fibers), per the docs' rule of thumb.
  • Notifications: webhook (Discord/Telegram-compatible JSON) in v1; optional email via the agents email channel (send_email binding) in v1.1 (email channel).

4.5 Module layout (mirrors the trust gradient)

1src/ 2 agent.ts QuantAgent (lifecycle, schedules, callable methods, state) 3 chat.ts QuantChat (AIChatAgent subclass, tools) 4 chain/ viem clients, multicall reader (block-pinned), log scanner, 5 fee quoting, private submitter, revert decoding 6 game/ PURE bigint math ported from quantum-raffle-v2-ui/lib/math 7 (winners, cohorts, pool replay, claims, distance-to-slot) 8 strategy/ PURE evaluate() + playbooks + phase model + forecasts 9 policy/ caps, idempotency, spend journal, approval token (brand-sealed) 10 signer/ the only module importing WALLET_PRIVATE_KEY; requires PolicyApproval 11 llm/ provider adapter (workers-ai | anthropic | openai | vercel-gateway), 12 advisor (clamped proposals), rationale writer, report writer 13 notify/ webhook/email fan-out 14dashboard/ React SPA (Vite), useAgent + useAgentChat

Strategy cannot import signer; signer's entry function takes PolicyApproval by type. Enforced by ESLint import-boundary rules + the brand symbol.

4.6 How the user controls the agent — the interface decision

The user asked directly: is editing code enough, or is an interface needed? Decision: three layers, because they serve different time-constants.

  1. Config file (quant.config.jsonc) — slow decisions: chain/contract, provider choice, default limits, playbook defaults. Versioned with the repo; re-applied on deploy. Code-comfortable users can stop here; the agent is fully operable in simulation/manual from the file alone.
  2. Dashboard — fast decisions: pause now, approve this entry, drop the budget mid-endgame, watch the countdown live. A raffle endgame moves in 600-second windows; "edit → commit → wrangler deploy" is a multi-minute loop and would be the difference between acting and missing. The Agents SDK makes this layer nearly free (state sync + callable RPC are platform primitives), so the cost/benefit strongly favors shipping it in v1.
  3. Chat — explanation and intent: "why did you buy 8 tickets at 14:02?", "be more conservative this week" (→ clamped config proposal shown as a diff → confirm). This is where the BYOK LLM earns its keep without ever holding execution authority.

Runtime overrides persist in the DO's SQLite; the dashboard shows drift from file defaults and offers one-click reset. This "file = defaults, DO = runtime truth" split keeps git pull upgrades clean.

4.7 What we deliberately do not use

  • McpAgent / MCP server surface — deprecated in current docs (createMcpHandler replaced it); nothing in v1 needs to expose MCP (MCP agent API). Possible future: expose Quant as an MCP server so users' Claude/other assistants can query it.
  • Think harness — optimized for chat-first agents; Quant's core loop is a deterministic tick, so raw Agent + AIChatAgent for the chat lane is the right level (Think).
  • Browser rendering, voice, Slack channels — out of scope v1.
  • Worker-level cron triggers — agent-internal schedules do the same job without the account-level 5-cron free-plan cap (limits).

Part V — Repository layout (open-source presentation)

1quant/ 2├── README.md ← hero: what/why, 10-minute quickstart, screenshots, cost table, disclaimers 3├── LICENSE ← MIT 4├── CONTRIBUTING.md · CODE_OF_CONDUCT.md · SECURITY.md 5├── docs/ 6│ ├── PRD.md ← this document 7│ ├── game-mechanics.md ← Quantum Raffle explained (distilled Part I/II) 8│ ├── strategy-guide.md ← playbooks, parameters, evidence, tuning 9│ ├── setup.md ← step-by-step: Cloudflare account → deploy → arm 10│ ├── configuration.md ← every config key, every secret 11│ ├── security.md ← key handling, threat model, what the LLM can/can't do 12│ └── faq.md 13├── quant.config.example.jsonc 14├── .dev.vars.example ← WALLET_PRIVATE_KEY=, ADMIN_TOKEN=, ARCHIVE_RPC_URL=, <llm keys> 15├── wrangler.jsonc 16├── package.json · tsconfig.json (extends agents/tsconfig) · vite.config.ts 17├── src/ · dashboard/ ← §4.5 18├── abi/QuantumRaffleV2.json ← Sourcify-verified ABI 19├── scripts/ 20│ ├── setup.ts ← interactive wizard: writes .dev.vars, runs `wrangler secret put`, sanity-checks RPCs 21│ ├── local-fork.sh ← anvil --fork-url <mainnet> --chain-id 1 (ported) 22│ ├── start-new-game.sh ← impersonate host on fork, open a game (ported) 23│ └── analyze-history.mjs ← on-chain replay / strategy backtesting (ported & extended) 24├── test/ ← Vitest + Workers pool; golden-vector math tests vs. forge outputs 25└── .github/workflows/ci.yml ← typecheck, unit tests, config-schema validation

README quickstart contract (the promise we hold ourselves to):

1git clone https://github.com/<you>/quant && cd quant 2npm install 3npm run setup # wizard: Cloudflare login, secrets, config 4npm run dev # local: agent + dashboard on a mainnet fork 5npm run deploy # wrangler deploy to YOUR account 6# open the printed URL → dashboard → agent is in Simulation mode → observe → arm when ready

Part VI — Implementation plan

Dependency-ordered milestones; each lands green CI and a working demo. (Est. effort in focused days, solo + AI-assisted.)

M0 — Scaffold & plumbing (1–2d). create-cloudflare agents-starter base (quick start); wrangler.jsonc (DO binding, sqlite migration, nodejs_compat, assets); config loader + schema (zod) + .example files; CI. Accept: npm run dev serves a hello-world QuantAgent with synced state.

M1 — Chain read layer (2–3d). viem clients w/ fallback transport; Multicall3 block-pinned reader (aggregate3, three-batch pattern); chunked reorg-aware log scanner (port lib/server/logs.ts semantics); ticket index in agent SQL; Phase derivation. Accept: against mainnet, agent state shows live game snapshot matching the V3 UI; log scan failures fail closed.

M2 — Game math (1–2d). Port lib/math/{grand-prize,cohorts,pools,claims}.ts + collectable.ts + cold-wallet-aware tickets.ts to src/game/; golden-vector tests against gitbook/simulations/results.md tables and forge test outputs. Accept: 100% agreement with on-chain views on historical games.

M3 — Strategy + policy (3–4d). Pure evaluate() with ClaimOnly, EarlyBird, TailBlock, Sentinel; dilution guard; rival-bot detector; policy layer (caps, idempotency, spend journal, brand-sealed approval token); simulation-mode decision logging. Accept: replay harness runs strategies over historical games (extend analyze-history.mjs) with sane decisions; policy unit tests prove every cap fail-closed.

M4 — Execution (2–3d). Fee quoting (floor-not-cap min fee, headroom multiplier); signer module; private submission (mevblocker fullprivacy; public mempool only when chain ≠ fork is false); receipt/nonce tracking; the pre-sign re-read abort. Accept: full enter-and-claim cycle on local-fork.sh + start-new-game.sh; idempotency holds across forced agent restarts (hibernation test).

M5 — Dashboard (3–4d). React SPA (Vite + agents() plugin), useAgent live panels, controls wired to @callable methods behind ADMIN_TOKEN, approvals inbox, P&L from spend journal + claims. Accept: pause/arm/budget changes take effect mid-tick; approval flow round-trips.

M6 — LLM layer (2–3d). Provider adapter (workers-ai default, anthropic, openai, vercel-gateway); rationale annotations; clamped advisor proposals rendered as config diffs; AIChatAgent chat with graded tools; post-game report. Accept: switching provider is a 2-line config change; adversarial-prompt test shows chat cannot move money or exceed clamps.

M7 — Docs, hardening, release (2–3d). All docs/*; npm run setup wizard; notification webhooks; free-plan budget audit (poll cadence vs. 100k req/day + 100k row-writes/day); disclaimers; tag v0.1.0. Accept: a fresh Cloudflare account goes clone→simulation-on-mainnet in ≤10 minutes following only the README.

Post-v1 backlog: email channel + email approvals; multi-instance portfolio view; P(counter-entry) model + tail-block optimizer (the open game-theory problems in §2.5); MCP server exposure; Workers Paid autoscaling notes; host-mode analytics dashboard; strategy backtest CLI as a first-class tool.


Part VII — Open questions

  1. Endgame ordering primitive. Among same-block entrants the highest tx index wins the tail, but builders sort by profit — a bigger tip sorts you earlier, backwards from what we want. Titan's eth_sendEndOfBlockBundle is the only known end-of-block primitive; semantics unverified (Quantum Bot/docs/quantum-bot-prd.md open question #2). V1 ships without it; worth a research spike.
  2. The resident rival. The 540-second re-entry signature implies an existing well-run bot. TailBlock's economics vs. a rival who also reads this open-source repo need a game-theoretic pass (symmetric-strategy equilibrium) before Automatic mode defaults get finalized.
  3. How much LLM autonomy to grow toward. v1 clamps hard. If the advisor proves well-calibrated in simulation logs, later versions could widen clamps or let it schedule its own reviews — evidence-first.
  4. Fork-testing ergonomics on Workers. wrangler dev + local anvil works but crosses two runtimes; consider a docker-compose recipe or documented tmux flow in M7.

Sources

Local repositories analyzed

  • Contract + docs: ~/Documents/QR Contract/QuantumRaffleV2/src/QuantumRaffleV2.sol; gitbook/math/specification.md; gitbook/concepts/{how-it-works,winner-selection,virality-bonus,game-lifecycle}.md; gitbook/operations/host-guide.md; bot/quantum_raffle_bot.py; script/Deploy.s.sol; broadcast/.
  • Prior bot (architecture source): ~/Documents/Quantum Bot/src-tauri/src/bot/{strategy,policy,runtime,types}.rs; src-tauri/src/blockchain/{reader,tickets,fees,submitter,revert}.rs; docs/quantum-bot-prd.md; docs/findings.md; scripts/analyze-history.mjs.
  • Dapp (math + infra source): ~/Documents/Quantum Raffle V3/quantum-raffle-v2-ui/lib/math/*; lib/{collectable,tickets,player}.ts; lib/server/{logs,games}.ts; lib/contracts/*; hooks/use-enter-game.ts; scripts/{local-fork.sh,start-new-game.sh,verify-abi.ts}; .env.example.

Cloudflare docs (agents SDK v0.17.x era)

  • Overview & page index: developers.cloudflare.com/agents/ · /agents/llms.txt
  • Quick start / config / testing: /agents/getting-started/quick-start/ · /agents/api-reference/configuration/ · /agents/getting-started/testing-your-agent/
  • State & SQL: /agents/api-reference/store-and-sync-state/ — Scheduling: /agents/api-reference/schedule-tasks/ — Routing/auth: /agents/api-reference/calling-agents/ — WebSockets/hibernation: /agents/api-reference/websockets/
  • LLMs: /agents/api-reference/using-ai-models/ · /agents/concepts/calling-llms/ — Chat: /agents/api-reference/chat-agents (@cloudflare/ai-chat)
  • Long-running patterns & fibers: /agents/concepts/agentic-patterns/long-running-agents/ — HITL: /agents/concepts/human-in-the-loop/ — Workflows: /agents/api-reference/run-workflows/
  • Payments-with-viem precedent: /agents/tools/payments/x402/pay-from-agents-sdk/ — MCP status: /agents/model-context-protocol/apis/agent-api/ — Think: /agents/harnesses/think/
  • Limits & pricing: /agents/platform/limits/ · /durable-objects/platform/pricing/ · /workers/platform/limits/ — Secrets: /workers/configuration/secrets/

Vercel docs

  • AI Gateway: vercel.com/docs/ai-gateway · /docs/ai-gateway/getting-started/text (endpoints https://ai-gateway.vercel.sh{,/v1}) · /docs/ai-gateway/authentication-and-byok/byok

Submission-privacy references (via Quantum Bot PRD §3.4): rpc.mevblocker.io/fullprivacy; rpc.flashbots.net?hint=hash; inclusion-latency measurement arXiv:2505.19708.