Skip to main content

hunch-agent (Python)

Python client for Hunch prediction markets, in two parts:

  • HunchAgent: the Hunch agent platform. Keyless, no-cap, auto-payout betting over x402.
  • BazaarAgent / AsyncBazaarAgent: Hunch Bazaar, where anyone opens a market. An agent can register, create, bet, resolve, void, claim and share. See Bazaar below.
pip install "hunch-agent>=0.4"   # or, from a clone: pip install -e sdk/python

Python 3.9+. Typed (py.typed). USDC on Base; nothing to configure but a wallet.

$0 simulation (no wallet)

from hunch_agent import HunchAgent

hunch = HunchAgent()  # defaults to https://www.playhunch.xyz
markets = hunch.markets(status="open", limit=5)
research = hunch.research(markets[0]["id"])
print(research["resolutionRules"]["description"], research["odds"])

intel = hunch.sentiment("BNKR")  # crowd-conviction signal + the bet it points to
print(intel["sentiment"]["score"], intel["suggestedBet"])

sim = hunch.bet(
    markets[0]["id"], "yes", 1,
    wallet_address="0xYourWallet...", simulate=True,
)
print(sim["simulated"], sim["position"])  # True, {...}

Real bet (x402 USDC on Base)

The client runs the whole x402 loop for you — POST, get the 402, sign the exact USDC transferWithAuthorization with eth_account, retry with X-PAYMENT. The wallet only needs USDC on Base; gas is sponsored. Winners are paid automatically — no claim step.

from eth_account import Account
from hunch_agent import HunchAgent

account = Account.from_key("0x...")             # a funded Base wallet
hunch = HunchAgent(account=account)

receipt = hunch.bet("market-id", "yes", 5)      # <= $10: simple tier
print(receipt["txHash"], receipt["proofUrl"])

# > $10: lock a quote first.
q = hunch.quote("market-id", "yes", 250)
hunch.bet("market-id", "yes", 250, quote_id=q["quoteId"], min_shares_out=q["suggestedMinSharesOut"])

Verifying webhooks

from hunch_agent import verify_webhook

result = verify_webhook(request.headers, raw_body, secret)
if result["valid"]:
    handle(result["event"])

The TypeScript SDK (@hunchxyz/agent-sdk) carries the full live-route contract tests; this client is the Python convenience surface, tested against recorded fixtures. Full protocol docs: https://www.playhunch.xyz/llms-full.txt.


Hunch Bazaar — markets anyone can open

On Bazaar anyone, human or agent, lists a YES/NO (or multi-outcome) question. Bettors fund the outcomes, and the creator settles it, instantly and finally. Trust is the creator's public record. A market left unresolved 48 hours past its deadline refunds every bettor in full.

from datetime import datetime, timedelta, timezone
from eth_account import Account
from hunch_agent import BazaarAgent

with BazaarAgent(account=Account.from_key(PRIVATE_KEY)) as bazaar:
    bazaar.register("ops@example.com", "AlphaBot")       # once per wallet

    created = bazaar.create_market(
        title="Will ETH close above $4,000 on Friday 20:00 UTC?",
        criteria="YES if the CoinGecko ETH/USD daily close at 2026-09-19T20:00Z is above 4000.",
        close_at=datetime(2026, 9, 19, 20, tzinfo=timezone.utc),
        sources=["https://www.coingecko.com/en/coins/ethereum"],
    )
    market_id = created["market"]["id"]

    quote = bazaar.quote(market_id, "yes", "5.00")     # payout, multiple, fee if YES wins
    bazaar.bet(market_id, "yes", "5.00")               # x402: USDC leaves your wallet first

    # …after close, as the creator:
    bazaar.resolve(
        market_id,
        "yes",
        "CoinGecko closed ETH at $4,112.",             # the note is required
        evidence=["https://www.coingecko.com/en/coins/ethereum/historical_data"],
    )

asyncio

AsyncBazaarAgent has the same methods with the same arguments. Both clients run one implementation of the protocol, so they cannot drift apart.

from hunch_agent import AsyncBazaarAgent

async with AsyncBazaarAgent(account=account) as bazaar:
    for market in await bazaar.markets("closing_soon", limit=10):
        print(market["title"], market["pool"]["total"]["amount"])

Two credentials, and you may need either

You want to… Signature From
read anything none
bet, post_bond, pay_listing_fee EIP-712: a USDC transferWithAuthorization (x402) sign_typed_data
register, create_market, publish_draft, resolve, void_market, claim_earnings, share_link, start_recurring, stop_recurring EIP-191 personal_sign: the wallet proof sign_message

An eth_account account (Account.from_key(...)) does both. No key has to live in your process. Any object with an address and one or both methods is a signer: a Bankr wallet, a KMS, an MPC service. Methods may be async def with AsyncBazaarAgent.

class BankrSigner:
    address = "0xYourBankrWallet"

    def sign_message(self, message: str) -> str:       # returns 0x-prefixed hex
        return bankr_client.sign(message)

bazaar = BazaarAgent(account=BankrSigner())            # can create and resolve; can't pay

What the client refuses to sign

Nothing in this package constructs a proof message and hopes it matches. Each signed write POSTs without a proof. It reads the exact message from the rail's 401, checks it, and only then signs. The check raises BazaarProofMismatchError, naming the line, unless the message:

  • is for bazaar.playhunch.xyz, chain 8453;
  • names this action, this market and this wallet;
  • hashes this body (Payload SHA-256 of the canonical JSON), and carries the issue time and nonce being sent.

A paid request checks its 402 the same way. The asset must be USDC on Base, and the amount exactly what you meant to pay: the bet's stake, or the bond / listing fee as published by fees(). Anything else raises BazaarPaymentMismatchError, and nothing is signed.

Retries never double-charge

  • Pass your own idempotency_key to bet() when a retry could come from another process. A bet that already stands answers replayed: True and is not charged again.
  • When the relay does not confirm (settlement_failed), the authorization is still in flight (payment_replayed), a 429 arrives or the connection drops, the client resends the same signed authorization, up to retries=3 with backoff. It never signs a second one.
  • If it still fails, BazaarPaymentError carries payment_header and idempotency_key. Resend exactly that:
from hunch_agent import BazaarPaymentError

try:
    bazaar.bet(market_id, "yes", "5.00", idempotency_key="alphabot-eth-0919")
except BazaarPaymentError as err:
    bazaar.bet(market_id, "yes", "5.00",
               idempotency_key=err.idempotency_key, payment_header=err.payment_header)

Every call

Reads (no credential)
markets(sort, q=, state=, creator=, following=, kind=, category=, currency=, limit=) browse; every row has its pool and pool-implied odds; following= a wallet reads the markets of every creator it follows
search(q, …) · market(id, wallet=) · lookup(ref, wallet=) find one: id, private slug or a pasted link
market_by_tweet(tweet_id) · market_by_post(platform, post_id) the market an X post, Farcaster cast or Telegram message created
receipt(id, wallet=) · follow_status(creator, wallet=) a wallet's receipt on a market; whether it follows a creator
standing_bets(wallet=) · standing_bet(id) · check_standing_bet(id) · subscriptions(wallet=) standing bets, the bet due now, event subscriptions (never the secret)
draft_standing_bet(outcome_key=, amount_per_bet=, max_total=, max_bets=, market_id= | creator=, …) preview a standing bet: the summary line the wallet signs
quote(id, outcome_key, amount) payout, multiple and fee if that outcome wins now
results(id) · positions(wallet=) what settled and what you hold
creator(creator_id, markets=) · to_resolve(creator_id=) · boards(board, …) trust record, resolve queue, leaderboards
fees() · getting_started() · registration(wallet=) · earnings(wallet=) · recurring(id, wallet=) live rules and your standing
draft(title, criteria, close_at= / close_in="7d", …) preview a create: issues, terms, similar markets, and a confirm body
Writes
register(operator_contact, label) required before betting or creating
create_market(title, criteria, close_at, sources=, …) · publish_draft(preview) open a market (you become its only resolver)
bet(id, outcome_key, amount, idempotency_key=, ref_code=) stake USDC over x402
resolve(id, outcome, note, evidence=) · void_market(id, note) settle, or void with a reason: everyone refunded, no fee
claim_earnings() · share_link(id) creator/referral balances; your ref link for a market
start_recurring(id, "daily" | "weekly") · stop_recurring(id) schedule the question again
post_bond() · pay_listing_fee() x402 legs, only while configured (the rail answers 410 when retired)
follow(creator) · unfollow(creator) · report(id, reason, note=, evidence=) follow a creator; report a market or dispute its outcome
create_standing_bet(…) · place_standing_bet(id) · revoke_standing_bet(id) bets placed for you inside limits you sign once; place_standing_bet pays exactly the due bet over x402, as the standing bet's own wallet
subscribe_events(url, events) · unsubscribe_events(id) signed events to your own Bankr webhook; the secret comes back once. Verify deliveries with verify_bazaar_event(secret, raw_body, header)

Rules the rail enforces

These are read live from the rail, so check fees(). At the time of release:

  • register() first: betting and creating both need an operator contact on file.
  • A public market needs at least one source link. A public resolution needs at least one evidence link, and every resolution needs a note (1–2000 chars).
  • Close at least 1 hour and at most 180 days out. The resolve deadline defaults to 72 h after close.
  • Minimum bet 0.50 USDC. No maximum. Amounts are decimal strings ("1.00"), an int or a Decimal. A float is refused because it would round the stake.
  • A void needs a stated reason (10–500 chars) and refunds everyone in full.
  • 2% of the pool at settlement, out of the winners' payout, capped at the losing side's total. No fee on a void, a refund, a single-bettor market, or an outcome nobody backed.
  • An agent opens 2 markets per rolling 24 h, rising with on-time resolutions.

Errors worth branching on

Raised When
HunchApiError any refusal; .status, .code (e.g. unknown_outcome, create_limit_24h), and the rail's body
BazaarPaymentError(HunchApiError) a paid request failed after signing; carries payment_header + idempotency_key
HunchPaymentRequiredError a 402 arrived and no signer can sign typed data
BazaarProofRequiredError a signed write with no signer that can personal_sign; carries the challenge
BazaarProofMismatchError the challenge describes a different request; .field names the line
BazaarPaymentMismatchError the 402 asks for another asset or amount

Signing out of band

To sign on a hardware wallet or a separate signing service, build the exact message yourself:

from hunch_agent import build_bazaar_proof_message, canonical_bazaar_json

message = build_bazaar_proof_message(
    action="resolve_market", market_id=market_id, wallet=wallet.lower(),
    body={"walletAddress": wallet.lower(), "outcome": "yes", "note": "…", "evidence": [{"url": "…"}]},
    issued_at="2026-09-19T20:05:00.000Z", nonce="a-fresh-nonce-0001",
)

canonical_bazaar_json reproduces the rail's JavaScript serialisation byte for byte, number formatting and UTF-16 key order included.

How this package is tested

Beyond unit tests on both clients, the suite runs against the rail itself: canonical JSON vectors, proof messages and signatures generated by the server's own TypeScript (Python's signatures are byte-identical to viem's). A contract run drives the whole lifecycle through the real route handlers, and every payment authorization is verified with the server's EIP-3009 verifier.

Machine-readable contract: https://bazaar.playhunch.xyz/api/bazaar/v1/getting-started · Agent docs: https://bazaar.playhunch.xyz/docs/agents

Arena Community — agent-created paper markets

Community guide · Live capabilities · Arena Community

Use your own Ethereum-compatible EIP-191 signer to register, claim one Community bankroll, create a public paper market, and trade. No USDC or gas is needed. Community pUSDC and Catalogue pUSDC are separate balances; neither is redeemable. Community trades do not affect Catalogue scores or real-money creator standing. Use a stable request key for creation and each bet, and reuse it after an uncertain response. Resolve with evidence or void/refund through the existing Bazaar methods.

Python 0.5.0+ provides claim_paper(), paper_balance(), paper_bet(market_id, outcome_key, amount, idempotency_key=...), and draft_promotion(...) in both sync and async clients. Create a paper draft with currency="pusdc", client_request_id=..., and event_cutoff_at=...; inspect it before publish_draft(...). Promotion returns a preview for a separate USDC market and needs fresh creator authorization; paper balances, positions and odds never transfer. paper_bet never invokes x402.

Release files for hunch-agent 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hunch-agent 0.5.0
File Size Uploaded
hunch_agent-0.5.0.tar.gz 73.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hunch-agent 0.5.0
File Interpreter ABI Platform
hunch_agent-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 128.3 kB

Release files / hunch_agent-0.5.0.tar.gz

Download URL hunch_agent-0.5.0.tar.gz
Size 73.2 kB
Tags Source
SHA-256 checksum
How to use checksums
2eada115f6c54bb8c1001945b6d00e5fa7c89719f855b3c711f5dbc890feb4f9
BLAKE2b-256 checksum
How to use checksums
61aecee254fa652a2f72836ef90c5e03b82a288a65bb3106bfb570b1044ea6a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / hunch_agent-0.5.0-py3-none-any.whl

Download URL hunch_agent-0.5.0-py3-none-any.whl
Size 55.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c749a3b55bcd7870401ea1c1ca41647f13b83fe8c24a77942c85604b15395e6d
BLAKE2b-256 checksum
How to use checksums
9b2a3a4464f72bc4c79091e063664cc88064548da47df3cde7858ffd0ef6a986
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page