Puttcroft docs

← Back to the course

Daily mini golf on a toy stock market. Beat the AI caddie, take the pot.

Play

Drag back and let go to putt. Three market-themed holes, the same for everyone today. Beat the AI's score; fewest strokes among holders splits the daily pot.

Open the site and drag back from anywhere on the course, then let go. The ball rolls the opposite way you pulled, and the further you pull, the harder it goes. No wallet, no login, no download: a round takes about a minute.

Rules

The AI caddie

Every day an AI caddie plays the same three holes and sets a score to beat. The AI caddie is a simple search over the same physics, run once per day. Its shots are public at /api/course/today (field ai). It shows on the board as a separate row for comparison: it has no rank, takes no pot share and is never eligible for the pot.

Controls

Ranked rounds and Practice

A ranked round is one start-to-submit run of today's three holes. Playing as a guest gives you 1 ranked round a day; to stop cleared cookies from handing out free rounds, one network address can also start at most 3 guest rounds a day. When you are out of rounds you will see the time until the next course, and Practice stays open: all library holes, no limit, nothing is sent to the server.

Today's three holes are hidden in Practice until you have played your ranked round. This is "hidden, not enforced": the hole files are static and anyone can download them.

Share

After a round you can copy a short result card (one square per stroke, a blue square per water penalty, a flag for the cup, and how you did against the AI) or save an image of it.

Course

The course is not stored anywhere: it is computed from the UTC date, the same way in your browser and on the server. The seed is the first 31 bits of sha256("puttcroft-YYYY-MM-DD"), fed to a small deterministic generator that picks one hole per tier from a library of handmade holes (easy, mid, hard). A hole is not reused within 7 days, counted from the first course on 2026-10-27. The par of the course is the sum of the three hole pars. The three holes are laid out as one connected board; the physics still runs hole by hole.

/api/course/today is the official answer: the date, the reset time, the par, the full hole definitions and the AI caddie's score. Two devices that call it get the same hole ids. Finished days stay available at /api/course/YYYY-MM-DD (today and future days return 404).

If a hole turns out to be broken after the day has started, the server can swap one hole for another of the same tier for that date. override in /api/course/today is then true. This is a safety valve, not something we expect to use.

Every hole in the library is checked before release: a stored reference solution must sink the ball within par, with a tolerance band around the angle and power so that finger jitter on a phone cannot make a hole impossible, and the physics must give identical results run after run.

Fair play

Your shots (angle and power) are sent to the server, which replays them with the same deterministic physics and counts the strokes itself. The three holes are fixed for the whole UTC day; attempts are limited per day; the board ranks the best run per player, earlier submission wins ties.

What the server checks

Being straight about the limits: the hole files are public and the physics is deterministic, so someone could write a script that searches for perfect shots (the AI caddie is exactly that, in a simple form). The defences are the attempt limits, the fact that the pot only goes to holders, and the team review of the winners before payment. We do not claim more than that.

Pot

The daily pot is funded by hand from creator fees: 50% of what came in that day (UTC). The top three holders on that day's board split it 50/30/20. No contract, no amount promised; every payout is posted with its transaction on /proof.

Prize pot opens after launch. Until then the daily board runs and pays nothing.

How a payout happens

  1. At 00:00 UTC the server freezes the board of the day that just ended. For every wallet on it, the server reads the token balance from the chain at that moment and stores it with the read time, so buying after midnight does not count.
  2. Among the wallets that were holders at the freeze, the three with the fewest strokes (earlier submission wins ties) are the winners and get 50% / 30% / 20% of that day's pot. The AI caddie row never takes a share.
  3. The team sends the ETH by hand from the public pot wallet within 24 hours and adds each transaction to /proof. A row only appears there after the server has checked the transaction on the chain: it must have succeeded, gone to the winner, for exactly that amount.
  4. If fewer than three holders are on the board, the empty shares are not paid and stay in the wallet. The team notes it on /proof.

"Pot so far" and how it is counted

The pot is a share of creator fees, so the site shows an estimate read from the chain, labelled with its method and read time: pot so far = pot share × fees in today, where fees in today = ETH balance of the creator-fee wallet now − its balance at the start of the day + ETH that left that wallet since then (payouts, gas, anything the team lists on /proof).

