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:
- 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.
- 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).
- 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) buysmsg.value / entryAmounttickets 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 + deadlineandentrantCount >= minTicketsBeforeEnd(isGameOver(),QuantumRaffleV2.sol:498-503). It is an inactivity timer, not a deadline. - Winners ("Power Slots") are deterministic — no randomness anywhere. Ticket
kwins iff its position from the end,1 + N − k, is an exact power ofgrandPrizeLogBase(includingB⁰ = 1, so the last entrant always wins). Winner countW = floor(log_B(N)) + 1; each winner getsprizePool / 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
kbelongs to cohortfloor(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)
| Fact | Value | Source |
|---|---|---|
| Chain | Ethereum mainnet (chain id 1) | quantum-raffle-v2-ui/.env.example |
| Contract | 0xA730A436eAD33E5cf0C4Efed707A15E79F528260 | same + Quantum Bot/src-tauri/src/blockchain/contract.rs |
| Deploy block | 25012088 | quantum-raffle-v2-ui/contracts/README.md |
entryAmount | 0.001 ETH | Quantum Bot PRD §1.4, verified on-chain |
deadline | 600 s (10 min inactivity) | same |
grandPrizeLogBase / viralityBonusLogBase | 5 / 5 | same |
| Pool split | 70% grand / 30% virality | same |
depositFeeBps | 100 (1%) | same |
minTicketsBeforeEnd | 25 | same |
| Multicall3 | 0xcA11bde05977b3631167028862bE2a173976CA11 | blockchain/contract.rs |
| ABI (verified) | Quantum Bot/reference/QuantumRaffleV2.verified.abi.json, quantum-raffle-v2-ui/lib/contracts/QuantumRaffleV2.abi.json | Sourcify / forge inspect |
Contract-level traps every integration must respect:
getNumWinnersreverts whenentrantCount == 0→ batch reads needaggregate3with failure tolerance, notaggregate.- Entry gas is ~27k per ticket because
_processEntryloops 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 treatgameId/entryAmountdrift as an abort (pattern fromQuantum Bot/src-tauri/src/bot/runtime.rs). GameEntered.entryAmountis 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
GameEnteredlogs (IDsentrantCount − num_entries + 1 … entrantCountper event), which makes an archive-capableeth_getLogsRPC 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 < jTwo 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.) prizePerWinnersawtooths. Each entry growsGlinearly, but whenNcrosses a power ofB_g,Wincrements and every winner's prize drops stepwise. This drives both the dilution guard (don't pushNacross 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)
| Codebase | What it is | Verdict for Quant |
|---|---|---|
QR Contract/QuantumRaffleV2/bot/quantum_raffle_bot.py | Python 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
- The last slot (position 1 from end) — deterministic if you are the final entrant when the countdown expires. Value
P ≈ N·e′·0.7/Wgrows roughly linearly withN. - Deep power slots (positions
B_g^jfrom end) — not targetable at entry time, because they depend on the finalN. 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. - 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 finalN(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 toB_v^d − 1) strictly dominate the first seats of cohortd+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
Nacross a power ofB_gadds a winner and shrinks your own prize byW/(W+1). The Rust bot implements this ascrosses_power_boundary(bot/strategy.rs); worked example: atN=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^jcrossing. EV of endgame action is discontinuous inN; the engine must evaluateP(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):
| Metric | Value |
|---|---|
| "Last slot held near deadline" that was counter-entered within 600 s | 90 / 90 — snipe win rate 0% |
| Median inter-entry gap | 540 s (vs. 600 s deadline) |
| Gaps in the 480–600 s band | 97 / 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:
| Playbook | When it acts | What it does | Default |
|---|---|---|---|
| ClaimOnly | always | Claims grand prizes post-game and adoption bonuses the moment pools unlock; never enters. | on (it's the safe floor) |
| EarlyBird | warm-up / young game | Buys cohort-1/2 seats when the growth forecast (from historical games) clears an EV threshold; prefers last-seats-before-boundary. | on, small budget |
| TailBlock | endgame (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 detected | Single 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 host | Auto-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
| Risk | Mitigation |
|---|---|
| Attrition-war escalation | Hard 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 moment | Multi-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 expiry | N-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 / contract | Chain-id + contract-address allowlist checked every tick |
| LLM misbehavior | LLM cannot sign, cannot spend, cannot exceed clamped parameter ranges; all proposals pass the same policy gate as everything else |
| User misconfiguration | Simulation 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
- The player-operator (primary): holds ETH, wants to play Quantum Raffle competitively without babysitting a 10-minute countdown 24/7. Comfortable running
npmcommands; not necessarily a Solidity dev. - The manual player who only wants ClaimOnly + notifications ("tell me when to look, claim what I'm owed").
- 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
| Mode | Behavior | Guard |
|---|---|---|
| Simulation (default) | Full pipeline runs, decisions logged with rationale + would-have-spent accounting; no transactions signed. | — |
| Manual | Agent 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 |
| Automatic | Agent 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):
| Provider | Secret | Wiring |
|---|---|---|
| Cloudflare Workers AI (default; zero keys) | none — ai binding | workers-ai-provider; free-tier friendly |
| Anthropic direct | ANTHROPIC_API_KEY | @ai-sdk/anthropic |
| OpenAI direct | OPENAI_API_KEY | @ai-sdk/openai |
| Vercel AI Gateway | AI_GATEWAY_API_KEY | AI 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 viemprivateKeyToAccount— 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'sPRIZE_RECIPIENTaddress 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_TOKENsecret) enforced inonBeforeRequest/onBeforeConnect(routing options); docs additionally recommend Cloudflare Access in front of the Worker. - Policy layer structurally unavoidable: signing requires a
PolicyApprovalobject that onlypolicy.approve()can construct (module-private brand symbol — the TypeScript approximation of the Rust bot's compile-time guarantee). - Idempotency key
wallet:chainId:contract:gameId:entrantCountpersisted 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.varsgitignored; README warns against.envin 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
getLogsrange is handled by chunking, Alchemy free's 10-block cap is documented as unsuitable (Quantum Bot/docs/findings.mdprovider 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 viagetAgentByName(calling agents). Default instancemain; multi-wallet users create more instances — isolation for free. wrangler.jsoncneedsnodejs_compat, a DO binding forQuantAgent, andmigrations: [{ 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 inthis.sql(state docs).validateStateChangeblocks client-side writes to protected fields (clients get read-mostly state; mutations go through@callablemethods 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 sLive, clock quiet < threshold: every 30 s- Endgame (elapsed ≥
quietClockSeconds): every 10–15 s plus one absoluteschedule(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)
- Refuse if an in-flight tx exists (nonce not yet passed) → reschedule.
- Pin head block hash; verify chain id + contract allowlist.
- Multicall3
aggregate3batches at the pinned block (three batches —gameIdis discovered inside the first;getNumWinnersmay revert on empty games). - Derive
Phase(NoGame | WarmingUp | Live | Endgame | Ended) — a discriminated union where onlyLive/Endgamecarry a countdown field, making the warm-up-countdown bug unrepresentable. - Update ticket index from
GameEnteredlogs (chunked, reorg-aware, fail-closed). - Run
evaluate(state, settings)→Decision(pure). policy.precheck(caps, idempotency, reserve) →eth_estimateGasas simulation → re-read at newest head, abort ongameId/entryAmountdrift →policy.approvemints the approval token.- Record idempotency + spend journal, sign (viem account from secret), submit via private RPC, track receipt.
- Persist decision + rationale to SQL;
setStatethe 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@callableRPC +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:
AIChatAgentfrom@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-inneedsApprovalflow (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_emailbinding) 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 + useAgentChatStrategy 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.
- 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. - 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. - 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 (
createMcpHandlerreplaced 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+AIChatAgentfor 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 validationREADME 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 readyPart 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
- 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_sendEndOfBlockBundleis the only known end-of-block primitive; semantics unverified (Quantum Bot/docs/quantum-bot-prd.mdopen question #2). V1 ships without it; worth a research spike. - 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.
- 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.
- 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.