Under the hood
Keeper and operations
The jobs that list, settle and pay out markets, the refund policy, health checks, and how anyone can do each job.
A convenience, not an authority
The keeper is a small program that lists markets before the opening bell, settles them after the closing bell and pushes payouts to winners. Every action it takes, anyone can take: settle with the proven rounds, refund on a proven stale price, deliver a payout. If it stops, markets still settle, just later; after 72 hours anyone can refund a market outright.
It runs as scheduled route handlers on Vercel, each protected by a secret, and every job is idempotent: it reads the chain, decides, acts, and can run twice without harm.
The jobs
| Job | When (UTC, weekdays unless noted) | What it does |
|---|---|---|
open | Every 10 minutes, 12:00 to 13:59 | Makes sure today's daily markets exist for every allowed ticker (and the week's weekly markets), listed before 9:30 am ET, using the NYSE calendar. Skips a ticker with a corporate action in the window. |
resolve | Every 2 minutes, 20:00 to 21:59; and hourly at :07 every day | For every market past its bell and not settled: finds both rounds, previews, then settles or refunds per the policy below. |
deliver | Every 5 minutes, every day | Pushes every unclaimed payout and refund to its owner, one call per position; returns refused parts of bets; sweeps fees to the treasury when more than 5 USDG has built up. |
relay | On request | Checks a bettor's signed bet, simulates it and sends it, paying the gas. See Gasless betting. |
health | On request | Reports whether every job is keeping up (below). |
Market hours are computed in New York time with a time-zone library, never a fixed offset. The bells are 13:30 and 20:00 UTC until November 1, 2026, and 14:30 and 21:00 UTC after it.
The refund policy
The keeper may refund a market automatically, because a refund here is based on proof, not on a timeout: the rounds in effect at the bells either break the market’s age limit or they do not, and an RPC outage cannot change that. It still waits:
- Stale price: only after the bell plus 15 minutes, and only when two independent reads both say STALE.
- Corporate-action pause: retried every 10 minutes; refunded only 24 hours after the bell if still paused.
- Phase change on a feed (
PhaseBoundary): never refunded automatically. The operator is alerted, and the 72-hour timeout remains the backstop.
Wallets and funds
| Wallet | Holds | Rule |
|---|---|---|
| Keeper (lister and relayer), a hot wallet | ETH for gas (its own calls, relayed bets, payout deliveries); the USDG float for opening seeds | Keeps at least 0.001 ETH, and enough USDG (in its wallet plus the seeds in its open markets) for every market it is set to list, all open at once. Seeds come back to it at each settlement. Its key lives only in the hosting environment. |
| Treasury Safe | Fees and rounding leftovers | Swept automatically; nothing to spend. |
| Guardian Safe (the same Safe) | Nothing | Only signs: pause or resume new bets and new markets, name the pauser, allow-list feeds, allow listers. |
| Pauser, one key kept offline | A little ETH for gas | Can pause new bets and new markets in one transaction; cannot resume them or do anything else. |
The keeper’s wallet can list markets with its own money and holds the seed float. It cannot touch anyone else’s position; if its key leaked, the Safe removes it as a lister and a new key takes over.
Health checks
GET /api/health answers 200 only if all of these hold, and 503 listing the ones that failed:
- the last successful listing run is less than 26 hours old on a trading day, and today’s markets exist after 13:25 UTC;
- no market is more than 30 minutes past its bell without being settled (unless it is waiting on a phase change);
- no settled market has an undelivered payout older than 20 minutes;
- the keeper holds at least 0.001 ETH, and its wallet plus the seeds in its open markets reach its USDG floor;
- the Safe owns the factory (ownership accepted), HunchVPM’s only creator is the factory, the keeper is a lister, and new bets are not paused;
- the RPC’s latest block is less than 60 seconds old;
- every allowed feed has updated within 26 hours on a trading day;
- the relayer has sent a bet successfully in the last trading day, or none was requested;
- every open market’s page reads current numbers, through the same cache the page uses (
market-reads); - the newest market’s entry times and transaction links can be read from the chain’s logs (
market-logs).
An external monitor polls it every 5 minutes and alerts the operator.
Anyone can do it
| Job | Call | Who receives the money |
|---|---|---|
| Settle a market | resolve(specId, strikeRound, finalRound) | Nobody; it records the result |
| Refund on a stale price | voidStale(specId, strikeRound, finalRound) | Nobody; it records the refund |
| Refund on an out-of-range price | voidBadAnswer(specId, strikeRound, finalRound) | Nobody; it records the refund |
| Refund after a long pause | voidPaused(specId) | Nobody; it records the refund |
| Deliver a payout | claimFor(positionId) | The position's owner, always |
| Deliver a refund | withdrawRefundFor(positionId) | The position's owner, always |
| Sweep fees | sweepFees(usdg) | The treasury Safe, always |
Step by step, with commands: resolve it yourself.
When something goes wrong
- A bug is suspected: the pauser (or the Safe) pauses new bets and new markets. Payouts, refunds and settlement keep working; only the Safe can resume.
- A price feed misbehaves: the Safe removes it from the allow-list, so no new market uses it; existing markets settle or refund on their own proofs.
- The keeper’s key leaks: the Safe removes it as a lister and allows a new one; the float moves first.
- The keeper is down: anyone settles and delivers; the operator can also run the keeper by hand.