The creator-fee wallet and the pot wallet, with live balances, are listed on /proof and in the footer once they exist.

Wallet

Playing never needs a wallet. Connecting one gives you a name on the board (your shortened address) and is how the pot checks that you hold the token. You only sign a message: it costs nothing and sends no transaction.

Sign-in

Network

FieldValue
NetworkRobinhood Chain (mainnet)
Chain id4663 (0x1237)
RPC URLhttps://{DOMAIN}/rpc (a same-origin proxy to a public node, POST only; some networks block the official endpoint)
CurrencyETH
Explorerrobinhoodchain.blockscout.com

Browser-extension wallets work; phone wallets via WalletConnect are offered where the button is shown.

API

JSON, cache-control: no-store. /api/course/today, /api/stats and /api/leaderboard allow any origin (CORS *).

EndpointReturns
GET /api/course/today{date, resetAt, par, holes:[{id, name, tier, par, json}], override, ai:{strokes, total, toPar, shots}}
GET /api/course/YYYY-MM-DDThe same for a finished day (404 for today and later)
POST /api/run/start {"mode":"daily"}{runId, date, holeIds, attemptsLeft, expiresAt}; 409 {error:"no_attempts", attemptsLeft:0, resetAt} when none are left; 10 per minute per IP
POST /api/run/submit {runId, shots}Accepted: {accepted:true, strokes:[a,b,c], total, toPar, holesInOne, rank, best}. Rejected: {accepted:false, reason}
GET /api/leaderboard?date=YYYY-MM-DDTop 50 of the day (default today) plus the AI caddie row (ai:true, rank:null): {date, par, frozen, paid, resetAt, eligibleCount, rows}. On frozen days each row has eligible (holder at the freeze) and, once paid, paidTx; eligible holders below rank 50 are listed too
GET /api/statsRounds, players and holes-in-one today, best score, average strokes, pot estimate, team wallets with live balance
GET /api/meYour attempts left, holder status and balance (with read time), whether you have played ranked today
GET /api/proofPayouts (and the dev-buy seed, if any), each verified against the chain, plus team wallets
GET /api/run/:id/replayShots of an accepted run, for finished days only
GET /api/healthzServer, RPC and hole library status
GET /token.jsonToken address, settings and thresholds (never cached)

Rejections

reasonHTTPMeaning
invalid_shots400Malformed or out of range, wrong order, or a shot after a hole was finished
too_many_shots400 / 413More than 24 shots, or a body over 8 KB
incomplete400A hole was not finished
expired410Older than 30 minutes, or the UTC day changed
already_submitted409This run was already submitted
unknown_run400No such run, or it belongs to someone else

Examples

curl https://{DOMAIN}/api/course/today
curl https://{DOMAIN}/api/stats
curl "https://{DOMAIN}/api/leaderboard?date=2026-10-28"

Try to cheat and watch the server say no. This starts one real guest round (it uses your daily guest attempt) and submits one made-up shot:

curl -c jar.txt -X POST https://{DOMAIN}/api/run/start \
  -H 'content-type: application/json' -d '{"mode":"daily"}'

curl -b jar.txt -X POST https://{DOMAIN}/api/run/submit \
  -H 'content-type: application/json' \
  -d '{"runId":"<runId from above>","shots":[[0,900,500]]}'
# {"accepted":false,"reason":"incomplete"}

Please keep to a few requests per second. Limits answer 429 with retryAfterSec.

FAQ

Do I need a wallet?

No. You can play and be ranked as a guest. A wallet gives you your name on the board and is how the pot checks that you hold the token.

Is the course really the same for everyone?

Yes. It is derived from the UTC date; check /api/course/today from two devices.

Is the pot guaranteed?

No. It is a team promise, paid by hand from creator fees. Every payout is posted with its transaction on /proof.

Is there a contract?

No. The game runs on one server; there is no contract of ours.

Can I cheat?

Shots are replayed on the server, which counts the strokes itself, and the team replays the winners before paying. See Fair play for what that does and does not stop.

Can I practice today's holes first?

They are hidden in Practice until you have played your ranked round. This is not enforced.