Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
name: Bootstrap Anchr Market
name: Bootstrap Two-party binary bet

# Manual one-shot to stand up the prediction-market Fly app from scratch:
# Manual one-shot to stand up the two-party-binary-bet Fly app from scratch:
# creates the Fly app + volume (idempotent), runs FROST 2-of-3 DKG,
# uploads the encrypted shares + passphrase as Fly secrets, then deploys.
#
# Required GitHub secrets (Settings → Secrets and variables → Actions):
# FLY_API_TOKEN_MARKET — Fly deploy token scoped to anchr-market
# FROST_KEY_PASSPHRASE — 32+ hex chars (e.g. `openssl rand -hex 32`)
#
# After this workflow succeeds, the regular .github/workflows/deploy.yml
# `deploy-market` job picks up future commits to main automatically.
# After this workflow succeeds, the regular deploy-two-party-binary-bet.yml
# workflow picks up future commits to main automatically.

on:
workflow_dispatch:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
if: ${{ !cancelled() }}
run: |
docker compose up -d relay blossom postgres
# Wait for Postgres so the prediction-market order-book tests
# Wait for Postgres so the two-party-binary-bet order-book tests
# don't start before /docker-entrypoint-initdb.d finishes.
for i in $(seq 1 30); do
if docker compose exec -T postgres pg_isready -U anchr -d anchr_market > /dev/null 2>&1; then
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Deploy Prediction Market
name: Deploy Two-party binary bet

# Auto-deploys the 巫(Kannagi) prediction market to Fly when:
# Auto-deploys the 巫(Kannagi) two-party binary bet app to Fly when:
# - push to main, AND
# - the change touches example/two-party-binary-bet/, the src/ &
# packages/ paths the market server depends on at build time,
Expand Down Expand Up @@ -32,7 +32,7 @@ on:
- "deno.lock"
- "crates/tlsn-verifier/**"
- "crates/frost-signer/**"
- ".github/workflows/deploy-market.yml"
- ".github/workflows/deploy-two-party-binary-bet.yml"
workflow_dispatch:

permissions:
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ uploads/*
generated.css

# bundled UI outputs (rebuilt by `deno task build:ui`)
example/prediction-market/ui/main.js
example/prediction-market/ui/main.js.map
example/two-party-binary-bet/ui/main.js
example/two-party-binary-bet/ui/main.js.map

# mobile (expo)
mobile/.expo/
Expand Down
18 changes: 9 additions & 9 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Design System — Anchr Prediction Market
# Design System — Anchr Two-party binary bet

## 1. Visual Theme & Atmosphere

Anchr is a Bitcoin-native prediction market built on Cashu HTLC + Nostr + TLSNotary. The visual identity is rooted in Nostr culture — dark, sovereign, cypherpunk — but elevated to the polish level of Polymarket and Stripe. The interface should feel like a serious financial instrument that happens to run on Nostr keys and Lightning sats, not a toy or proof of concept.
Anchr is a Bitcoin-native two-party binary bet built on Cashu HTLC + Nostr + TLSNotary. The visual identity is rooted in Nostr culture — dark, sovereign, cypherpunk — but elevated to the polish level of Polymarket and Stripe. The interface should feel like a serious financial instrument that happens to run on Nostr keys and Lightning sats, not a toy or proof of concept.

The atmosphere is **dark-immersive**: deep, near-black surfaces with a subtle purple tint inherited from Nostr's brand identity. Purple is the singular brand accent — every interactive element, glow, and highlight traces back to it. Color is rare and meaningful: only YES (green), NO (red), and brand purple appear against the dark canvas.

**Key Characteristics:**
- Near-black backgrounds with purple undertone (`#0A0910`)
- Single brand accent: Nostr purple (`#8B5CF6`)
- Prediction-market-native color pair: YES emerald / NO red
- Two-party-binary-bet-native color pair: YES emerald / NO red
- Inter Variable for all UI text; Geist Mono for numbers, hashes, pubkeys
- Conservative border-radius (8–12px) — polished but not bubbly
- Purple glow on hover/focus — subtle, never decorative
Expand All @@ -25,7 +25,7 @@ The atmosphere is **dark-immersive**: deep, near-black surfaces with a subtle pu
- **Purple Glow** (`rgba(139, 92, 246, 0.15)`): Ambient glow for hover cards, focused inputs.
- **Purple Surface** (`rgba(139, 92, 246, 0.10)`): Background tint for selected/active states and badges.

### Prediction Market Pair
### Two-party binary bet Pair
- **YES Green** (`#22C55E` / `hsl(142, 71%, 45%)`): YES bets, positive outcomes, success. Used in probability bars, bet buttons, and resolved-YES badges.
- **YES Green Background** (`rgba(34, 197, 94, 0.12)`): Tinted surface for YES-related regions.
- **YES Green Dark** (`#166534`): YES button text (on green background) for contrast.
Expand Down Expand Up @@ -71,7 +71,7 @@ Default and only mode. All values above are the canonical palette.

| Role | Font | Size | Weight | Line Height | Letter Spacing | Use |
|------|------|------|--------|-------------|----------------|-----|
| Page Title | Inter | 30px (1.875rem) | 700 | 1.2 | -0.03em | "Prediction Markets" |
| Page Title | Inter | 30px (1.875rem) | 700 | 1.2 | -0.03em | "Two-party binary bets" |
| Section Heading | Inter | 20px (1.25rem) | 600 | 1.3 | -0.02em | Panel titles, "Market Details" |
| Market Title | Inter | 15px (0.9375rem) | 600 | 1.4 | -0.01em | Market question text in cards |
| Body | Inter | 14px (0.875rem) | 400 | 1.6 | normal | Descriptions, paragraphs |
Expand Down Expand Up @@ -267,7 +267,7 @@ Default and only mode. All values above are the canonical palette.

### Do
- Use `#8B5CF6` (Nostr purple) as the singular brand/interactive color — consistency IS the brand
- Use YES green (`#22C55E`) and NO red (`#EF4444`) only for prediction market outcomes — never decoratively
- Use YES green (`#22C55E`) and NO red (`#EF4444`) only for two-party binary bet outcomes — never decoratively
- Use Geist Mono for all numeric and cryptographic data — sat amounts, probabilities, hashes, pubkeys
- Use purple glow (`rgba(139, 92, 246, 0.15)`) for hover/focus elevation — it's atmospheric, not decorative
- Keep border-radius between 8–12px for cards and buttons — polished but not bubbly
Expand Down Expand Up @@ -354,7 +354,7 @@ Default and only mode. All values above are the canonical palette.
### Implementation Notes
- CSS variables defined in `globals.css` using HSL values in `:root` and `.dark` selectors
- Tailwind `@theme` block maps CSS vars to utility classes (`bg-primary`, `text-foreground`, etc.)
- Custom colors `--yes` and `--no` extend the theme for prediction-market-specific utilities
- Custom colors `--yes` and `--no` extend the theme for two-party-binary-bet-specific utilities
- `cn()` utility from `clsx` + `tailwind-merge` for conditional class merging
- Components use React + Tailwind CSS — no CSS-in-JS, no styled-components

Expand All @@ -372,6 +372,6 @@ Default and only mode. All values above are the canonical palette.
| Date | Decision | Rationale |
|------|----------|-----------|
| 2026-03-31 | Findexa-inspired blue theme | Initial design for Worker/Requester dashboards. Blue brand accent. |
| 2026-04-06 | Nostr purple theme for Prediction Market | Market page needs distinct identity from dashboard. Nostr culture uses purple (Damus, Primal). Black + purple creates sovereign, cypherpunk feel appropriate for permissionless prediction markets. |
| 2026-04-06 | Nostr purple theme for Two-party binary bet | Market page needs distinct identity from dashboard. Nostr culture uses purple (Damus, Primal). Black + purple creates sovereign, cypherpunk feel appropriate for permissionless two-party binary bets. |
| 2026-04-06 | 9-section structure adopted | Following awesome-design-md convention for AI-agent-readable design systems. Sections: theme, color, typography, components, layout, depth, do's/don'ts, responsive, agent guide. |
| 2026-04-06 | YES/NO as domain colors, not brand | Green/red reserved exclusively for prediction outcomes. Prevents color confusion and keeps the brand identity clean (purple only). |
| 2026-04-06 | YES/NO as domain colors, not brand | Green/red reserved exclusively for bet outcomes. Prevents color confusion and keeps the brand identity clean (purple only). |
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"test:e2e": "deno test e2e/ --allow-env --allow-read --allow-write --allow-net --allow-run --allow-sys",
"test:e2e:frost": "deno test e2e/frost-threshold.test.ts --allow-env --allow-read --allow-write --allow-net --allow-run --allow-sys",
"test:e2e:relay": "NOSTR_RELAYS=ws://localhost:7777 BLOSSOM_SERVERS=http://localhost:3333 deno test e2e/relay.test.ts --allow-env --allow-read --allow-write --allow-net --allow-run --allow-sys",
"test:regtest": "CASHU_MINT_URL=http://localhost:3338 NOSTR_RELAYS=ws://localhost:7777 BLOSSOM_SERVERS=http://localhost:3333 deno test e2e/regtest-cashu.test.ts e2e/regtest-htlc-trustless.test.ts e2e/regtest-htlc-attacks.test.ts e2e/core-flow.test.ts e2e/conditional-swap.test.ts e2e/prediction-market-lifecycle.test.ts e2e/prediction-market-orderbook-pg.test.ts e2e/sdk-integration.test.ts --allow-env --allow-read --allow-net --allow-run",
"test:regtest": "CASHU_MINT_URL=http://localhost:3338 NOSTR_RELAYS=ws://localhost:7777 BLOSSOM_SERVERS=http://localhost:3333 deno test e2e/regtest-cashu.test.ts e2e/regtest-htlc-trustless.test.ts e2e/regtest-htlc-attacks.test.ts e2e/core-flow.test.ts e2e/conditional-swap.test.ts e2e/two-party-binary-bet-lifecycle.test.ts e2e/two-party-binary-bet-orderbook-pg.test.ts e2e/sdk-integration.test.ts --allow-env --allow-read --allow-net --allow-run",
"test:ci": "deno test src/ packages/photo-bounty/ packages/tlsn-toolkit/ packages/cashu-frost-oracle/ packages/cashu-conditional-swap/ packages/core-runtime/ packages/core-cashu/ --allow-env --allow-read --allow-write --allow-net --allow-run --allow-sys '--ignore=src/**/*.integration.test.ts,packages/sdk/'",
"test:example": "deno test --allow-all example/",
"test": "deno test src/ packages/photo-bounty/ packages/tlsn-toolkit/ packages/cashu-frost-oracle/ packages/cashu-conditional-swap/ packages/core-runtime/ packages/core-cashu/ e2e/ --allow-env --allow-read --allow-write --allow-net --allow-run --allow-sys",
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ services:
- lnd-mint-data:/lnd:ro
command: ["poetry", "run", "mint"]

