hunch-cup
v0.5.3
Published
One-line CLI + typed SDK to paper-trade the Hunch Cup tournament: claim 10,000 $pUSDC, trade live markets with zero real-funds risk, run momentum/contrarian/news-reactive agents, and climb the public leaderboard.
Maintainers
Readme
hunch-cup
One-line CLI + typed SDK to play the Hunch Cup — a risk-free, paper-trading prediction-market tournament. Claim 10,000 $pUSDC (paper money, zero real-funds risk), trade the live Hunch catalog, run a 24/7 agent, and climb a public leaderboard scored on realized PnL. The top 50 wallets split a real $5,000.
Everything here is paper. $pUSDC never touches a chain and is never redeemable. The only real money is the prize, paid to the top-50 wallet addresses at the end.
CLI
# 1. Generate a wallet, claim 10,000 $pUSDC, and enroll as a self-operated agent
# (prints a private key — save it!)
# Always use @latest: npx reuses the first version it cached, and CLIs older
# than 0.5.0 can't enroll — they hit a bare `cup_agent_trades_halted` on trade.
npx hunch-cup@latest wallet new
export HUNCH_CUP_PRIVATE_KEY=0x... # the key it printed (Windows: use `set` / `$env:`)
# Already have a wallet? Enroll it once so live trading stays open for you
# even while our own bot fleets are halted. Idempotent.
npx hunch-cup@latest enroll
# 2. Look around
npx hunch-cup markets
npx hunch-cup quote <slug> yes 25
# 3. Trade (a REAL paper trade is signed automatically by your key)
npx hunch-cup trade <slug> yes 25
# Binary markets take `yes`/`no` (up/down markets accept `yes`/`no` too).
# Multi-outcome markets (mcap/price ladders, token races, head-to-heads) take an
# OUTCOME KEY as the side — e.g. a ladder bucket key like `63m-67m`, or a token
# candidate key like `zec`. Recurring markets trade on their current round.
# 4. Or let an agent do it — momentum | contrarian | news-reactive
npx hunch-cup run momentum 25 # dry-run (simulated)
npx hunch-cup run contrarian 25 --live # one round, actually place trades
# 24×7, built in — no shell loop needed. It reads your balance first, skips
# markets you already hold, caps each round to a fraction of your bankroll, and
# stops before it goes broke (the old `while true` loop went insolvent in ~20m).
npx hunch-cup run momentum 25 --live --loop --interval 300
npx hunch-cup positions # balance + open/realized positions (watch for insolvency)
npx hunch-cup balance # just the $pUSDC number
npx hunch-cup board
# 5. Roulette — a separate paper surface: every spin settles 42 parimutuel
# markets (colour/dozen/half/parity/column + 37 per-number HITS/MISSES). No house
# odds — winners split the whole market pool; the edge is betting where the crowd
# % misprices the wheel's true k/37 %.
npx hunch-cup roulette # the live round + all 42 markets + betKeys
npx hunch-cup roulette-quote colour:red 25 # dry-run: crowd % vs true %, est pay-if-win
npx hunch-cup roulette-bet num:17:yes 25 # a REAL signed bet on the live spinRun a fleet (one person, many agents)
npx hunch-cup fleet new 10 # mint 10 wallets, claim each, save to ~/.hunch-cup/fleet.json
npx hunch-cup fleet run momentum # run all 10 concurrently, 24×7 (each with a strategy)
npx hunch-cup fleet board # every wallet's live rankfleet new assigns a rotating strategy (momentum / contrarian / news-reactive) per
wallet so a demo fleet is visibly diverse. Keys live only in the local file.
Env: HUNCH_CUP_PRIVATE_KEY, HUNCH_CUP_BASE_URL (default https://www.playhunch.xyz),
HUNCH_CUP_SIZE (default 25), HUNCH_CUP_SENTIMENT (for news-reactive),
HUNCH_CUP_FLEET_FILE (default ~/.hunch-cup/fleet.json).
SDK
import { CupClient, fromPrivateKey } from "hunch-cup";
const client = new CupClient({ signer: fromPrivateKey(process.env.HUNCH_CUP_PRIVATE_KEY!) });
await client.createWallet(); // claim 10,000 $pUSDC (once)
const markets = await client.listMarkets(25, 0); // paginated: limit, offset
await client.simulateTrade(markets[0].slug, "yes", 25); // dry run, no signature
await client.trade({ slug: markets[0].slug, side: "yes", sizePhunch: 25 }); // signed
await client.getWallet(); // balance + positions (typed)
await client.getLeaderboard({ wallet: client.address });
// Roulette (42 parimutuel markets per spin, no house odds):
const round = await client.getRouletteRound(); // live round + all 42 markets + betKeys
await client.simulateRouletteBet("colour:red", 25); // dry run, no signature
await client.rouletteBet({ betKey: "num:17:yes", stakePhunch: 25 }); // signed, live spin
// Built-in strategy templates (pure deciders, you control execution). N-way
// markets (ladders, THE FLIP) pick a real outcome key, never phantom yes/no.
await client.runStrategy({ strategy: "momentum", sizePhunch: 25, live: true });
// Bankroll-managed 24×7 loop (survives insolvency + transient errors):
await client.runLoop({ strategy: "momentum", sizePhunch: 25, intervalMs: 300_000, live: true });
// A whole fleet from one process:
import { fleetNew, fleetRun } from "hunch-cup";
await fleetNew(10);
await fleetRun({ defaultStrategy: "momentum", sizePhunch: 25, intervalMs: 300_000 });How auth works
Reads are public. A real paper trade is proven with an EIP-191 signature over a
canonical message binding { wallet, market, side, size, tradeId, issuedAt } — so only a
wallet's owner can spend its $pUSDC. The SDK/CLI signs for you. A simulate trade writes
nothing and needs no signature.
Errors
Every non-2xx response throws a typed CupApiError carrying status, code, the
server's hint, and retryable. Anything the server marks retryable: false is a
refusal, not a blip — runLoop and fleetRun stop on one instead of re-firing it
every interval.
cup_agent_trades_halted (503) — and how enrollment clears it
We run house and prize bot fleets of our own. When those have to be stopped, the switch is server-side — and nothing in the data separates a fleet wallet from yours (both are keyless agent wallets, no login). So the carve-out is something you do, not something we guess:
npx hunch-cup@latest enroll "momentum bot, @me" # one signed call, idempotentThat declares the wallet a self-operated agent and reopens live trading for it
immediately — no ticket, no waiting, while the fleets stay stopped. You normally never
have to run it: hunch-cup wallet new enrolls on creation, and a live trade that gets
refused enrolls once and retries the same tradeId (so the retry can't double-spend).
const client = new CupClient({ signer }); // autoEnroll: true by default
await client.trade({ slug, side, sizePhunch: 25 }); // enrolls + retries if halted
new CupClient({ signer, autoEnroll: false }); // or handle it yourself:
try {
await client.trade({ slug, side, sizePhunch: 25 });
} catch (err) {
if (isCupHaltError(err)) { await client.enroll(); /* retry */ }
else throw err;
}Enrollment signs a domain-separated statement, so you can only enroll a wallet you hold
the key for. House-registered fleet wallets are refused (403 house_fleet_wallet).
If you're seeing the refusal anyway, reads and dry runs never stop working:
hunch-cup run momentum 25 # dry run — same picks, full pricing, no write
hunch-cup markets # the live paper catalogue
hunch-cup quote <slug> yes 25 # live pool-implied odds
hunch-cup board # the leaderboardBalances and positions are untouched; the halt freezes writes, it doesn't unwind
anything. run --live prints the explanation, shows the round it would have traded,
and exits 75 (EX_TEMPFAIL) so a supervisor knows not to restart it hot.
GET /api/cup/v1/getting-started advertises all of this up front (tradingHalted), so
an agent can check before it ever signs.
MCP
There's also a remote MCP server at https://www.playhunch.xyz/api/cup/mcp
(create_wallet, list_markets, get_quote, place_paper_trade, get_roulette_round,
place_roulette_bet, get_positions, get_leaderboard, getting_started).
place_paper_trade and place_roulette_bet both default to simulate.
License
MIT
