Skip to content

feat(markets): Bitcoin difficulty betting (hidden L2, Lightning fund only) - #31

Merged
adrienlacombe merged 13 commits into
mainfrom
cursor/bitcoin-markets-betting
Aug 16, 2026
Merged

feat(markets): Bitcoin difficulty betting (hidden L2, Lightning fund only)#31
adrienlacombe merged 13 commits into
mainfrom
cursor/bitcoin-markets-betting

Conversation

@adrienlacombe

@adrienlacombe adrienlacombe commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Summary

Implements Bitcoin Markets betting for first users who hold Bitcoin. The UI never surfaces Starknet / STRK / felts / class hashes / strkBTC / deploy / paymaster.

Fund vs Bet (strict split)

Screen Path
Add bitcoin / Fund with Lightning Atomiq FROM_BTCLN_AUTO → product-labeled BTC. Tauri fund_lightning only returns the address.
Place bet Hidden Starknet only: prepareTrade-style prep → [feeTransfer, ...trade.calls] → BIP-340 OutsideExecution → AVNU sponsored. No Lightning / invoice / Atomiq on the bet path.

prepareTrade / lognormal

No SDK prepareLognormalTrade. Reuse prepareTrade({ targetMean }) with targetMean = ln(D). Both hints = cairo isqrt(2·σ·√π) (same limbs). Calls = [strkBTC.transfer(feeRecipient, feeAmount), ...trade.calls]. No executeTrade(). Do not bump approve / supplied_collateral for the fee.

INDEXER_URL

