The attention quality marketplace. Brands pay for proof of understanding. Users earn USDC for demonstrating it.
- The Problem
- The Insight
- What BrandBlitz Does
- Value to Brands
- Value to Users
- How It's Different
- The Platform's Network Effect
- For Drips / Stellar Ecosystem
- Architecture Overview
- Monorepo Structure
- Tech Stack
- Prerequisites
- Quick Start (Docker)
- Quick Start (Local)
- Environment Variables
- Services & Ports
- Game Flow (Technical)
- Stellar Integration
- Anti-Cheat
- Scoring
- Deployment (Production)
- Workspace Scripts
- Packages
- Further Reading
Digital advertising is broken in two specific ways:
1. You're paying for fraud and passivity. $140 billion in global ad spend is lost to fraud every year. Up to 50% of programmatic traffic is bots. Of the humans who do see an ad, 76% don't remember viewing it at all. The industry standard metric — the impression — measures whether a pixel loaded, not whether anyone cared.
2. You can't advertise a brand people don't know. Platforms like Brave Ads reward users for viewing ads, but only for brands that already have recognition. If you're a new Stellar ecosystem project trying to reach your first 10,000 users, passive ad views teach nobody anything. You pay for exposure to people who scroll past.
Every "earn crypto" product pays fractions of a cent for passive attention. Watch an ad: $0.001 BAT. Stare at a banner: near zero. The model treats users as eyeball inventory, not as people with skills worth rewarding.
Active engagement generates 17x more purchase intent than passive ads (YouGov / Neuro-Insight research). Interactive quiz formats produce 80–91% brand recall uplift. Branded live trivia achieves 91% sponsor recall.
The research is clear: learn-then-test is categorically better than see-and-forget. But no one has wired it to real money payouts — until now.
BrandBlitz is a skill-validated attention marketplace built on Stellar USDC. The mechanic has three steps:
1. WARM-UP (30–60s)
User sees the full brand story: logo, tagline, product image,
brand description, USP. The "Start Challenge" button is locked
server-side for a minimum of 20 seconds — guaranteed exposure.
2. CHALLENGE (45s across 3 rounds × 15s each)
Round 1 — Tagline recognition: show the logo, pick the correct tagline
Round 2 — USP match: show the brand name, identify what makes it unique
Round 3 — Product recognition: show the product image, name the brand
Faster correct answers score higher (100 base + 0–50 speed bonus per round).
3. PAYOUT
Top scorers receive proportional USDC shares from the brand's prize pool.
Settlement is atomic on Stellar — 3–5 second finality, ~$0.0007 fee.
Any user who pays attention during warm-up has every answer they need. The challenge rewards attention and recall speed — both provably skill-based.
BrandBlitz does not sell impressions, clicks, or views. It sells verified attention seconds — the emerging IAB/Adelaide AU measurement standard for 2025.
When a brand deposits $100 USDC:
- Every player completed the warm-up (minimum 20s, enforced server-side — not a client timer)
- Every player attempted all 3 rounds testing recall of brand content they just saw
- Scores objectively measure which parts of the brand message landed
- High scorers are self-identified, high-intent audience segments
This is auditable, first-party data that no other ad format produces.
| Format | Recall Uplift |
|---|---|
| Standard display ad | 4–8 percentage points |
| Standard video | Baseline |
| Interactive streaming ad | +36% vs standard video (BrightLine 2024) |
| Branded live trivia | 91% sponsor recall (West Coast Fever / Komo) |
| Interactive quiz (YouGov FreeWall) | 80–86% brand recall uplift |
| BrandBlitz warm-up + challenge | Architecturally comparable to interactive formats |
After a challenge ends, brands get real data on attention quality — not vanity metrics:
- Warm-up completion rate (% who stayed the full 20s+)
- Challenge completion rate (% who finished all 3 rounds)
- Round-by-round accuracy (which brand messages actually landed)
- Score distribution histogram (were the messages clear or confusing?)
- Cost per verified attention session = pool ÷ completions
| Ad format | Approx. CPM | What you get |
|---|---|---|
| Display | $2–10 | Passive view; ~50% bot traffic; no engagement proof |
| Premium video | $20–50 | Passive viewing; 50% skip rate |
| BrandBlitz | $20–50 equiv. | Verified engagement + first-party data + organic virality |
Top scorers are self-identified brand enthusiasts — they chose to engage, proved comprehension, and competed. That's a qualified lead list that can't be bought anywhere else. Research shows first-party behavioral data drives 83% improvement in customer acquisition cost (AppsFlyer).
Every winner also generates a shareable result card: "I earned $X knowing [Brand]." That's organic brand reach through social proof, extending past the campaign end date at zero additional cost.
- Earn USDC in 90 seconds: 30s warm-up + 45s challenge + results
- Instant settlement on Stellar (no withdrawal queue, no seed phrases)
- Top 10 in a $100 pool = potentially $15–50 USDC from a single session
- This is not $0.001 BAT for staring at a banner. These are competitive prizes with real stakes.
BrandBlitz is the only attention platform where the user actually gets better over time:
- Recall speed — the speed bonus rewards improvement. Practice mode lets users sharpen before competing.
- Brand pattern recognition — experienced players develop intuition for how brands communicate (color psychology, tagline structure) that transfers across all future challenges.
- Brand knowledge — users who play regularly develop genuine understanding of brands in the Stellar ecosystem.
- Public global leaderboard rank
- Weekly league tiers: Bronze → Silver → Gold (resets weekly — fresh start for everyone)
- Challenge streaks and achievement badges
- Non-transferable Stellar SBT credentials for tier milestones (verifiable on-chain proof of performance, embeddable in portfolio/LinkedIn)
Top scorers earn exclusive access: brand Discord roles, early product access, invite-only high-prize rounds. This is a super-fan loyalty pipeline built from competition, not a sign-up form.
| Platform | Model | Payout | Unknown Brand? | Active Learning? |
|---|---|---|---|---|
| Brave Ads | Passive attention | BAT | No — needs prior recognition | No |
| Adsgram | Dev monetisation | TON | No | No |
| Coinbase Earn | Educational | USDC | No — crypto only; discontinued 2025 | Yes |
| HQ Trivia (2018) | Branded trivia | USD | Yes — showed brand content | Partial |
| Pinterest Quiz Ads | Sponsored quiz | None | Yes | Yes |
| BrandBlitz | Warm-up + challenge | USDC instant | Yes — core mechanic | Yes — core mechanic |
The combination of warm-up → competition → instant USDC payout is unoccupied.
HQ Trivia proved the model: Warner Bros., Nike, and GM paid for branded challenge rounds in 2018. Warner Bros. alone paid ~$3M for three film promotions. Users engaged. Sponsors reported "strong impact on sales, not just engagement." BrandBlitz is HQ Trivia with USDC payouts, Stellar settlement, and a micro-learning warm-up that fixes the one documented weakness of gamified ads (cognitive recall drops without a learning component).
More brands → more challenges → more user sessions
↓
More score data → better brand ROI signals
↓
More brands willing to pay premium
↑
More users → higher competition → higher quality signals for brands
Both loops reinforce each other. The data moat deepens with every challenge — which brand messages land, which user segments know which brands, which formats drive highest completion. This makes the platform more valuable to brands over time.
BrandBlitz is open-source infrastructure for skill-validated brand attention on Stellar. It demonstrates production-grade Stellar mechanics:
- Multi-winner batch USDC payouts (up to 50 Payment ops per transaction, ~$0.0007 total fee)
- Deposit detection via memo fields using Soroban RPC
getEvents - Muxed accounts — virtual sub-accounts per user with zero on-chain reserve cost (no 2 XLM minimum)
- Soroban escrow contract — trustless USDC holding with
settle()/refund()functions - Embedded wallet onboarding (no seed phrase, 30-second signup)
All patterns are documented, tested, and running in Docker.
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ Next.js 16 (App Router, SSR) │
└──────────────────────────┬──────────────────────────────────┘
│ HTTP via Nginx reverse proxy
┌───────────────┴───────────────┐
│ │
/api/* → Express 5 /* → Next.js
│
┌──────────▼──────────┐
│ Express 5 API │ JWT auth · Redis rate limiting · anti-cheat
│ apps/api │
└──────┬──────┬───────┘
│ │
pg Pool Redis
│ │
PostgreSQL BullMQ Worker ──→ Stellar Horizon
(10 tables) Soroban RPC
USDC batch payouts
The API is stateless — all game state in PostgreSQL and Redis. The BullMQ worker runs as a separate process and handles async payout jobs after a challenge's end date passes.
brandblitz/
├── apps/
│ ├── api/ Express 5 REST API + BullMQ worker
│ └── web/ Next.js 16 frontend
├── contracts/
│ └── contracts/
│ └── escrow/ Soroban USDC escrow contract (Rust)
├── packages/
│ ├── stellar/ Stellar SDK helpers (payout, deposit, muxed accounts)
│ └── storage/ S3/MinIO client + WebP image optimisation
├── nginx/ Dev and prod reverse proxy configs
├── docker-compose.yml 8 services: web, api, worker, postgres,
├── docker-compose.override.yml redis, nginx, minio, minio-setup
├── docker-compose.prod.yml
├── init.sql Full PostgreSQL schema (10 tables, indices, triggers)
└── .env.example All required environment variables with documentation
| Layer | Technology | Version |
|---|---|---|
| Frontend | Next.js App Router | 16.2.1 |
| React | React | 19.2.4 |
| Styling | Tailwind CSS v4 (CSS-first) | 4.2.2 |
| Auth | next-auth (Google OAuth) | 4.24.13 |
| Backend | Express | 5.2.1 |
| Language | TypeScript | 6.0.2 |
| Database | PostgreSQL | 17 |
| Cache / queues | Redis + BullMQ | 7 / 5.71.1 |
| Object storage | MinIO (dev) / S3-compatible (prod) | — |
| Stellar SDK | @stellar/stellar-sdk | 14.6.1 |
| Smart contracts | Soroban (Rust, soroban-sdk 25) | — |
| Monorepo | Turborepo + pnpm workspaces | 2.8.21 / 10.33.0 |
- Node.js 22+
- pnpm 10.33.0 —
npm install -g pnpm@10.33.0 - Docker + Docker Compose v2
- Rust + Stellar CLI (smart contract work only)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shcargo install --locked stellar-cli@25 --features opt
cd brandblitz
# Configure environment — minimum required:
# JWT_SECRET, NEXTAUTH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
# STELLAR_HOT_WALLET_SECRET
cp .env.example .env
# Start all 8 services (hot-reload enabled in dev)
docker compose up --build
# Verify
open http://localhost # Next.js frontend
curl http://localhost/api/health # {"status":"ok"}
open http://localhost:9001 # MinIO console (brandblitz / brandblitz123)docker compose up auto-loads docker-compose.override.yml in development, which bind-mounts source directories for hot-reload and exposes direct service ports.
# Infrastructure in Docker, apps native
docker compose up postgres redis minio minio-setup
pnpm install
cp .env.example .env # update DATABASE_URL, REDIS_URL to localhost
pnpm dev # Turborepo runs all packages in parallelSee .env.example for all variables with inline documentation. Minimum to get running:
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
REDIS_URL |
Redis connection string |
JWT_SECRET |
Sign API JWTs (64+ chars in prod) |
NEXTAUTH_SECRET |
next-auth session encryption |
NEXTAUTH_URL |
Public URL of the web app |
GOOGLE_CLIENT_ID/SECRET |
Google OAuth credentials |
STELLAR_HOT_WALLET_SECRET |
Stellar keypair for payouts |
STELLAR_NETWORK |
testnet or public |
S3_* |
Storage endpoint, credentials, bucket |
WEBHOOK_SECRET |
Protects /webhooks/stellar |
| Service | Port | Description |
|---|---|---|
| nginx | 80 | Reverse proxy; routes /api/* → Express |
| web | 3000 | Next.js frontend |
| api | 3001 | Express REST API |
| worker | — | BullMQ payout processor (no HTTP) |
| postgres | 5432 | PostgreSQL 17 |
| redis | 6379 | Redis 7 |
| minio | 9000 | MinIO S3 API |
| minio-console | 9001 | MinIO admin UI |
Only port 80 (and 443 in prod) is exposed externally in the Docker stack.
POST /sessions/:id/warmup-start ← session row created (status=warmup)
POST /sessions/:id/warmup-complete ← server validates ≥20s elapsed;
returns challengeToken (5-min TTL)
POST /sessions/:id/start ← validates token; status=active
POST /sessions/:id/answer/1 ← server validates + scores round 1
POST /sessions/:id/answer/2 ← round 2
POST /sessions/:id/answer/3 ← round 3; status=completed
[challenge.ends_at passes]
BullMQ job fires → rank all sessions → calculate proportional shares
→ build Stellar batch tx (≤50 Payment ops) → submit → record tx_hash
Correct answers never leave the server. The client only receives round scores.
Brand sends USDC to the hot wallet with a unique memo (e.g. BLITZ-A1B2C3). The Stellar webhook endpoint or background polling detects the deposit via getEvents on the Soroban RPC. Challenge transitions pending_deposit → active.
Single hot wallet + muxed accounts. No per-user Stellar accounts (no 2 XLM minimum reserve per user). Payout job chunks winners into batches of ≤50 Payment ops, each batch submitted as one atomic transaction.
For brands wanting full on-chain transparency, the escrow contract holds USDC trustlessly. settle(recipients) distributes to winners; refund() returns the pool to the brand. See contracts/README.md.
Five independent layers:
| Layer | Mechanism |
|---|---|
| Server authority | Correct answers and scores never sent to client |
| Warmup enforcement | Server rejects challenge start if < 20s since warmup began |
| Reaction time bounds | Answers < 150ms (physically impossible) or > 30s flagged |
| Device fingerprinting | FingerprintJS visitor ID; multi-account device detection via Redis |
| Rate limiting | Redis-backed: 5 challenge starts/hr, 100 API calls/15min per IP |
Per round (correct answer): 100 base + floor((timeLeft / 15s) × 50) speed bonus
Per round (wrong answer): 0 (no penalty — keeps engagement positive)
Maximum total: 450 points (3 rounds × 150)
Payout share: userScore / sumOfAllWinnerScores × prizePool
Only users with at least one correct answer receive a payout share.
git clone <repo> brandblitz && cd brandblitz
cp .env.example .envEdit .env with the following production-specific values:
NODE_ENV=production
# Use a real S3-compatible provider (Cloudflare R2, AWS S3) — remove MinIO vars
S3_ENDPOINT=https://<accountid>.r2.cloudflarestorage.com
S3_REGION=auto
S3_ACCESS_KEY_ID=<r2-access-key>
S3_SECRET_ACCESS_KEY=<r2-secret-key>
S3_BUCKET=brandblitz-assets
S3_PUBLIC_URL=https://assets.yourdomain.com
# Remove S3_FORCE_PATH_STYLE for R2/AWS
# Switch to Stellar mainnet
STELLAR_NETWORK=public
STELLAR_HORIZON_URL=https://horizon.stellar.org
STELLAR_RPC_URL=https://mainnet.sorobanrpc.com
# Mainnet USDC issuer (Centre)
USDC_ISSUER=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
# Generate strong secrets (run each command separately)
# openssl rand -hex 64 → JWT_SECRET
# openssl rand -hex 64 → NEXTAUTH_SECRET
# openssl rand -hex 32 → WEBHOOK_SECRET
# Set your domain
NEXTAUTH_URL=https://yourdomain.com
NEXT_PUBLIC_API_URL=https://yourdomain.com/apiIn the Google Cloud Console:
- Add
https://yourdomain.com/api/auth/callback/googleas an authorised redirect URI - Add
https://yourdomain.comas an authorised JavaScript origin
Edit nginx/nginx.prod.conf and replace ${DOMAIN} with your domain, or set it as a shell variable before starting:
export DOMAIN=yourdomain.comEnsure Let's Encrypt certificates exist at /etc/letsencrypt/live/${DOMAIN}/.
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
# Verify all services are healthy
docker compose ps
# Check logs
docker compose logs -f api
docker compose logs -f webProduction differences from dev:
- No bind mounts — fully built images
restart: unless-stoppedon all services- Nginx serves HTTPS with HSTS, X-Frame-Options, rate limiting
- API and web each run 2 replicas for zero-downtime deployments
- MinIO removed — use Cloudflare R2 or AWS S3
- Worker retries failed payout jobs with exponential back-off
pnpm dev # Start all apps in parallel (Turborepo)
pnpm build # Build all packages and apps
pnpm test # Run all test suites
pnpm lint # Lint all packages
pnpm type-check # TypeScript type-check everything
pnpm format # Prettier format all .ts/.tsx/.json files
pnpm clean # Remove all build artifacts and node_modulesTyped helpers for all Stellar network interactions. Imported by apps/api.
| File | What it does |
|---|---|
src/client.ts |
Factory for Horizon Server and Soroban RPC.Server instances; getUsdcAsset() |
src/deposit.ts |
Polls Soroban RPC getEvents to detect USDC deposits by memo; returns DepositEvent[] |
src/payout.ts |
submitBatchPayout(recipients) — builds a TransactionBuilder with up to 50 Payment ops and submits it atomically |
src/accounts.ts |
createMuxedAddress(baseAddress, muxedId) — virtual sub-accounts per user at zero reserve cost; sponsorNewAccount() for CAP-0033 sponsored reserves |
src/constants.ts |
Network configs, MAX_OPS_PER_TX=50, WARMUP_MIN_SECONDS=20, poll intervals |
src/index.ts |
Re-exports everything |
S3-compatible object storage with image optimisation. Imported by apps/api. Works with MinIO in dev and Cloudflare R2 / AWS S3 in prod — same code, different env vars.
| File | What it does |
|---|---|
src/client.ts |
S3Client configured from env; BUCKETS constant; getPublicUrl(key) |
src/optimize.ts |
optimizeImage(key, type) — fetches from S3, runs sharp WebP resize, overwrites: logos → 400×400 contain, products → 800×600 inside, avatars → 200×200 cover |
src/index.ts |
Re-exports everything |
apps/api/README.md— Full API reference, all routes, middleware, services, database schemaapps/web/README.md— Frontend pages, components, auth flow, game state machine, upload flowcontracts/README.md— Soroban escrow contract: build, test, deploy, full function reference