# Prediction-market order book persistence. The official postgres image
# Two-party-binary-bet order book persistence. The official postgres image
# auto-runs every *.sql under /docker-entrypoint-initdb.d on first start,
# so the order-book schema is applied without a separate migrate step.
postgres:
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ flowchart LR
Mint -->|payout| Winner
```

Used by: prediction-market (`example/two-party-binary-bet/`). Imports
Used by: two-party-binary-bet (`example/two-party-binary-bet/`). Imports
`@anchr/cashu-conditional-swap` + `@anchr/cashu-frost-oracle` directly
(no SDK).

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Prediction Market Deployment Guide
# Two-party binary bet Deployment Guide

End-to-end operator runbook for deploying the Anchr prediction market on
End-to-end operator runbook for deploying the Anchr two-party binary bet on
**regtest** (local development) or **testnet** (public preview). The
production-style deploy uses encrypted FROST DKG keys (PR-D), the
auto-resolver (PR-B), trustless TLSNotary verification (PR-C), and the
Expand Down Expand Up @@ -57,7 +57,7 @@ docker compose up -d
./scripts/init-regtest.sh
docker compose restart cashu-mint

# 3. Start the prediction market server
# 3. Start the two-party binary bet server
CASHU_MINT_URL=http://localhost:3338 \
NOSTR_RELAYS=ws://localhost:7777 \
MARKET_PORT=3001 \
Expand Down Expand Up @@ -230,7 +230,7 @@ the HTLC fallback path for those legacy markets.

```bash
# In-process test of the full lifecycle (skips when mint isn't reachable):
deno test --allow-all e2e/prediction-market-lifecycle.test.ts
deno test --allow-all e2e/two-party-binary-bet-lifecycle.test.ts

# Just the unit + route tests (no Docker needed):
deno task test:example
Expand Down Expand Up @@ -258,7 +258,7 @@ deno run --allow-all scripts/market-screenshots.ts
The script boots the market server in-process on port 3098, drives
headless Chromium via Playwright through the empty state and the
create-market form, optionally seeds one demo market, and writes
the screenshots to `docs/prediction-market/screenshots/`.
the screenshots to `docs/two-party-binary-bet/screenshots/`.

## 7. Known gaps (pre-1.0)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Kannagi vs Polymarket — market-maker gap analysis

What the bot fleet under `scripts/bot-fleet/` exercised against the current
Kannagi (巫) prediction-market platform, and which Polymarket-style
Kannagi (巫) two-party-binary-bet platform, and which Polymarket-style
liquidity behaviors are not yet expressible. Captured 2026-04-28.

The fleet runs real Cashu wallets on a regtest Lightning + Cashu mint and
Expand Down Expand Up @@ -98,7 +98,7 @@ Smallest unblocking change for the bot fleet. Expose:

### 2. Order cancellation HTTP endpoint
The `OrderBook.cancelOrder` primitive already exists (`example
/prediction-market/src/order-book.ts:31`). Adding `DELETE /markets/:id
/two-party-binary-bet/src/order-book.ts:31`). Adding `DELETE /markets/:id
/orders/:orderId` (with `bettor_pubkey` auth) is mechanical. Without it
bots that change their mind have to wait for the order to fill or for
the market to expire.
Expand Down
4 changes: 2 additions & 2 deletions e2e/conditional-swap.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* E2E tests for the conditional-swap spec: Conditional Swap — full prediction market lifecycle.
* E2E tests for the conditional-swap spec: Conditional Swap — full two-party binary bet lifecycle.
*
* Tests the complete lifecycle with real Cashu escrow on regtest:
* 1. Create dual preimage store (generates hash_a, hash_b for YES/NO outcomes)
Expand Down Expand Up @@ -157,7 +157,7 @@ async function executeMatchDirect(

const suite = INFRA_READY ? describe : describe.ignore;

suite("e2e: the conditional-swap spec — Conditional Swap full prediction market lifecycle", () => {
suite("e2e: the conditional-swap spec — Conditional Swap full two-party binary bet lifecycle", () => {
const wallet = sharedWallet!;

// Shared state across the lifecycle tests
Expand Down
4 changes: 2 additions & 2 deletions e2e/frost-p2pk-cashu.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* E2E tests: FROST P2PK + real Cashu mint — full trustless prediction market flow.
* E2E tests: FROST P2PK + real Cashu mint — full trustless two-party binary bet flow.
*
* Tests the complete lifecycle using P2PK multi-sig instead of HTLC:
* 1. Generate two Oracle keypairs (YES/NO groups) — simulates FROST DKG output
Expand Down Expand Up @@ -105,7 +105,7 @@ async function createP2PKLockedProofs(

const suite = INFRA_READY ? describe : describe.ignore;

suite("e2e: FROST P2PK + real Cashu mint — trustless prediction market flow", () => {
suite("e2e: FROST P2PK + real Cashu mint — trustless two-party binary bet flow", () => {
const wallet = sharedWallet!;

// Oracle keypairs (simulating FROST DKG output)
Expand Down
2 changes: 1 addition & 1 deletion e2e/nip60-user-flow.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* E2E: NIP-60-backed user participating in 巫(Kannagi) prediction market.
* E2E: NIP-60-backed user participating in 巫(Kannagi) two-party binary bet.
*
* Demonstrates the user-facing path:
* 1. Open a fresh NIP-60 wallet (real Nostr nsec, real relay).
Expand Down
4 changes: 2 additions & 2 deletions e2e/nip61-nutzap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ suite("e2e: NIP-61 nutzap (regtest Cashu + Nostr relay)", () => {
senderProofs,
amountSats: ZAP,
relays: [RELAY_URL],
comment: "for the prediction market faucet",
comment: "for the two-party binary bet faucet",
pool,
});
expect(sendResult.eventId).toMatch(/^[0-9a-f]{64}$/);
Expand All @@ -101,7 +101,7 @@ suite("e2e: NIP-61 nutzap (regtest Cashu + Nostr relay)", () => {
expect(nz).toBeTruthy();
expect(nz!.amountSats).toBe(ZAP);
expect(nz!.mintUrl).toBe(MINT_URL);
expect(nz!.comment).toBe("for the prediction market faucet");
expect(nz!.comment).toBe("for the two-party binary bet faucet");

// Recipient swaps the locked nutzap at the mint. The proofs are
// P2PK-locked to recipientPk; redeemNutzap passes the nsec to
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* E2E: prediction market full lifecycle on regtest Cashu.
* E2E: two-party binary bet full lifecycle on regtest Cashu.
*
* Walks the entire user-visible flow with real Cashu proofs:
* 1. Mint sats for alice (YES) and bob (NO) via regtest Lightning.
Expand All @@ -23,7 +23,7 @@
* docker compose up -d
* ./scripts/init-regtest.sh && docker compose restart cashu-mint
* CASHU_MINT_URL=http://localhost:3338 \
* deno test e2e/prediction-market-lifecycle.test.ts --allow-all
* deno test e2e/two-party-binary-bet-lifecycle.test.ts --allow-all
*/

import { afterAll, describe, test } from "@std/testing/bdd";
Expand Down Expand Up @@ -109,7 +109,7 @@ async function buildOrderBook(): Promise<OrderBook | undefined> {
return pgOrderBook;
}

suite("e2e: prediction market lifecycle (regtest Cashu)", () => {
suite("e2e: two-party binary bet lifecycle (regtest Cashu)", () => {
afterAll(async () => {
if (pgOrderBook) await pgOrderBook.close();
});
Expand Down
Loading
Loading