Required env (or product public host). No localhost default. Adrien does not want this run locally. Loopback (http://127.0.0.1:8787) was listing-proof only and is refused by client resolvers.

INDEXER_URL=https://markets.bitcoinmarkets.app
  • GET {INDEXER_URL}/api/markets
  • GET {INDEXER_URL}/health

v1 market: address 0x023b3a7bbe48a905ceadc17cd21b6b71fedaf90ee1218e462b106e01703b9cc8, title “Bitcoin difficulty after next retarget”, marketType lognormal, xAxisLabel Difficulty, collateral copy BTC.

Do not put ADMIN_API_KEY or AVNU_API_KEY in the repo, client, or PR. Listing/health need no admin key. Set AVNU_API_KEY only on buzz-avnu-proxy at runtime.

Tests

  • Fee math, halt-at-height, agent-key gate
  • INDEXER_URL uses product host and refuses loopback
  • v1 listing row match

See docs/bitcoin-markets.md.

Open in Web Open in Cursor 

@adrienlacombe
adrienlacombe marked this pull request as ready for review August 16, 2026 13:14

@adrienlacombe adrienlacombe left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking review — 7 red CI jobs, plus one compile error hiding behind them

Opened threads for the blockers.

Attribution: I diffed each failure against main before calling it a regression. Desktop Build (macOS) and Smoke E2E 1/2/4 pass on main, so those failures are introduced here. Desktop Core and Smoke E2E 3 are already red on main for unrelated reasons (the entity-link scheme literals, and the flaky video-attachment spec) — but Desktop Core is red here for a new reason on top, see thread 8.

Threads opened:

  1. MarketsScreen.tsx — wrong relative import path, 5 × TS2307, breaks pnpm build
  2. routes/markets.tsx — invalid ViewLoadingFallbackKind
  3. lognormalHints.tsInstanceType<typeof SQ128x128> isn't newable
  4. desktop/package.json — the new deps break tsc in two files this PR doesn't touch
  5. commands/mod.rspub(crate) dropped from mod media; won't compile
  6. placeBet.ts — the user's BTC amount is validated, then discarded
  7. indexer.ts — silent fallback to an arbitrary market
  8. buzz-core/src/lib.rs — rustfmt, plus the biome roundup

Worth knowing before fixing: pnpm build fails before the Tauri build runs, and the desktop crate sits outside the root workspace (AGENTS.md gotcha #5), so no job in this run has compiled desktop/src-tauri/src/commands/markets.rs. Thread 5 is one error found by reading it. Expect more once the frontend builds and that file actually reaches the compiler — worth running cargo check --manifest-path desktop/src-tauri/Cargo.toml locally before the next push.


Not opened as threads — these are from the full review and aren't CI failures, but a couple are more serious than anything above. Flagging so they don't get lost; say the word and I'll open threads for them too:

  • buzz-avnu-proxy is an unauthenticated open relay for a paid API key. Binds 0.0.0.0:8788, CorsLayer with allow_origin(Any)/allow_methods(Any)/allow_headers(Any), no auth, no method allowlist — it forwards any JSON-RPC body upstream with x-paymaster-api-key injected. Anyone who can reach it spends the sponsor's quota. Compare infra/aws/paymaster.tf, which gives the paymaster an egress-only SG with no ingress rule at all.
  • place_bet signs whatever the frontend hands it. It takes Vec<PreparedCall> and validates only that entrypoint names don't contain lightning/atomiq/invoice. Nothing checks the calls target DIFFICULTY_MARKET / COLLATERAL_TOKEN / FEE_RECIPIENT, so a compromised renderer gets a BIP-340 signature over arbitrary Starknet calls — gas-sponsored. This inverts the rule AGENTS.md sets for the paymaster ("the account address is derived from the event author, never from the payload").
  • The wallet fee isn't enforced. place_bet computes fee_amount and returns it but never checks the fee transfer is actually in calls. A modified client omits it; PlaceBetResult.feeAmount is cosmetic.
  • The agent-key gate is a tautology. human_keys() calls assert_human_keyring_name(HUMAN_IDENTITY_KEYRING_NAME) — the constant is "identity", exactly the value that returns Ok. It cannot fail, and every test around it exercises a function never called with a real input.
  • markets.test.mjs re-implements the functions it tests (walletFeeAmount, haltHeight, resolveIndexerUrl, findDifficultyMarket, …) instead of importing them, so it passes regardless of what ships. Worth fixing for fee math specifically.
  • The proxy never deploys. infra/aws/markets.tf.md is a .md, and avnu_proxy_url() defaults to http://127.0.0.1:8788 — so in a shipped build the bet path points at a local proxy that doesn't exist. The PR is emphatic that INDEXER_URL has no localhost default; the same standard isn't applied to the variable that actually spends money.
  • env.INDEXER_URL doesn't reach the client. indexer.ts reads it off import.meta.env, which Vite only populates for VITE_-prefixed vars. .env.example and the docs tell operators to set INDEXER_URL; client-side that silently does nothing and falls through to the hardcoded host.
  • No Signed-off-by on any of the 6 commits. AGENTS.md requires git commit -s; the DCO check doesn't appear to be enabled on this fork, so nothing caught it.
  • AGENTS.md's fork-local table got a row update for Dockerfile but not for the new divergences — buzz-core/src/markets.rs + its lib.rs mod line, crates/buzz-avnu-proxy/, and the desktop sidebar/AppShell/routeTree.gen.ts edits. Those last ones will conflict on every upstream sync.

Comment thread desktop/src/features/markets/ui/MarketsScreen.tsx Outdated
Comment thread desktop/src/app/routes/markets.tsx Outdated
Comment thread desktop/src/features/markets/lib/lognormalHints.ts Outdated
Comment thread desktop/package.json Outdated
Comment thread desktop/src-tauri/src/commands/mod.rs
Comment thread desktop/src/features/markets/lib/placeBet.ts
Comment thread desktop/src/features/markets/lib/indexer.ts
Comment thread crates/buzz-core/src/lib.rs Outdated
@cursor
cursor Bot force-pushed the cursor/bitcoin-markets-betting branch from ddba14b to c45753a Compare August 16, 2026 13:45
@adrienlacombe

Copy link
Copy Markdown
Owner Author

Replied on each review thread.

Resolved (code matches):

  • ../lib imports
  • ViewLoadingFallbackKind (kind="projects")
  • SQ128x128 (no InstanceType)
  • pub(crate) mod media restored; Felt/selectors re-exported from buzz-core (no starknet_core in Tauri)
  • user collateralBtc is passed through and spent
  • no markets[0] substitute
  • rustfmt (also after rebase onto main / PR 32)

Left open:

  • desktop/package.json — alphabetize + exact pins for @the-situation/*
  • biome: noExplicitAny / noNonNullAssertion / noLabelWithoutControl

Not opened as threads (still unfixed, from the review body): unauthenticated AVNU proxy, place_bet signs arbitrary frontend calls, fee transfer not enforced in calls, agent-key gate tautology, tests re-implement helpers, proxy localhost default, INDEXER_URL vs VITE_INDEXER_URL, DCO/Signed-off-by, AGENTS.md table. Say the word and those get threads + fixes.

cursoragent and others added 10 commits August 16, 2026 14:10
Wire Fund (Atomiq Lightning → BTC) and Bet (lognormal prepare + fee/approve/
execute_trade via NostrAccount BIP-340 and AVNU sponsored paymaster proxy).

- Lognormal hints: both denoms = isqrt(2σ√π) (same limbs); UI axis is raw D,
  candidate μ = ln(D); place_bet never mixes Lightning
- buzz-avnu-proxy: server-side AVNU_API_KEY via env; Dockerfile ships binary
- INDEXER_URL product host https://markets.bitcoinmarkets.app (no loopback)
- Tests: 10 bps fee (min 1 sat), halt-at-height, agent keys excluded

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
Require the product host as INDEXER_URL with no localhost default.
Refuse 127.0.0.1:8787 in Rust/JS resolvers. Document that Markets is
adding the indexer in infra/aws while the hostname stays locked during
DNS propagation.

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
INDEXER_URL is a required env with no client default. Refuse unset and
refuse http://127.0.0.1:8787 / localhost (listing-proof only). Product
host https://markets.bitcoinmarkets.app is documentation for the public
value to set later — not a silent fallback. Listing remains
GET /api/markets and GET /health on whatever host INDEXER_URL points at.

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
Wire desktop to configurable INDEXER_URL defaulting to
http://127.0.0.1:8787 (Adrien's machine, not sslip.io). Match unpadded
listing addresses and xAxisLabel. Do not live-fetch that host from
cloud/CI. prepareTrade path unchanged: targetMean=ln(D), same-limb
lognormal hints, prepend fee transfer then ...trade.calls.

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
Desktop listing defaults to http://127.0.0.1:8787 (configurable).
Document padded v1 market row and BTC collateral copy. Explicitly forbid
reading or shipping indexer ADMIN_API_KEY — listing/health need no auth.
Cloud VMs cannot reach Adrien localhost; desktop client only.

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
Product INDEXER_URL is https://markets.bitcoinmarkets.app (required env
or that public host). Refuse loopback — 127.0.0.1:8787 was listing-proof
only. Update docs, tests, and keep ADMIN/AVNU keys out of the repo.

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
- MarketsScreen imports ../lib/*; loading fallback kind=projects
- lognormalHints takes SQ128x128 (not InstanceType)
- Restore pub(crate) mod media alongside markets
- Re-export Felt/selectors from buzz-core; drop Tauri starknet_core import
- cargo fmt
- Opaque Atomiq dynamic import so /// <reference types="node" /> does not
  poison DOM timer types (communityOnboarding / localStorageSweep stay untouched)
- Pass user collateralBtc into prepare/placeBet; spend that amount
- Fail closed when difficulty market is missing (no markets[0] substitute)

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
Product signal is GET mempool.space/api/v1/difficulty-adjustment —
halt when remainingBlocks <= 24. Tauri difficulty_halt_status feeds the
UI; place_bet re-fetches and refuses. Keep 2016-block tip math as
unit-test / tip-height fallback only. No settle bot.

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
Rebased onto origin/main (PR 32). Format markets.rs / mod.rs so Desktop
Core rustfmt passes.

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
- Rebuild/validate place_bet batch in Rust (fee first, approve+trade only)
- Refuse agent keyring slots with real agent:<pubkey> tests
- Require AVNU_PROXY_URL (no localhost default); loopback-only proxy bind + auth off-loopback
- Honor INDEXER_URL via Vite envPrefix + Tauri markets_indexer_url
- Alphabetize/pin @the-situation/*; tsconfig types:[] vs @types/node
- Biome: nested labels, fromNumber null-check, import real test helpers

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/bitcoin-markets-betting branch from c45753a to a658594 Compare August 16, 2026 14:11
@adrienlacombe

Copy link
Copy Markdown
Owner Author

Hold-list on a6585948 (no dedicated review threads for these; recording here):

3–4. place_bet rebuilds the batch via build_validated_bet_batch (fee transfer first, then only approve + execute_trade against product contracts). Fee-first is also asserted on AVNU's typed_data Calls before BIP-340.
5. Agent-key gate tests real agent:<pubkey> slots (agent_keyring_slot_used_by_secret_store_is_rejected), not a HUMAN_IDENTITY_KEYRING_NAME tautology.
6. markets.test.mjs imports walletFeeAmount / halt helpers / indexer helpers — does not reimplement them.
7. AVNU_PROXY_URL is required (no localhost client default). Proxy bind defaults to loopback; non-loopback requires PROXY_AUTH_TOKEN.
8. INDEXER_URL reaches the client (VITE_INDEXER_URL + Tauri markets_indexer_url). Loopback refused.
9. buzz-avnu-proxy is not an unauthenticated open relay (loopback default, no CORS Any, auth off-loopback, JSON-RPC parse before forward).

Review threads on package.json and biome are resolved. DCO / AGENTS.md still deferred.

cursoragent and others added 3 commits August 16, 2026 14:51
Keep Markets Tauri commands and sidebar Markets wiring; satisfy the
file-size ratchet without raising MAX_LINES.

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
Share one primary-menu row between Inbox and Markets so custom-section
sortable headers stay at main's vertical position. Fixes smoke
virtualization.spec.ts "06 — custom-section dnd reorder" (PR 31 regression).

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
CI Smoke E2E (2) on 6bdabd2 failed huddle-transcription compact-controls:
Radix modal popover opened then closed before toBeVisible (3 retries). Keep
Inbox+Markets on one primary-menu row for DnD height, switch AgentVoiceMenu to
non-modal controlled open, and avoid redundant voice-settings state churn.

Co-authored-by: Adrien Lacombe <adrienlacombe@users.noreply.github.com>
@adrienlacombe
adrienlacombe merged commit 1e80e83 into main Aug 16, 2026
36 checks passed
@adrienlacombe
adrienlacombe deleted the cursor/bitcoin-markets-betting branch August 16, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants