This document is a walkthrough of what the product is, how the crates fit together, and what each file is for. It describes the tree as it exists today, not a future wishlist.
Related docs (do not duplicate them here):
README.md— architecture table, RP rules, quick-start commandsdocs/RUNBOOK.md— deploy, secrets, seeding, launch checklistdocs/LEGAL.md/docs/PRIVACY.md/docs/TOS.md— legal postureprograms/auctioning/DEPLOY.md— Anchor build/deploy steps
auctioning.lol is a reputation board for projects people care about. Users earn RP (reputation points), allocate it to projects, and watch those projects race. Live races tick on a MagicBlock ephemeral rollup (~50 ms); final rankings and paid-RP receipts settle on Solana mainnet.
The economy is split on purpose:
| Kind | Where it lives | Cashable? | How you get it |
|---|---|---|---|
| Paid RP | Postgres and an on-chain RpReceipt |
No (consumable utility). Provenance is public. | Whop fiat purchase → webhook credits the private ledger → payer optionally signs log_paid_rp |
| Free / promo RP | Postgres only (free_rp_lots, FIFO, expires next Monday 00:00 UTC) |
Never. Not transferable, not on-chain. | Weekly stipend, content reads, event multipliers |
Nothing in the race engine writes free RP on-chain. Rank, velocity, momentum, overtakes, and photo finishes are derived from the append-only project_allocations table.
Legal posture (product, not advice): free RP is a promotional thank-you; paid RP is consumable gameplay utility; no yield, no secondary market, no investment framing. See docs/LEGAL.md.
Declared program id (current tree): 3GGYRVymmKQhmxP9nw9yPs8HCf7YWw7WViPjkKFkZNGs
Program keypair path: gitignored keys/auctioning-keypair.json (never under target/deploy).
SOON Stack is explicitly a future migration path. This repo does not build on it.
Phantom (browser)
│
▼
app/leptos-auctioning (CSR WASM)
│ HTTP JSON │ sign + send unsigned tx
▼ ▼
backend/shuttle-auctioning Solana mainnet
Shuttle + Axum + Postgres programs/auctioning (Anchor)
│ ▲
│ ticks / settle │ settle_race
▼ │
magicblock/ MagicBlock ER
race session client (live ~50ms ticks)
│
▼
marketing/ (Next.js static — Vercel)
tools/seeder/ (outbid.lol → /v1/projects/import)
Signing model: the Shuttle backend never holds user keys. It builds an unsigned Solana transaction (recent blockhash + Anchor instruction bytes), returns it as base64, and the Leptos app asks Phantom to sign and broadcast.
Two race concepts (easy to confuse):
- On-chain races — PDA
["race", project, race_id]on the Anchor program. Opened/settled viaopen_race/settle_race. Postgres tableracesis the private mirror (er_session,settle_tx). - Race windows — category tracks (Sprint / GP / Championship) in
race_windows. The engine scores the catalog board fromproject_allocations. This is the “grid” the news/narrative layer consumes.
Rust workspace (Cargo.toml at repo root, resolver 2, edition 2021):
| Member | Crate name | Role |
|---|---|---|
programs/auctioning |
auctioning |
Anchor program (cdylib + lib) |
backend/shuttle-auctioning |
shuttle-auctioning |
HTTP API, private ledger, race worker |
app/leptos-auctioning |
leptos-auctioning |
Wallet dApp (CSR WASM + bin) |
magicblock |
auctioning-magicblock |
ER session client + settlement helpers |
Non-workspace (but first-class):
| Path | Role |
|---|---|
marketing/ |
Next.js 15 static marketing site |
tools/seeder/ |
Python outbid.lol snapshot importer |
docs/ |
Runbook + legal |
keys/ |
Gitignored program keypair directory (keys/*.json ignored; directory kept) |
site/marketing/ |
Stale duplicate of an older marketing tree — ignore |
.github/workflows/ci.yml |
cargo test for the three Rust crates |
paid— mirrors chainfree_weekly— Monday-bound stipendbonus— content / narrative rewardsevent_multiplier— operator windows (Double RP Hour, etc.)
Inflows must carry a source. Spends drain earliest-expiring free lots first, then paid. Allocations to projects record the exact lot_id they drained.
| Table | Introduced | Purpose |
|---|---|---|
wallets |
0001 | paid_rp / free_rp (cache) / spent_rp |
ledger_events |
0001, typed in 0003 | Append-only movements (paid, free, spend, expire) |
weekly_claims |
0001 | One claim per wallet per ISO week |
whop_members |
0001 | Membership cache |
races |
0001 | On-chain race mirror |
content_items / content_reads |
0002 | Story items that pay small free RP |
projects |
0002, stable_id in 0003 |
Catalog (manual + outbid import) |
free_rp_lots |
0003 | FIFO expiry lots |
project_allocations |
0003 | Immutable per-project RP ledger (race engine input) |
race_windows / rank_snapshots / race_events |
0004 | Windowed grid + narrative events |
narrative_posts |
0005, publish cols in 0006 | Per-event, per-channel copy |
er_ticks |
0006 | MagicBlock tick ingest (session_id, seq) |
oauth_tokens / oauth_states |
0006 | SuperGrok Heavy PKCE (never on public tape APIs) |
Boot (every Shuttle start): run migrations → seed content → expire_due_lots → reconcile_free_rp_cache → ensure default race window → spawn race worker.
| File | What it is |
|---|---|
Cargo.toml |
Workspace members + release overflow-checks |
Cargo.lock |
Locked dependency graph |
Anchor.toml |
Program id on localnet/devnet/mainnet; provider cluster localnet; test script cargo test -p auctioning |
README.md |
Product intro, RP model, commands, race/narrative endpoints |
AUCTIONING.md |
This map |
.env.example |
Env names only (paths, flags, URLs). Copy to .env; never commit .env |
.gitignore |
target/, Secrets.toml, keys/*.json, .env, Trunk dist/, Next .next/ / out/ |
.github/workflows/ci.yml |
On push/PR: test auctioning, auctioning-magicblock, shuttle-auctioning |
Anchor 1.1 program. Free RP never enters it.
| File | What it is |
|---|---|
src/lib.rs |
#[program] surface: initialize, register_project, log_paid_rp, open_race, settle_race |
src/state.rs |
declare_id!, accounts (Config, Project, RpReceipt, Race, RaceResult), errors, PDA seeds (config, project, receipt, race) |
src/instructions.rs |
Handlers + Accounts structs. Paid RP CPI-transfers lamports to fee vault and writes an immutable receipt PDA. Race PDA uses pre-bump nonce. Settle is authority-gated |
Anchor.toml |
Nested program config (same id as workspace Anchor.toml) |
Cargo.toml |
cdylib + lib, no-entrypoint / cpi features, fat LTO release |
DEPLOY.md |
Keygen path (keys/), anchor build / deploy, initialize, MagicBlock notes, security |
idl/auctioning.json |
IDL for the five instructions (address = current program id) |
tests/pda_contract.rs |
Pins PDA derivations so clients stay in sync |
tests/state.rs |
Account layout / space tests |
PDA cheat sheet
- Config:
["config"] - Project:
["project", owner] - Receipt:
["receipt", project, seq_le_bytes]whereseq == receipt_countbefore the ix - Race:
["race", project, race_id_le_bytes]whererace_id == race_noncebefore open
| File | What it is |
|---|---|
Cargo.toml |
auctioning-magicblock; solana-client/sdk 2; optional tokio |
src/lib.rs |
Re-exports race_session |
src/race_session.rs |
RaceSession / RaceSessionConfig / TickEnvelope. Buffers ticks, ranks (score desc, earliest-tick tiebreak), packs settle_race (discriminator = sha256(global:settle_race)[..8]), talks mainnet + ER RPC. Delegation program id is pinned |
Used by shuttle-auctioning as path dep auctioning-core.
Shuttle 0.57 + Axum 0.8 + sqlx 0.8 (pinned: shuttle-shared-db 0.57’s PgPool bridge).
| File | Lines (approx) | What it is |
|---|---|---|
lib.rs |
~300 | Shuttle main: config validation, migrations, boot sweeps, race worker, all HTTP routes, CORS/timeout/body-limit/request-id layers |
auth.rs |
~450 | Sign-In-With-Solana nonces + sessions, AuthedWallet / Operator / Ingest extractors, constant-time compare |
ratelimit.rs |
~150 | In-process per-IP fixed-window limiter as a tower layer |
stats.rs |
~350 | BI reads: overview, per-project telemetry, wallet history, operator revenue |
config.rs |
98 | AppConfig from Shuttle SecretStore (Whop, PROGRAM_ID, RPCs, ingest, SuperGrok, narrative LLM) |
error.rs |
46 | AppError → HTTP (404, 400, 401, 429, 409, 500) |
handlers.rs |
637 | Thin HTTP adapters over ledger/catalog/whop/onchain/race_engine |
ledger.rs |
901 | Wallet ensure, FIFO lots, credit/spend, weekly claim, content rewards, expire/reconcile, race row CRUD |
catalog.rs |
524 | Idempotent stable_id upserts, support/allocate, allocation history |
whop.rs |
192 | HMAC webhook verify, membership lookup, paid credit intent |
onchain.rs |
403 | Unsigned tx prep: register / log_paid / open_race / settle_race. Discriminators + PDA math. program_id from cfg |
race_engine.rs |
764 | Pure scorer: ranks, velocity, momentum, overtakes, photo finishes, dark-horse; snapshots + events |
race_worker.rs |
270 | Background: 15s timeout scan; optional ER_WS subscribe. Builds delegate ixs when an authority key exists but does not send SOL |
ticks.rs |
457 | POST .../ticks ingest + session grid. Tick rows never mutate allocation-derived events |
narrative.rs |
719 | Templates for X / TikTok / Instagram / newsletter / timeline. Optional LLM polish; always falls back to template |
oauth_llm.rs |
559 | SuperGrok Heavy PKCE. Tokens stay in oauth_tokens |
publish.rs |
291 | Operator state machine draft → approved/skipped/published/failed. Does not post to social networks |
| File | What it adds |
|---|---|
0001_init.sql |
wallets, ledger_events, weekly_claims, whop_members, races |
0002_content_projects.sql |
content_items, content_reads, projects |
0003_rp_sources_and_seeding.sql |
typed sources, free_rp_lots, project_allocations, stable_id |
0004_race_engine.sql |
unique tx_id, race_windows, rank_snapshots, race_events |
0005_narrative_posts.sql |
narrative_posts |
0006_ticks_publish_oauth.sql |
er_ticks, publish_status, oauth tables, er_tick event type |
0007_operator_events.sql |
operator_events (Afterburner etc.), wider race types |
0008_project_clicks.sql |
projects.clicks |
0009_auth_bi_hardening.sql |
auth_nonces, auth_sessions, whop_webhook_log, snapshot paid/community split, hot-path indexes, BI views |
| File | What it is |
|---|---|
tests/integration_rp.rs |
Dual-source RP + lots + catalog against real Postgres (self-skips without DATABASE_URL) |
tests/http_auth.rs |
Router-level tests: sign-in, wallet binding, operator/ingest gates, Whop HMAC + idempotency |
tests/smoke_db.rs |
Postgres smoke (--features sqlx-test) |
Secrets.toml.example |
Secret names for Shuttle. Real Secrets.toml is gitignored |
Cargo.toml |
Shuttle metadata name = "auctioning-backend" |
Health / RP
GET /healthzGET /v1/rp/{wallet}POST /v1/rp/earn— ingest-gatedPOST /v1/rp/spendPOST /v1/rp/claim-weekly
Content & catalog
GET /v1/content·POST /v1/content/readGET /v1/projects·POST /v1/projects/importGET /v1/projects/{handle}·POST .../support·GET .../allocationsGET /v1/projects/{wallet}/public— explorer link for paid provenance
Whop
POST /v1/whop/webhookGET /v1/whop/membership/{wallet}
On-chain prep (unsigned txs for Phantom)
POST /v1/onchain/prepare-registerPOST /v1/onchain/prepare-log-paidPOST /v1/onchain/prepare-open-racePOST /v1/onchain/prepare-settle-race
On-chain race mirror
POST /v1/races/openPOST /v1/races/{project_pda}/{race_id}/settleGET /v1/races/{project_pda}
Windowed race engine
GET /v1/gridGET /v1/races/windowsGET /v1/races/windows/{slug}/gridPOST /v1/races/windows/{slug}/snapshotGET /v1/races/windows/{slug}/eventsPOST /v1/races/windows/{slug}/events/{event_id}/narrateGET /v1/races/windows/{slug}/tapePOST /v1/races/windows/{slug}/ticks—X-Auctioning-IngestGET /v1/races/sessions/{session_id}/grid
Narrative / OAuth (operator)
GET /v1/oauth/supergrok/login|callback|statusGET /v1/narrative/queuePOST /v1/narrative/posts/{id}/approve|skip|mark-published
CORS is locked to ALLOWED_ORIGINS outside dev. Every response carries
x-request-id; bodies are capped at 1 MiB; requests time out at 20 s.
Auth (added in the enterprise-hardening pass)
GET /v1/auth/nonce·POST /v1/auth/verify·GET /v1/auth/me·POST /v1/auth/logout- Wallet-bound writes (
AuthedWalletextractor):claim-weekly,spend,support,content/read,wallets/me/history - Operator (
Operatorextractor,X-Auctioning-Operator): snapshot, narrate, narrative queue/approve/skip/mark-published, OAuth login/status,races/open,races/{pda}/{id}/settle,stats/revenue - Machine (
Ingestextractor,X-Auctioning-Ingest):rp/earn,projects/import, ticks,events/afterburner
Business intelligence
GET /v1/stats/overview·GET /v1/projects/{handle}/stats·GET /v1/wallets/me/history·GET /v1/stats/revenue
Leptos 0.8 CSR. trunk serve on port 3000. API base from AUCTIONING_API_BASE (default Shuttle prod URL).
| File | What it is |
|---|---|
src/main.rs |
Panic hook + mount_app() |
src/lib.rs |
Entire UI. Components: App (connect, weekly claim, RP view), LiveGrid, RaceTape, ProjectBoard, ProjectPage, Web3Actions (register / log paid / Whop / races open-list-settle). Phantom is raw JS interop |
index.html |
Trunk entry. Loads phantom.js then @solana/web3.js@1.91.0 IIFE (dev/demo CDN) |
public/phantom.js |
Tiny facade: hasPhantom, connect, disconnect, signMessageUtf8, eager reconnect |
style.css |
App styles |
Trunk.toml |
Build index.html → dist/, serve :3000 |
Cargo.toml |
cdylib + rlib + bin; wasm-friendly reqwest |
Web3Actions talks to the four /v1/onchain/prepare-* endpoints, then Phantom::send_transaction. Program id from the prep response is shown and linked to Solana Explorer.
Next.js 15 App Router, intended static export → Vercel. No server functions. Keep claims aligned with docs/LEGAL.md.
| File | What it is |
|---|---|
package.json |
next ^15, react ^19 |
README.md |
Build/deploy notes |
app/layout.tsx |
Root layout |
app/globals.css |
Global styles |
app/page.tsx |
Hero + three pillars (stipend / races / provenance) |
app/legal/page.tsx |
AU-safe posture page |
app/privacy/page.tsx |
Privacy |
app/tos/page.tsx |
Terms |
tsconfig.json |
TS config |
marketing/.next/ and marketing/out/ are build artifacts (gitignored). Do not treat them as source.
| File | What it is |
|---|---|
outbid_seed.py |
Normalizes a JSON snapshot (or best-effort live scrape) into ImportProject rows. Dry-run by default; --push hits /v1/projects/import with INGEST_SECRET. Idempotent on stable_id (outbid:…) |
seed.sample.json |
Tiny sample snapshot |
| File | What it is |
|---|---|
README.md |
Shorter twin of root README |
RUNBOOK.md |
Deploy: program, Shuttle, Postgres smoke, Leptos, Vercel, seeder, MagicBlock, launch checklist |
LEGAL.md |
Design-level AU notes (not advice) |
PRIVACY.md / TOS.md |
Privacy / terms copy |
Gitignored *.json. Durable home for the program keypair. Copy into programs/auctioning/target/deploy/ only at deploy time (cargo build-sbf wipes target/).
Leftover nested site/marketing/. Not part of the workspace. Prefer marketing/.
First 8 bytes of sha256("global:<name>") (Anchor sighash). Backend onchain.rs hard-codes the same bytes the MagicBlock client uses for settle:
| Instruction | Role |
|---|---|
initialize |
One-time Config PDA (authority, fee vault, fee_bps ≤ 5000) |
register_project |
Create Project PDA for the signing owner |
log_paid_rp |
Transfer lamports + write RpReceipt |
open_race |
Init Race PDA from current race_nonce |
settle_race |
Commit Vec<RaceResult> (max 16). Settler = race authority or protocol authority |
- Connect wallet —
app/.../lib.rsPhantom+public/phantom.js - Claim weekly free RP —
POST /v1/rp/claim-weekly→ledger::claim_weekly→free_rp_lotsexpiring next Monday 00:00 UTC - Support a project —
POST /v1/projects/{handle}/support→catalogdrain lots →project_allocations - Buy RP — Whop checkout →
POST /v1/whop/webhook→whopHMAC →ledger::credit_paid - Public receipt — dApp
prepare-log-paid→onchain.rsunsigned tx → Phantom →log_paid_rponprograms/auctioning - Watch a GP —
GET /v1/races/windows/{slug}/grid→race_engineover allocations - Live ER race — worker / ticks →
er_ticks→ optionalsettle_racevia MagicBlock client - Narrative tape —
narratetemplates (narrative.rs) → operator approve (publish.rs) — no auto-post
cargo test -p auctioning # program logic + PDA contract
cargo test -p auctioning-magicblock # ER client
cargo test -p shuttle-auctioning # ledger / engine / Whop HMAC (unit)
# Postgres smoke:
# DATABASE_URL=postgres://... cargo test -p shuttle-auctioning --features sqlx-test
cd app/leptos-auctioning && trunk serve # :3000
cd marketing && npm install && npm run dev
./tools/seeder/outbid_seed.py --snapshot tools/seeder/seed.sample.jsonCI (.github/workflows/ci.yml) runs the three cargo test -p commands; it does not build WASM or the Next site.
Loaded from Shuttle SecretStore / Secrets.toml (gitignored) and optionally .env:
WHOP_API_KEY, WHOP_WEBHOOK_SECRET, WEEKLY_FREE_RP, PROGRAM_ID, MAINNET_RPC, ER_RPC, ER_WS, AUTHORITY_SECRET, MAX_RACE_SECS, INGEST_SECRET, NARRATIVE_LLM_*, SUPERGROK_*.
Empty ER_WS keeps the race worker on ping-only. INGEST_SECRET gates earn/import/ticks. PROGRAM_ID overrides the compile-time default in config.rs.
In the tree and wired
- Dual-source private ledger + FIFO free lots + boot reconcile
- Project catalog + outbid seeder
- Windowed race engine + snapshots + events
- Narrative templates + SuperGrok PKCE + operator publish queue
- Whop webhook HMAC + membership
- Anchor program source + IDL + PDA tests
- Backend unsigned-tx prep for all five user-facing on-chain ixs
- Leptos dApp: Phantom connect, weekly claim, board, Web3Actions, live grid / tape
- MagicBlock session client + tick ingest + worker stub
Still operator / env work (not missing source files)
- Actual
anchor deploy+ one-timeinitializeon mainnet (CLI + funded keypair) PROGRAM_ID/Secrets.tomlfilled in the deploy environment- CORS lock, ingest secret required in prod, authority key in Vault
- SuperGrok redirect URI registered
- Production bundling of
@solana/web3.js(CDN is demo) - Lawyer review of legal pages + Whop copy
site/marketing/leftover can be deleted when convenient