feat(markets): Bitcoin difficulty betting (hidden L2, Lightning fund only) - #31
Conversation
adrienlacombe
left a comment
There was a problem hiding this comment.
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:
MarketsScreen.tsx— wrong relative import path, 5 × TS2307, breakspnpm buildroutes/markets.tsx— invalidViewLoadingFallbackKindlognormalHints.ts—InstanceType<typeof SQ128x128>isn't newabledesktop/package.json— the new deps breaktscin two files this PR doesn't touchcommands/mod.rs—pub(crate)dropped frommod media; won't compileplaceBet.ts— the user's BTC amount is validated, then discardedindexer.ts— silent fallback to an arbitrary marketbuzz-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-proxyis an unauthenticated open relay for a paid API key. Binds0.0.0.0:8788,CorsLayerwithallow_origin(Any)/allow_methods(Any)/allow_headers(Any), no auth, no method allowlist — it forwards any JSON-RPC body upstream withx-paymaster-api-keyinjected. Anyone who can reach it spends the sponsor's quota. Compareinfra/aws/paymaster.tf, which gives the paymaster an egress-only SG with no ingress rule at all.place_betsigns whatever the frontend hands it. It takesVec<PreparedCall>and validates only that entrypoint names don't containlightning/atomiq/invoice. Nothing checks the calls targetDIFFICULTY_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_betcomputesfee_amountand returns it but never checks the fee transfer is actually incalls. A modified client omits it;PlaceBetResult.feeAmountis cosmetic. - The agent-key gate is a tautology.
human_keys()callsassert_human_keyring_name(HUMAN_IDENTITY_KEYRING_NAME)— the constant is"identity", exactly the value that returnsOk. It cannot fail, and every test around it exercises a function never called with a real input. markets.test.mjsre-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.mdis a.md, andavnu_proxy_url()defaults tohttp://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 thatINDEXER_URLhas no localhost default; the same standard isn't applied to the variable that actually spends money. env.INDEXER_URLdoesn't reach the client.indexer.tsreads it offimport.meta.env, which Vite only populates forVITE_-prefixed vars..env.exampleand the docs tell operators to setINDEXER_URL; client-side that silently does nothing and falls through to the hardcoded host.- No
Signed-off-byon any of the 6 commits. AGENTS.md requiresgit 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
Dockerfilebut not for the new divergences —buzz-core/src/markets.rs+ itslib.rsmod line,crates/buzz-avnu-proxy/, and the desktop sidebar/AppShell/routeTree.gen.tsedits. Those last ones will conflict on every upstream sync.
ddba14b to
c45753a
Compare
|
Replied on each review thread. Resolved (code matches):
Left open:
Not opened as threads (still unfixed, from the review body): unauthenticated AVNU proxy, |
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>
c45753a to
a658594
Compare
|
Hold-list on 3–4. Review threads on package.json and biome are resolved. DCO / AGENTS.md still deferred. |
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>
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)
FROM_BTCLN_AUTO→ product-labeled BTC. Taurifund_lightningonly returns the address.[feeTransfer, ...trade.calls]→ BIP-340 OutsideExecution → AVNU sponsored. No Lightning / invoice / Atomiq on the bet path.prepareTrade / lognormal
No SDK
prepareLognormalTrade. ReuseprepareTrade({ targetMean })withtargetMean = ln(D). Both hints = cairoisqrt(2·σ·√π)(same limbs). Calls =[strkBTC.transfer(feeRecipient, feeAmount), ...trade.calls]. NoexecuteTrade(). Do not bump approve /supplied_collateralfor 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.GET {INDEXER_URL}/api/marketsGET {INDEXER_URL}/healthv1 market: address
0x023b3a7bbe48a905ceadc17cd21b6b71fedaf90ee1218e462b106e01703b9cc8, title “Bitcoin difficulty after next retarget”,marketTypelognormal,xAxisLabelDifficulty, collateral copy BTC.Do not put
ADMIN_API_KEYorAVNU_API_KEYin the repo, client, or PR. Listing/health need no admin key. SetAVNU_API_KEYonly onbuzz-avnu-proxyat runtime.Tests
See
docs/bitcoin-markets.md.