diff --git a/hunch/SKILL.md b/hunch/SKILL.md index 49bdb69294..177e81890f 100644 --- a/hunch/SKILL.md +++ b/hunch/SKILL.md @@ -106,8 +106,10 @@ frens" → nothing; (5) "what was $BNKR's all-time high?" → answer, no market; 1. **Discover** — turn a phrase or post into matched markets. `GET /api/partner/discover?q=` (free-text / cashtags), or `GET /api/partner/discover?post=` (claim-LLM extraction). - → `{ count, matches: [{ market, odds, stats, matchKind, … }] }` — each match - is **nested** under `matches[].market`. No match → offer nothing. + → `{ count, matches: [{ market, odds, stats, headline, matchKind, … }] }` — + each match is **nested** under `matches[].market`, and carries a + screenshot-ready **`headline`** (title · odds · social proof · close). No + match → offer nothing. 2. **Quote** — live odds + a full cost breakdown for a chosen market. `GET /api/partner/quote?marketId=&side=&sizeUsd=` → `{ market, odds, stats, tokenSnapshot, quote{ priceCents, feeUsd, netUsd, @@ -137,6 +139,7 @@ Every market object is the shared ref documented in `references/market-ref.md`. | `/api/partner/proof/{tradeId}` | GET | on-chain proof of a settled bet | `proof.md` | | `/api/partner/positions` | GET | a wallet's portfolio + PnL | `positions.md` | | `/api/partner/result` | GET | how a market resolved + payout | `result.md` | +| `/api/partner/resolved` | GET | a wallet's settled bets + ready-to-post win-broadcast | `resolved.md` | | `/api/partner/trending` | GET | hottest markets + daily-post digest | `trending.md` | | `/api/partner/mint` | POST | mint a market on demand (advanced, dark) | `mint.md` | @@ -174,11 +177,38 @@ scheduled "what's trending on Hunch" post, or surface the top entry unprompted. Read-only, cached, deterministic id selection (the model never picks). See `references/trending.md`. +## Win-broadcast (close the loop loudly) + +A silent settlement is a wasted viral moment. When a market a Bankr user bet on +resolves, **reply in the original bet thread** with the result + on-chain proof + +a rematch hook — the dopamine for the winner, the FOMO for everyone watching. + +`GET /api/partner/resolved?wallet=<0x…>` returns the wallet's **settled** bets +(won + lost), newest first. Each entry carries a ready-to-post **`broadcast`** +line, plus a wallet-level **`digest`** (a "here's how it settled" recap). Read-only, +no money path; positions are keyed to the paying wallet exactly as the bet wrote +them. Two uses: + +- **In-thread reply** — when a bet settles, reply to its original cast with the + entry's `broadcast` (it already ends with the project tags — post verbatim), e.g. + > 🎉 Won $8.40 on $BNKR → $100M (YES) — settled in USDC on Base. + > Proof: playhunch.xyz/markets/bankr-100m. Run it back? Tag @bankrbot. @playhunchxyz + Losses get a **rematch** nudge, never a dunk. +- **Recap post** — drop `digest.text` as a "your week on Hunch" post. + +**Stateless — you dedupe.** Hunch reports the current resolved set; the bot tracks +what it has already broadcast (by wallet + `marketId`) so a settled bet is +announced once. Poll on a cadence, or check right after `result` flips to +`resolved`. See `references/resolved.md`. + ## Money-path rules (do not break) - **You never pick the market id or size from a model guess.** Discovery's deterministic ranker returns the id; you echo it. The user picks side + size. - **Bets are $1–$10** (x402 ceiling). Reject anything outside the band. +- **Offer sized chips, don't demand a number.** Pre-select the market's + `defaultTicketUsd`, surface `[$1] [$5] [$10]`, and accept any custom $1–$10. + One tap from "what are the odds" to a placed bet is the whole point. - **Idempotent.** Reuse the same `idemKey` on retries — a replay returns the original receipt, never a second bet. - **Always show the disclosure line** from the market's category before @@ -239,12 +269,85 @@ text cannot reach the money path. ## Reply shape -When discovery matches, render the bot's `Take YES / Take NO` UI: +When discovery matches, render the bot's `Take YES / Take NO` UI. Lead with the +server-built **`headline`** — it already packs the title, the live odds **and** +the social proof (bet count + pool), so the reply reads like a real market +instead of a coin-flip — then the distance hook, the disclosure, **sized** +actions, and the **attribution tags**: -> **{market.question}** -> YES {odds.yesPriceCents}¢ · NO {odds.noPriceCents}¢ · closes {market.deadlineLabel} +> **{match.headline}** +> {distance hook — market-cap markets only, from the quote's `tokenSnapshot`} > _{category disclosure}_ -> [Take YES] [Take NO] +> [Take YES] [Take NO] · size [$1] [$5] [$10] +> {match.headline already ends with the @tags — keep them} + +- **`headline`** rides on every discover / trending match — render it verbatim as + the bold lead (it already opens with the market's short title); the numbers are + formatted server-side. Example: + `"$BNKR → $100M · YES 12¢ / NO 88¢ · 142 bets · $1.2k pool · closes Jun 30 · @playhunchxyz"`. + Don't strip it back to bare odds — the **depth** (bets + pool) is the social + proof that makes a reply travel, and the **trailing @tags credit the project** + (see *Project attribution* below). With no bets yet it reads `… · be the first + to bet · …`, a first-mover nudge rather than a hollow `0 bets`. +- **Distance hook** (market-cap markets): after you `quote`, fold the live + `tokenSnapshot` into one line — `"📈 $52M now · +92% to $100M"` (from + `distanceToTargetPct` + `targetMarketCapUsd`; `reachedTarget: true` → "already + past $100M ✅"). It turns a price answer into a reason to act. `null` for non + market-cap markets — just omit the line. +- **Size chips** — surface `[$1] [$5] [$10]` (the band is **$1–$10**) with the + market's `defaultTicketUsd` pre-selected. Don't make the user type a number; + still accept any custom $1–$10 amount. + +For an N-way market (`market.outcomes` non-null) the headline reads +`"… · 6 outcomes · …"` (no YES/NO); list the rungs with their `impliedPct` from +the quote `ladder`, mark the `isCurrent` one, and let the user pick a rung + size. + +## Project attribution (tag the project — every reply) + +**Every Hunch reply credits the project with @-tags — treat this like the +disclosure: non-negotiable, never stripped.** It is built server-side so the +numbers and handles are always right; your job is to render it, not to compose it. + +- **`@playhunchxyz` (Hunch) is always tagged**, plus the market's **token + project** when there's a verified one — e.g. a `$LFI` market tags + `@playhunchxyz @lienfiapp`, a `$VVV` market tags `@playhunchxyz @AskVenice`. +- **Where it already is:** the `headline` (discover + trending) **ends with the + tags** — render the headline verbatim and they ride along. The **`broadcast`** + and **`digest.text`** strings (win-broadcast / trending / settled recap) + **already contain the tags** — post them verbatim. +- **Where you add it:** the **`quote`** and **`positions`** responses carry a + separate **`tags`** field (e.g. `"@playhunchxyz @lienfiapp"`). End those replies + with that line, verbatim, as the last line. +- **Don't double-tag and don't add your own.** `$BNKR`'s own account is + `@bankrbot` (you), so a `$BNKR` market intentionally tags only `@playhunchxyz` — + the server already drops the self-tag. Never invent or substitute a handle; if a + token has no verified project tag the reply simply credits `@playhunchxyz`. + +## Funding a bet (insufficient balance) + +A `422 insufficient_balance` means the paying wallet doesn't hold enough **Base +USDC** for the bet — the most common reason a first-time user can't bet yet. +Don't dead-end the conversation. Offer a way forward, but treat funding as a +**money action that needs its own explicit consent**: + +1. **Lower the bet** to fit the balance (e.g. bet $3.50 of a $3.72 balance — + leave a small margin; never stake the whole cent-rounded amount). Re-quote at + the smaller size, confirm, retry with the **same `idemKey`**. +2. **Top up by swapping another token → USDC on Base — only with permission:** + - **Never pick a token or execute a swap automatically.** A swap moves the + user's funds. + - **Show the wallet's swappable balances and ASK which token** (and how much) + to convert to USDC on Base. + - **Get explicit confirmation for that specific swap** before executing it — + the same consent bar as the bet itself. + - Only after the user confirms: Bankr performs the swap, then retry the bet + with the **same `idemKey`**. +3. Or the user deposits USDC on Base themselves. + +> **Hard rule:** no token is ever swapped without the user naming it and +> approving that one swap. When in doubt, ask — don't convert. And never blindly +> retry the same amount: `insufficient_balance` keeps reverting until the balance +> or the size changes. ## Troubleshooting @@ -252,8 +355,9 @@ When discovery matches, render the bot's `Take YES / Take NO` UI: |---|---|---| | `402` | Payment required — the trade returned an x402 challenge. | Expected on the first `POST /trade`. Sign the EIP-3009 authorization, base64 it into `X-PAYMENT`, resubmit the **same** body + `idemKey`. | | `409` | `market_closed` — the market isn't open / its deadline passed; **or** `idempotency_conflict` — the `idemKey` was reused with a **different** body. | Check `error`. `market_closed` → re-run `discover` for a live market. `idempotency_conflict` → mint a fresh `idemKey` per distinct bet (a replay of the *same* body returns the original receipt, which is safe). | -| `422` | Bad size (outside **$1–$10**) or a missing field. | Clamp `sizeUsd` to 1–10; ensure `marketId`, `side`, `walletAddress`, `idemKey` are present. | +| `422` | `insufficient_balance` — the wallet doesn't hold enough Base USDC for the bet (e.g. it tried to stake its **whole** cent-rounded balance, which is fractionally short); **or** bad size (outside **$1–$10**); **or** a missing field. | For `insufficient_balance`: see **Funding a bet** above — **lower `sizeUsd`** (leave a margin), or top up by **swapping another token → USDC on Base _only with the user's explicit, per-swap permission_ (ask which token first — never auto-swap)**, or have them deposit USDC. **Do NOT blindly retry the same amount** — it keeps reverting. Otherwise clamp `sizeUsd` to 1–10 and ensure `marketId`, `side`, `walletAddress`, `idemKey` are present. | | `404` | Unknown market, or the partner API is disabled. | Re-run `discover` for a fresh id; never hand-craft a market id. If everything 404s, the endpoint may be off (see Safety). | +| `503` | `settlement_failed` / `settlement_recording_failed` — a genuine transient server/relay problem. **Funds were not moved.** | This one *is* safe to retry shortly with the **same** `idemKey`. If it persists, settlement is down — surface that, don't loop. (Contrast with `422 insufficient_balance`, which retrying never fixes.) | | `count: 0` / `silent: true` on discover | No live market matches (or the post is non-actionable). | **Offer nothing.** Never substitute a loosely related market. | Idempotency: use one `idemKey` (a UUID) per intended bet; reuse it verbatim on @@ -296,10 +400,11 @@ any network retry so a dropped response can never double-settle. - `references/proof.md` — on-chain proof read for a settled bet. - `references/positions.md` — wallet portfolio lookup. - `references/result.md` — market resolution read. +- `references/resolved.md` — a wallet's settled bets + the win-broadcast digest. - `references/trending.md` — the trending feed + daily-post digest. - `references/mint.md` — on-demand market mint (advanced, flag-gated). - `references/transcripts.md` — worked transcripts (bet, claim-LLM, injection, - multi-market, portfolio, result, silence). + multi-market, portfolio, result, win-broadcast, funding/swap, silence). - `scripts/walkthrough.sh` — a runnable discover → quote → trade(402) example. - `x402-registry.json` — the x402 service listing for go-live registration, plus the **pinned** `allowedOrigins` (host pinning) and `signingPolicy` (pre-sign diff --git a/hunch/references/discovery.md b/hunch/references/discovery.md index d4f6b86bcf..b2921d75e3 100644 --- a/hunch/references/discovery.md +++ b/hunch/references/discovery.md @@ -73,7 +73,8 @@ else stays lexical; the raw sentence is a last-resort fallback only. "matchedTerms": ["$bnkr"], "reason": "cashtag $bnkr matches market token", "odds": { "yesPriceCents": 12, "noPriceCents": 88 }, - "stats": { "totalBets": 142, "totalPoolUsd": 1240, "yesPoolUsd": 150, "noPoolUsd": 1090, "feeUsd": 24.8 } + "stats": { "totalBets": 142, "totalPoolUsd": 1240, "yesPoolUsd": 150, "noPoolUsd": 1090, "feeUsd": 24.8 }, + "headline": "$BNKR → $100M · YES 12¢ / NO 88¢ · 142 bets · $1.2k pool · closes Jun 30 · @playhunchxyz" } ] } @@ -95,6 +96,7 @@ else stays lexical; the raw sentence is a last-resort fallback only. | `reason` | Human explanation of why it matched — useful for debugging, not for the reply. | | `odds` | Live `{ yesPriceCents, noPriceCents }` (falls back to `50/50` if the book can't be read). For ladder markets odds are returned by `quote`, not here. | | `stats` | Live bet activity — see below. | +| `headline` | **Screenshot-ready one-liner**, built server-side: `title · odds · social proof (bets + pool) · close · @tags`. Render it verbatim under the question — the numbers **and the project @tags** are formatted for you. It **ends with the attribution** (`@playhunchxyz` + the token project, e.g. `… · @playhunchxyz @lienfiapp`) — keep those tags, they credit the project (see SKILL.md *Project attribution*). N-way markets read `N outcomes` instead of YES/NO; markets with no bets yet read `be the first to bet`. This is the line that makes a reply travel — lead with it instead of bare odds. | #### `stats` (bet activity) @@ -105,7 +107,9 @@ else stays lexical; the raw sentence is a last-resort fallback only. | `yesPoolUsd` / `noPoolUsd` | USD pooled per side (both `0` for ladder markets — per-rung backing is on the quote's `ladder`). | | `feeUsd` | Fees accrued so far, USD. | -Surface real depth ("142 bets · $1,240 pooled") instead of bare odds when it helps. +The `headline` already folds this depth in (`142 bets · $1.2k pool`) — render it +verbatim and the social proof comes for free, no assembly. Reach into the raw +`stats` fields only when you want a custom layout. ### Response — `post` mode (claim-LLM) diff --git a/hunch/references/positions.md b/hunch/references/positions.md index 79ad024789..ac44437486 100644 --- a/hunch/references/positions.md +++ b/hunch/references/positions.md @@ -41,7 +41,8 @@ Use for: "show my Hunch bets", "how are my positions doing", "what's my PnL". "proofUrl": "https://basescan.org/tx/0x…", "filledAt": "2026-06-01T12:00:00.000Z" } - ] + ], + "tags": "@playhunchxyz @lienfiapp" } ``` @@ -55,9 +56,11 @@ Use for: "show my Hunch bets", "how are my positions doing", "what's my PnL". | `maxPayoutUsd` | Payout if this side wins ($1 / share). | | `status` | `open`, `resolved-won`, or `resolved-lost`. | | `proofUrl` | Entry-settlement on-chain proof (BaseScan), when available. | +| `tags` | **Project attribution** for the portfolio reply's footer — `@playhunchxyz` + up to two of the held markets' token projects. Render as the last line, verbatim (SKILL.md *Project attribution*). | ### Reply shape > **Your Hunch bets** (1 open · 1 resolved · PnL +$1.42) > • **$BNKR → $100M** — YES, $5 @ 12¢ → 15¢ · +$1.25 · open > • **$HUNCH flips $LFI** — YES, $3 · resolved-lost +> @playhunchxyz @lienfiapp diff --git a/hunch/references/quote.md b/hunch/references/quote.md index 110c333f19..ac7d820b3e 100644 --- a/hunch/references/quote.md +++ b/hunch/references/quote.md @@ -44,13 +44,18 @@ the pricing call between `discover` and `trade`. Read-only, CORS-open, cached "netUsd": 4.9, "shares": 40.83, "feeRecipient": "Hunch market treasury" - } + }, + "tags": "@playhunchxyz" } ``` - `market` — the shared [market ref](./market-ref.md). - `odds` — live `{ yesPriceCents, noPriceCents }` (50/50 fallback if unreadable). - `stats` — bet activity (same shape as discover; see `discovery.md`). +- `tags` — **project attribution** to render as the **last line** of the quote + reply, verbatim: `@playhunchxyz` + the token project when verified (e.g. a `$LFI` + quote returns `"@playhunchxyz @lienfiapp"`). Always present. Treat it like the + disclosure — don't strip it (SKILL.md *Project attribution*). - `quote` — the cost breakdown for `side` at `sizeUsd`: | Field | Meaning | @@ -108,7 +113,8 @@ added: }, "tokenSnapshot": null, "quote": { "side": "63m-67m", "priceCents": 35, "grossUsd": 5, "feeUsd": 0.1, - "netUsd": 4.9, "shares": 14, "feeRecipient": "Hunch market treasury" } + "netUsd": 4.9, "shares": 14, "feeRecipient": "Hunch market treasury" }, + "tags": "@playhunchxyz" } ``` @@ -125,13 +131,23 @@ added: ### Reply shape -> **{question}** -> YES {yesPriceCents}¢ · NO {noPriceCents}¢ · closes {deadlineLabel} -> _{category disclosure}_ -> [Take YES] [Take NO] +By quote time you have the discover `headline` **and** the live `tokenSnapshot`, +so add the distance hook and sized actions: -For a ladder, list rungs with `impliedPct` and let the user pick one; mark the -`isCurrent` rung. +> **{discover `headline`}** +> {distance hook} +> _{category disclosure}_ +> [Take YES] [Take NO] · size [$1] [$5] [$10] + +- **Distance hook** (market-cap markets): `"📈 $52M now · +92% to $100M"`, from + `tokenSnapshot.currentMarketCapUsd` / `distanceToTargetPct` / + `targetMarketCapUsd` (`reachedTarget: true` → "already past $100M ✅"). It turns + a price answer into a reason to bet. `tokenSnapshot` is `null` for every + non-market-cap market — just omit the line. +- **Size chips** — `[$1] [$5] [$10]` (band $1–$10) with `defaultTicketUsd` + pre-selected; accept any custom $1–$10. Don't make the user type a number. +- **Ladder** — list rungs with their `impliedPct`, mark the `isCurrent` rung, and + let the user pick one + a size (no YES/NO on an N-way market). ### Errors diff --git a/hunch/references/resolved.md b/hunch/references/resolved.md new file mode 100644 index 0000000000..c08d74247c --- /dev/null +++ b/hunch/references/resolved.md @@ -0,0 +1,91 @@ +# Win-broadcast — `GET /api/partner/resolved` + +`GET https://www.playhunch.xyz/api/partner/resolved?wallet=<0x…>` + +A wallet's **settled** Hunch bets (won + lost), newest first, each with a +ready-to-post **`broadcast`** line, plus a wallet-level **`digest`**. This is the +loop-closing surface: when a bet settles, reply in its original thread so the win +(and the on-chain proof) is *seen*, not silent. Read-only — no payment, no money +path. Positions are keyed `bankr:`, exactly as the x402 trade wrote them. + +404 when the partner API is off; `422 wallet_required` without a valid wallet; +CORS-open. + +## Request + +``` +GET /api/partner/resolved?wallet=0x1234…abcd +``` + +## Response + +```json +{ + "meta": { "name": "…", "version": "hunch-partner-api-v1", "generatedAt": "…", "docsUrl": "…" }, + "wallet": "0x1234…abcd", + "count": 2, + "summary": { "wonCount": 1, "lostCount": 1, "netPnlUsd": -1.6, "totalWonUsd": 8.4 }, + "resolved": [ + { + "marketId": "bankr-100m-mcap-2026-06-30", + "slug": "bankr-100m", + "question": "Will $BNKR reach a $100M market cap by June 30, 2026?", + "shortTitle": "$BNKR → $100M", + "side": "yes", + "outcomeLabel": "YES", + "won": true, + "shares": 40.83, + "stakedUsd": 5, + "avgEntryCents": 12, + "currentCents": 100, + "pnlUsd": 3.4, + "maxPayoutUsd": 8.4, + "status": "resolved-won", + "appUrl": "https://www.playhunch.xyz/markets/bankr-100m", + "proofUrl": "https://basescan.org/tx/0x…", + "filledAt": "2026-06-01T00:00:00.000Z", + "broadcast": "🎉 Won $8.40 on $BNKR → $100M (YES) — settled in USDC on Base. Proof: https://www.playhunch.xyz/markets/bankr-100m. Run it back? Tag @bankrbot. @playhunchxyz" + } + ], + "digest": { + "title": "🎯 Settled on Hunch", + "lines": ["✅ Won $8.40 — $BNKR → $100M (YES)", "❌ $HUNCH flips $LFI (YES)"], + "text": "🎯 Settled on Hunch\n✅ Won $8.40 — $BNKR → $100M (YES)\n❌ $HUNCH flips $LFI (YES)\n\nRun it back — tag @bankrbot to bet, settles in USDC on Base. @playhunchxyz" + } +} +``` + +## Fields + +Each `resolved[]` entry is the [position](./positions.md) shape (so `pnlUsd`, +`maxPayoutUsd`, `proofUrl`, etc. mean the same) plus: + +| Field | Meaning | +|---|---| +| `won` | `true` for `resolved-won`, `false` for `resolved-lost`. The broadcast headline. | +| `shortTitle` | Tweet-length market title (falls back to the full `question`). | +| `broadcast` | **Ready-to-post one-liner.** A win leads with the payout + the proof link + a rematch CTA; a loss is a rematch nudge, never a dunk. **Ends with the project @tags** (`@playhunchxyz` + the token project). Reply with this verbatim in the original bet thread — keep the tags. | +| `appUrl` | The durable market page (shows the resolved outcome + pool) — the proof link used in the `broadcast`. | +| `maxPayoutUsd` | The realized payout for a win (parimutuel, from the settled book); `0` for a loss. | + +`summary`: `wonCount`, `lostCount`, `netPnlUsd` (net realized PnL across settled +bets), `totalWonUsd` (sum of winning payouts). `digest`: `{ title, lines, text }` +— `text` is a post-ready "here's how it settled" recap (same pattern as +`trending.digest`). + +## Using it + +- **In-thread reply (the high-value path).** When a bet settles, reply to its + original cast with that entry's `broadcast`. The winner gets their flex + proof; + spectators see a real payout on Base and a "rematch?" hook. +- **Recap post.** Drop `digest.text` verbatim as a "your week on Hunch" post. + +## Dedupe (you hold the state) + +The endpoint is **stateless** — it reports the *current* resolved set every time. +The bot is responsible for announcing each settled bet **once**: track what you've +already broadcast by `wallet` + `marketId`. Either poll on a cadence, or check +`resolved` right after `result` (see `result.md`) flips a market to `resolved`. + +The model never picks anything here — it's a read over the wallet's own settled +positions, formatted for posting. diff --git a/hunch/references/trading.md b/hunch/references/trading.md index f2e310907d..4ff76b941b 100644 --- a/hunch/references/trading.md +++ b/hunch/references/trading.md @@ -174,6 +174,7 @@ settlement receipt `{ success, transaction, network, payer }`. | `422` | `invalid_wallet` / `invalid_side` | Bad wallet or side. | | `422` | `invalid_payment` | `X-PAYMENT` couldn't be decoded. | | `422` | `payer_mismatch` / `recipient_mismatch` / `amount_mismatch` | The signed authorization doesn't match the wallet / sink / bet size. | +| `422` | `insufficient_balance` | The wallet doesn't hold enough Base USDC (often it staked its whole cent-rounded balance). **Lower `sizeUsd`** (leave a margin) and retry with the same `idemKey`, or fund the wallet — swap another token → USDC on Base **only with the user's explicit, per-swap permission (ask which token first; never auto-swap)**, or have them deposit USDC. **Never blindly retry the same amount.** See SKILL.md → "Funding a bet". | | `409` | `market_closed` | The market isn't open or its deadline has passed. Re-discover. | | `409` | `idempotency_conflict` | The `idemKey` was reused with a **different** body. Mint a fresh key per distinct bet. | | `404` | `market_not_found` | Unknown market id. Re-run `discover`; never hand-craft an id. | diff --git a/hunch/references/transcripts.md b/hunch/references/transcripts.md index 15557116b4..7af5c20c69 100644 --- a/hunch/references/transcripts.md +++ b/hunch/references/transcripts.md @@ -4,16 +4,18 @@ > **user:** @bankrbot take YES on $BNKR hitting $100M, $5 -1. `GET /api/partner/discover?q=$BNKR` → match `bankr-100m-mcap-2026-06-30`. +1. `GET /api/partner/discover?q=$BNKR` → match `bankr-100m-mcap-2026-06-30`, with + `headline: "$BNKR → $100M · YES 12¢ / NO 88¢ · 142 bets · $1.2k pool · closes Jun 30 · @playhunchxyz"`. 2. `GET /api/partner/quote?marketId=bankr-100m-mcap-2026-06-30&side=yes&sizeUsd=5` - → YES 12¢, fee 2%. -3. Reply: - > **Will $BNKR reach a $100M market cap by June 30, 2026?** - > YES 12¢ · NO 88¢ · closes Jun 30 + → YES 12¢, fee 2%, `tokenSnapshot` ~$52M (+92% to $100M), `tags: "@playhunchxyz"`. +3. Reply — render the `headline` verbatim (it ends with the project tag), add the + distance hook + size chips: + > **$BNKR → $100M · YES 12¢ / NO 88¢ · 142 bets · $1.2k pool · closes Jun 30 · @playhunchxyz** + > 📈 $52M now · +92% to $100M > _Resolves from DexScreener market cap on Base. Not financial advice._ - > [Take YES] [Take NO] -4. On confirm → `POST /api/partner/trade` (x402, `idemKey`=mention id, `ref=bankr`) - → receipt + `proofUrl`. + > [Take YES] [Take NO] · size [$1] [$5] [$10] +4. User taps **Take YES · $5** → `POST /api/partner/trade` (x402, `idemKey`=mention + id, `ref=bankr`) → receipt + `proofUrl`. ### 2. Raw post → claim-LLM @@ -60,12 +62,13 @@ market appended. See SKILL.md → "Proactive injection". > **user:** what's $BNKR looking like today? 1. Answer the price/TA normally. -2. `discover?post=...` → `count > 0` → top match `bankr-100m-mcap-2026-06-30`. +2. `discover?post=...` → `count > 0` → top match `bankr-100m-mcap-2026-06-30`, + with a ready `headline` (odds + social proof + close). 3. `quote?marketId=…` → read `tokenSnapshot` (e.g. current ~$52M, target $100M). -4. Append **one** line: - > Want skin in the game? **Will $BNKR reach $100M by Jun 30?** — it's ~$52M - > now (+92% to go). YES 12¢ · NO 88¢. _Resolves from DexScreener mcap on Base. - > Not financial advice._ [Take YES] [Take NO] +4. Append **one** line — the match `headline` + the distance hook + size chips: + > Want skin in the game? **$BNKR → $100M · YES 12¢ / NO 88¢ · 142 bets · $1.2k + > pool · closes Jun 30 · @playhunchxyz** — 📈 $52M now, +92% to go. _Resolves from + > DexScreener mcap on Base. Not financial advice._ [Take YES] [Take NO] · size [$1] [$5] [$10] ### Fire 2 — comparison / "will it beat" question @@ -110,10 +113,11 @@ echo the link. > **user:** @bankrbot show my Hunch bets 1. `GET /api/partner/positions?wallet=`. -2. Render the summary + each position: +2. Render the summary + each position, then end with the `tags` footer verbatim: > **Your Hunch bets** (1 open · 1 resolved · PnL +$1.42) > • **$BNKR → $100M** — YES, $5 @ 12¢ → 15¢ · +$1.25 · open > • **$HUNCH flips $LFI** — YES, $3 · resolved-lost + > @playhunchxyz @lienfiapp Unknown wallet / no bets → empty list; reply "no Hunch positions yet" + offer `discover`. @@ -127,3 +131,49 @@ Unknown wallet / no bets → empty list; reply "no Hunch positions yet" + offer > **Resolved: NO.** $HUNCH didn't reach $10M (closed ~$142K). Winning shares > paid $1.00 each. Proof → playhunch.xyz/markets/hunch-10m 3. `status: "pending"` → "still open, resolves "; offer to bet. + +### 8. Win-broadcast (unprompted, the loop closing loudly) + +The user bet earlier in a thread; the market just resolved. **Reply in that +original thread** — don't wait to be asked. + +1. (Poll, or after `result` flips to `resolved`) + `GET /api/partner/resolved?wallet=`. +2. Find the freshly-settled entry (one you haven't broadcast for this wallet + + `marketId`) and reply to the original cast with its `broadcast` verbatim (it + already ends with the project tags): + > 🎉 Won $8.40 on $BNKR → $100M (YES) — settled in USDC on Base. + > Proof: playhunch.xyz/markets/bankr-100m. Run it back? Tag @bankrbot. @playhunchxyz +3. A **loss** is a rematch nudge, never a dunk (tags credit the token project too): + > Tough one — $HUNCH flips $LFI resolved against YES. Rematch? Tag @bankrbot. @playhunchxyz @lienfiapp +4. **Dedupe:** announce each settled bet once (track wallet + `marketId`). For a + weekly recap, post `digest.text` instead. + +--- + +## Funding a bet (insufficient balance — swap only with permission) + +### 9. Lower the size + +> **user:** @bankrbot bet $5 YES on $BNKR $100M + +1. `trade` → `422 insufficient_balance` (wallet holds ~$3.72 USDC on Base). +2. Don't dead-end; offer the smaller bet: + > Your Base USDC balance is ~$3.72 — want to bet **$3.50** instead? [Take YES · $3.50] +3. On confirm → re-quote at $3.50, `trade` with the **same `idemKey`**. + +### 10. Swap to fund — ASK first, never auto-swap + +> **user:** no, keep it $5 + +1. The wallet can't cover $5 in USDC. **Ask which token to convert** (show + swappable balances — never pick one): + > To bet $5 you'd need a bit more USDC on Base. I can swap one of these to + > USDC — **which, and how much?** • 0.12 ETH • 320 $BNKR • 45 $USDbC +2. User: "swap ~$3 of ETH". **Confirm that specific swap** before doing it: + > Swap **~$3 of ETH → USDC on Base**, then place the $5 YES bet? [Confirm swap] +3. Only on explicit confirm → Bankr executes the swap → retry the bet with the + **same `idemKey`**. + +> **Never** swap a token the user didn't name, and never swap without a per-swap +> confirmation — a swap moves their funds, same consent bar as the bet. diff --git a/hunch/references/trending.md b/hunch/references/trending.md index aa4ac8fe6e..986658c6d7 100644 --- a/hunch/references/trending.md +++ b/hunch/references/trending.md @@ -35,21 +35,25 @@ Feature-flagged behind `HUNCH_PARTNER_API` (404 when off); CORS-open; cached ~60 "links": { "app": "…", "quote": "…", "trade": "…" } }, "odds": { "yesPriceCents": 62, "noPriceCents": 38 }, - "stats": { "totalBets": 142, "totalPoolUsd": 1240, "yesPoolUsd": 150, "noPoolUsd": 1090, "feeUsd": 24.8 } + "stats": { "totalBets": 142, "totalPoolUsd": 1240, "yesPoolUsd": 150, "noPoolUsd": 1090, "feeUsd": 24.8 }, + "headline": "$BNKR → $100M · YES 62¢ / NO 38¢ · 142 bets · $1.2k pool · closes Jun 30 · @playhunchxyz" } ], "digest": { "title": "🔮 Trending on Hunch", "lines": ["1. $BNKR → $100M — YES 62¢ · 142 bets · closes Jun 30"], - "text": "🔮 Trending on Hunch\n1. $BNKR → $100M — YES 62¢ · 142 bets · closes Jun 30\n\nTag @bankrbot to bet in-thread — settles in USDC on Base." + "text": "🔮 Trending on Hunch\n1. $BNKR → $100M — YES 62¢ · 142 bets · closes Jun 30\n\nTag @bankrbot to bet in-thread — settles in USDC on Base. @playhunchxyz" } } ``` Each `trending[]` entry: `rank` (1-based), `heat` (score), `closesInHours`, `market` (the full shared [market ref](./market-ref.md), abbreviated above), -`odds`, and `stats` (same shapes as `discovery.md`). `count` is the number of -entries; the top-level `generatedAt` mirrors `meta.generatedAt`. +`odds`, `stats` (same shapes as `discovery.md`), and a **`headline`** — the same +screenshot-ready one-liner a discover match carries (`title · odds · social proof +· close`), so surfacing a trending market unprompted reads identically to a +discovered one. `count` is the number of entries; the top-level `generatedAt` +mirrors `meta.generatedAt`. ## Ranking @@ -61,8 +65,9 @@ markets. The id comes from the deterministic ranker; the model never picks it. ## Using it -- **Daily post:** post `digest.text` verbatim (it already names `@bankrbot` and - the Base USDC settlement). +- **Daily post:** post `digest.text` verbatim (it already names `@bankrbot`, the + Base USDC settlement, and tags `@playhunchxyz`). Each entry's `headline` also + ends with the project @tags — keep them when rendering a single entry. - **Custom card:** iterate `trending[]` and render each entry's `market` + `odds` with the standard `Take YES / Take NO` reply shape. - Always include the market's category disclosure line (from `catalogue`) before diff --git a/hunch/scripts/walkthrough.sh b/hunch/scripts/walkthrough.sh index 99b656b4f8..b8a712980b 100755 --- a/hunch/scripts/walkthrough.sh +++ b/hunch/scripts/walkthrough.sh @@ -26,9 +26,11 @@ say() { printf '\n\033[1;35m== %s ==\033[0m\n' "$1"; } say "1. discover ${TOKEN}" # URL-encode the token ($ → %24) for the query string. Q=$(printf '%s' "$TOKEN" | jq -sRr @uri) -# Each match nests the market under .market; odds/stats are siblings. +# Each match nests the market under .market; odds/stats/headline are siblings. +# `headline` is the screenshot-ready line (title · odds · social proof · close) +# the bot renders verbatim. DISCOVER=$(curl -fsS "${BASE}/api/partner/discover?q=${Q}") -echo "$DISCOVER" | jq '{count, matches: [.matches[] | {id: .market.id, question: .market.question, odds, matchKind}]}' +echo "$DISCOVER" | jq '{count, matches: [.matches[] | {id: .market.id, headline, matchKind}]}' MARKET_ID=$(echo "$DISCOVER" | jq -r '.matches[0].market.id // empty') if [ -z "$MARKET_ID" ]; then