PackX402 beta: core data model, fairness engine, x402 payments, CardTrader adapter, auth, docs - #1
Draft
The-Daly wants to merge 21 commits into
Draft
PackX402 beta: core data model, fairness engine, x402 payments, CardTrader adapter, auth, docs#1The-Daly wants to merge 21 commits into
The-Daly wants to merge 21 commits into
Conversation
…2 payments, CardTrader adapter, auth, docs, CI - Full 59-table Drizzle/Postgres schema covering every spec entity - Deterministic provably-fair selection engine with a published, hand-verified test vector - x402 Algorand/Solana/EVM payment adapters (mock-default; live paths via direct chain verification) - CardTrader SupplierAdapter (mock + live), idempotent purchases, cart-safety checks - Auth primitives: hashed sessions, SIWE-style wallet messages, real per-chain signature verification (Algorand/Solana/EVM) each tested against a generated keypair - Server-side age-gate/eligibility policy, responsible-purchasing limits, loyalty calc - Landing, marketplace, pack-detail, provably-fair verifier, and odds-library pages - Global security headers, CSRF double-submit cookie, Redis rate-limit helper - 77 passing Vitest tests, clean strict typecheck, clean production build - Full required documentation set, GitHub Actions CI, issue/PR templates, CODEOWNERS - Renamed project from Pack402 to PackX402 per user request See PROJECT_STATUS.md for the authoritative implemented/unverified/not-started split. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wires the tested auth-service (signup/login/wallet-verify/session logic) into /api/auth/* route handlers, introduces the PackArt component system (PackArt, PackCarousel, OpeningStage, FourPackStage, ResultEffect, CardBack) wired into the landing/marketplace/pack-detail pages, installs real Higgsfield-generated art for 10 unlocked tiers, relaxes CSP eval() only in dev to unbreak Next.js HMR, and moves the tier-lock threshold from $25 to $250 to match current supplier-fulfillment bankroll. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Regenerates all 10 unlocked tier packs in a cartoon/Pokemon-style (cel-shaded, starburst crest, "PackX402" wordmark, no gold, transparent cutouts for 3D interactions). Adds a real opening-theater flow at /packs/[tierKey]/open: browse via a 3D-perspective carousel, drag-to-rip the pack open (RipToOpen), watch the card-reveal wheel spin down to the winning card (CardRevealWheel), with an occasional low-probability cosmetic coin-flip flourish (CoinFlip) that never changes the real outcome or its odds. Wires the real x402 endpoint end-to-end; since no wallet-connect UI exists yet, the real payment requirement is shown honestly rather than faking a purchase. Also adds src/server/card-images/resolver.ts (the documented supplier-photo -> PSA -> CardTrader -> public-catalog -> fallback priority chain, with a domain-allowlist/SSRF guard, tested), replaces the flat 1.15x procurement price cap with a tapering per-tier max-obtainable-value multiplier (Spark $0.50 -> $25 cap down to Genesis's 2x), and surfaces per-card reference values plus a max-obtainable-value summary on the pack-detail page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Removes passwordless-email signup/login entirely (routes and auth-service functions) per product decision: Google sign-in (Auth.js/NextAuth v5, mounted at /api/oauth to avoid colliding with the existing wallet session routes) and direct wallet signature are now the only two ways into a PackX402 account. Adds an oauthIdentities table keyed by Google's stable subject id, and closes a real pre-existing gap: createPackOffer() previously never checked eligibility at all for any auth method; it now rejects any offer for a user with no passing age/location eligibility record, with a new POST /api/auth/oauth/complete-eligibility endpoint to satisfy it. CardImageResolver's public-catalog provider now makes real live calls to the Pokemon TCG API and YGOPRODeck (both free/keyless) instead of always returning null, resolving actual card images by name for both games - confirmed working from this environment. Tests updated to mock fetch for determinism. Fixes the drag-to-rip gesture: switches from a vertical drag to a horizontal drag-across-the-top gesture with a visible handle, adds a synthesized tear sound (Web Audio API, no asset file) and a sparkle burst along the seam, and corrects the tear seam position from a 50/50 split down the middle to a small strip near the top like a real foil pack. Restructures the open-pack page so the rip/reveal stage sits above the carousel and only appears after a pack is explicitly selected (tap), not just scrolled past. Adds a no-database /dev/rip-preview page for demoing the animation without Postgres. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CardRevealWheel now cycles through actual possible pulls from the pack's pool during the spin (via a new resolveCardImages batch resolver and /api/packs/[tierKey]/spin-preview endpoint) instead of blank card backs - confirmed live against the real Pokemon TCG API. Required adding the image hosts to next.config.ts's remotePatterns (next/image refused to load them otherwise - a real bug caught while verifying in-browser). Replaces the open-pack page's infinite carousel with PackShelf: uniform-size packs in a horizontally scrollable row, sorted ascending by price left to right, each with its own pay button. Adds PaymentMethodPanel showing wallet (the one real, functional method), plus Apple Pay and PayPal rendered realistically but disabled - neither has a real merchant credential configured, so they're documented as not-yet-live rather than faked as working, matching this repo's existing pattern for CardTrader/x402 live mode. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two real bugs from user testing: the drag surface was a thin handle strip pinned to the top instead of the whole pack, and the torn-off top piece used a straight inset() clip-path — since the pack's "PackX402" wordmark sits below the actual foil seam, cutting a clean horizontal line left the full wordmark visible on both the flying-away piece and the stationary pack underneath, reading as a duplicated top. Replaces the straight cut with a shared zigzag polygon clip-path so both pieces interlock along a genuinely torn-looking boundary with no overlap, and moves the drag handlers onto the entire pack area (the handle graphic at the top is now a decorative hint only, not the sole interactive target). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reverses the earlier "cosmetic only" coin-flip decision per explicit user request: a fixed 4% chance, evaluated server-side on every completed pack opening from the SAME committed fairness seed as the primary pull (never client-side Math.random), of awarding a second real card from the same pool. Both the hit/miss determination and the bonus card pick are independently verifiable exactly like the primary pull, via domain-separated derivations (deriveBonusFlipHit/selectBonusPoolEntry in src/server/fairness/engine.ts). Schema: rips.packOfferId/paymentId are no longer individually unique - a "primary" and a "bonus_flip" rip can now both exist against the same paid offer (nothing is paid twice), enforced instead by a (packOfferId, kind) composite unique index. CoinFlip.tsx now animates a server-determined result instead of generating its own random flip. The x402 open endpoint returns the real bonusFlip outcome; OpenPackClient and the no-DB dev preview both wired to show the second card on a hit. Flags the real EV/odds impact in docs/LEGAL_REVIEW_REQUIRED.md and documents the mechanic in docs/FAIRNESS_PROTOCOL.md - not yet reflected in the published odds table, which still needs updating before this ships publicly. Also replaces the open-pack page's carousel with PackShelf (uniform-size packs, ascending price left to right, per-pack pay button) built as structural inspiration from a reference app's UI patterns (not its branding), per user direction not to copy it directly - explicitly excludes that reference's real-money cash-out mechanic, which conflicts with PackX402's documented "physical cards only, not an investment platform" positioning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Applies a fixed odds table (Common 25% / Uncommon 24.8% / Rare 16.1% / Epic 16.1% / Legendary 14% / Grail 4%, summing to exactly 100.00%) uniformly across all 14 pack tiers, with each rarity's price band scaled proportionally to that tier's own price - e.g. a $5 pack's Grail band is $9-$100 (20x), matching the user-specified example exactly (verified by test). The max-obtainable-value cap now derives directly from the Grail band's upper bound, superseding the earlier separate tapering-multiplier schedule. Extracted into src/server/packs/rarity-bands.ts as pure, testable logic rather than buried in the seed script; db:seed rewritten to pick one representative fixture per rarity band from the mock CardTrader ladder. Also adds GET /api/packs/openings, a user-scoped pack-opening history endpoint (only the authenticated user's own rips, via packOffers.userId) - deliberately separate from /api/fairness/verify, which stays public-by-ripId on purpose since that's what makes a proof independently verifiable by any third party, not just the pack's owner. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes the header's "Log in"/"Get started" links, which pointed to /login and /signup - pages that never existed (a pre-existing gap, not something this session introduced) - by wiring in the real Google sign-in button and a working logout button. Auth state is checked client-side via /api/auth/session after mount rather than in the root layout via cookies(): the latter was tried first and forced every single page in the app to render dynamically instead of statically, a real regression caught by comparing build output before/after. Adds /collection, a simple page listing the signed-in user's own pack openings (card, set, tier, value, date, a link into the fairness verifier), consuming the user-scoped GET /api/packs/openings endpoint added earlier. Also teaches the fairness verifier form to read a ?ripId= query param and auto-verify, so the "Verify" link from /collection actually works instead of just prefilling the page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closes the gap flagged in docs/LEGAL_REVIEW_REQUIRED.md: the 4% bonus-flip
chance changes every tier's effective EV/odds but wasn't shown anywhere a
buyer would see it before purchasing. Adds a one-line disclosure ("Bonus
flip: 4% chance of a second card on every opening") next to the existing
max-obtainable-value figure. Updates the legal-review note and
PROJECT_STATUS.md to reflect that this is now disclosed, while making clear
disclosure isn't the same as legal clearance - counsel still needs to weigh
in on whether a disclosed "extra" reward changes the gambling-regulation
analysis.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Installs @txnlab/use-wallet-react + @txnlab/use-wallet's full real peer dependency set (@perawallet/connect, @blockshake/defly-connect, @walletconnect/modal + sign-client, @agoralabs-sh/avm-web-provider, lute-connect) - use-wallet bundles dynamic imports for every connector it supports regardless of which ones WalletManager is actually configured with, so the build fails without them installed even though only Pera is used (WalletManager is constructed with wallets: [WalletId.PERA] only). Adds WalletManagerProvider (TestNet-only, wraps the app), ConnectPeraButton (connect/disconnect, shortened address display), and PayWithWalletButton - the real second half of the x402 flow: builds an algosdk ASA-transfer transaction from the server's own PaymentRequirements (never a client-invented amount/recipient), signs it via the connected wallet's transactionSigner, submits to algod, waits for confirmation, and re-POSTs the open endpoint with a real X-PAYMENT header matching algorand-adapter.ts's decodePaymentHeader contract. Wired into PaymentMethodPanel/OpenPackClient, replacing the "wallet-connect UI doesn't exist yet" placeholder. Confirmed in-browser: the connect button renders and initiates a real Pera handshake with no console/bundler errors. Not verified: an actual connect-sign-submit-settle click-through, since no funded TestNet Pera wallet was available in this environment - same "implemented against the documented contract, unverified live" status as the rest of this repo's live payment paths. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extracts the carousel-select -> rip -> spin -> reveal -> bonus-flip demo into a shared InteractivePackDemo component and places it front-and-center on the homepage alongside a plain-language walkthrough of the commit-reveal fairness algorithm, so visitors can try the mechanic before connecting a wallet or paying. Same component now backs both the landing page and the existing /dev/rip-preview page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Replaces the ~20-card hand-authored mock CardTrader inventory with 110 real cards (90 Pokemon + 20 Yu-Gi-Oh) pulled live from api.pokemontcg.io and db.ygoprodeck.com, each carrying its real current market price (2026-08-02 snapshot, $0.08-$1,300). CardTrader itself stays mock mode. - Adds a real Higgsfield torn-pack render for 4 tiers (Spark, Scout, Obsidian, Mythic), shown briefly during the rip gesture's "tearing" phase before the reveal wheel spins. PackArt's new `torn` prop falls back to the closed-pack art for tiers without one. - Switches the rip gesture's clip-path (and the torn art itself) from a jagged zigzag tear to a single clean straight seam line, per feedback. - Confirms the bonus-flip coin animation never rolls its own odds (it only animates the server's real 4% result) - only the demo intentionally uses a higher rate, clearly commented as such. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Coin-flip UI now only renders when the bonus flip actually hits (4% server-determined chance) - it previously rendered on every spin and merely animated to a "miss" outcome the other 96% of the time. - The torn-pack art phase now has a guaranteed minimum visible duration (900ms real flow, 1100ms demo) instead of being purely fetch-latency- dependent, so it doesn't flash past unnoticed on a fast connection, and now also appears on the deferred-payment settle path which previously skipped straight to the reveal wheel without showing it at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- New /eligibility page + EligibilityForm collect DOB/country/18+ acknowledgment via the existing complete-eligibility endpoint (now CSRF-protected, matching its sibling mutating routes). OpenPackClient links here automatically on an "eligibility_required" rejection. - offer-service.ts's getRollingSpend() replaces the "passes zeros" stub with a real query summing settled payments over rolling 24h/7d/30d windows, feeding the existing (already-tested) limit-decision logic. - New supplier-purchase queue worker (src/server/suppliers/purchase- worker.ts + run-worker.ts, `npm run worker:supplier-purchases`): a real long-running process, not a request handler, that claims queued rows (race-safe via a conditional UPDATE), runs validate/add-to-cart/confirm/ purchase against the CardTrader adapter, and records the outcome. Documented, not silently solved: no cross-process lock for multiple workers on one supplier account, no substitution-search-on-unavailable (no configured price tolerance exists to drive it), and it fails clearly when a user has no shipping address on file (no UI for that yet). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New /shipping-addresses page + CRUD API (GET/POST /api/shipping-addresses, DELETE and set-default on /api/shipping-addresses/:id) so a user can add the address their pulled cards ship to - the exact gap the new supplier-purchase worker fails clearly on (no_shipping_address_on_file). Every free-text field is encrypted at rest via the existing encryptField/ decryptField helpers; first address added is auto-default; deleting the default promotes the next-oldest one. All mutating routes are CSRF-protected. Linked to from /collection. Also factors the country/US- state lists shared with EligibilityForm into src/shared/countries.ts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New /account page (linked from the header once signed in): read-only connected-wallets list (new GET /api/auth/wallet/list, scoped to the session's own userId), active sessions with a real "sign out everywhere" control wired to the existing sessions API, and links to the other account-scoped pages (openings, shipping addresses, eligibility, responsible purchasing). Linking an *additional* wallet to an already-authenticated account isn't wired up yet - documented as a follow-up rather than half-built. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Full redesign of all 10 unlocked tiers to one standardized template: a glowing P+X vault-arc emblem in a corner-bracket frame, tier name in a bottom pill badge, no "PackX402" wordmark on the pack face at all. Only material/color and a tier-specific background motif differ (lightning, sunburst, radar lines, art-deco fan, diamond facets, holographic rays, frost facets, cracked glass, cosmic nebula). Iterated through several rounds of real feedback - corner checkmarks removed, wordmark font/position issues resolved by dropping the wordmark entirely rather than continuing to fight image-gen text rendering. - Real video-driven rip animation for Spark: RipToOpenVideo.tsx maps the user's drag position directly to video.currentTime on an actual Higgsfield-generated tear-open video (kling3_0 image-to-video interpolation between a closed still and a straight-line-torn still), replacing the CSS clip-path illusion for this tier. Wired into both the real opening theater and the no-DB demo via a shared tier->asset map (rip-video-map.ts) so it's a per-tier rollout, not all-or-nothing - the other 9 tiers still use the existing CSS rip until they get the same video treatment. - Verified live: dragging scrubs the video frame-by-frame, releasing past the commit threshold plays it through, then the reveal wheel spins. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds Starter, Scout, Bronze, Silver, Gold, and Prism to the tiers with a real Higgsfield-generated tear-open video (kling3_0 interpolation between closed/torn stills), joining Spark. Wired via the existing RIP_VIDEO_BY_TIER map - no code changes needed beyond the new entries. Starter and Bronze needed a corrected still-generation prompt (the tear initially landed partway down the pack instead of as a thin strip near the top zigzag seam, per user-caught feedback) before their videos were generated. Platinum, Obsidian, and Mythic still need the same treatment - generation stopped when the Higgsfield workspace ran out of credits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TESTNET_CEILING and MAINNET_CEILING now cover just Spark and Starter - every other tier (Scout through Genesis) is locked server-side via isTierPurchasableOn()/offer-service.ts, not just hidden in the UI. This narrows the beta catalog to the two tiers that already have the real video-driven rip-open treatment, rather than shipping a mix of video-tier and CSS-clip-path-tier packs while Higgsfield credits are depleted. The landing-page/dev-preview demo now only features these two tiers as well, matching real availability. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ty fix - Fixture ladder (fixtures.ts) grows from 110 to 200 real cards: 180 Pokemon sampled from 799 real candidates across 9 real sets (previously just 3), biased toward the cheap end so Spark/Starter's rarity bands have genuine in-band matches instead of leaning on fallback selection. - pickFixtureForBand (rarity-bands.ts) now takes a hard absoluteMaxUsdcBaseUnits cap, enforced before both the in-band search and the closest-match fallback. Previously the fallback had no ceiling of its own - a cheap tier with no in-band candidate could theoretically land an arbitrarily expensive fixture. db:seed now passes each tier's own procurementPriceCapUsdcBaseUnits as that cap. - Verified by simulating the actual pool selection against the live fixture ladder: Spark's ($0.50) max obtainable card is $5.47, Starter's ($1) is $10.90 - both comfortably under their $10/$20 caps, with every rarity band landing a real in-band fixture rather than a fallback pick. - Two new regression tests in rarity-bands.test.ts lock in the cap behavior (105/105 tests passing). Pool-version label bumped to 2026-08-02.3. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Initial beta scaffold for PackX402 — a provably fair, supplier-backed trading-card pack
platform powered by Algorand x402. This PR lays the full data model and the core
safety-critical business logic (fairness selection, payment settlement, purchase limits,
loyalty, eligibility, wallet-signature auth), plus a handful of real UI pages, and is
honest in
PROJECT_STATUS.mdabout what's tested vs. scaffolded vs. not started.No Docker/PostgreSQL and no live third-party credentials (CardTrader, GoPlausible) were
available in the build environment. Everything verifiable without those — strict
TypeScript against the full Drizzle schema,
next build, and 77 Vitest tests — passes.See
PROJECT_STATUS.mdfor the exact ✅/🟡/⬜ breakdown; do not trust marketing languageelsewhere in the repo over that file.
What's implemented and tested
src/server/fairness/engine.ts) with a fixed,hand-computed test vector published in
docs/FAIRNESS_PROTOCOL.mdmonthly limits, immediate-decrease/delayed-increase rule)
SupplierAdapter— idempotent purchases, cart-safety abort onunexpected non-empty cart
(tweetnacl), and EVM (viem) — each verified against an actual generated keypair signing
and verifying a real message, with negative tests
/api/fairness/verifyendpoint), odds library + JSON downloadhelper
npm run buildsucceeds;tsc --noEmitis cleanWallet integrations
Signature verification logic is implemented and tested for all three chains. The actual
connect UI (
@txnlab/use-wallet-reactfor Algorand,@phantom/react-sdkfor Solana/EVM)is not built yet.
x402 integration status
/api/x402/algorand/v1/packs/openreturns HTTP 402 withPaymentRequirementsand, givena valid
X-PAYMENTheader, verifies/settles and runs the fairness pipeline end to end(
src/server/packs/offer-service.ts). Solana/EVM adapters exist with the same shape.Mock mode is the default and only tested path. Live mode does direct chain
verification (algod / web3.js / viem) rather than calling the GoPlausible facilitator's
HTTP API, since no facilitator credentials were available to verify that contract — see
docs/DECISIONS.md.Supplier-integration status
CardTrader
SupplierAdapterimplemented for both mock (tested) and live (unverified, noAPI token available) modes, per the documented v2 API shape with
via_cardtrader_zero=false. The serialized one-purchase-at-a-time worker described inspec section 39 is not implemented — offers only enqueue a
supplier_purchasesrowtoday; nothing consumes the queue yet. Top follow-up item.
Social and affiliate features
Data model only. No application code, routes, or UI. See
docs/SOCIAL_MODERATION.mdanddocs/AFFILIATE_PROGRAM.md.Weekly free-pack and loyalty systems
Pure calculation logic implemented and tested (loyalty level + beta cap, ISO week key,
free-pack-grant DB uniqueness). No claim API route or recalculation job exists yet. See
docs/LOYALTY_AND_FREE_PACKS.md.Security controls
No custodial wallets/private-key storage anywhere in the schema. Integer USDC base units
everywhere (no float money math).
Math.randomnever used for card selection. High-valuetiers and MainNet are gated server-side by env flags checked at both boot
(
src/server/env.ts) and per-request (createPackOffer). Session tokens are opaque andstored only as a sha256 hash. See
SECURITY.mdanddocs/THREAT_MODEL.md.Tests performed
npm run test)tsc --noEmit, cleannpm run build(Next.js 16 + Turbopack), cleannpm run lint(ESLint), cleannpx prettier --check ., cleanthird-party credential (none were available in the build environment) — flagged
explicitly everywhere it matters rather than claimed as done. Playwright e2e tests are
not written yet.
Known limitations
See
PROJECT_STATUS.mdfor the full list. Headline items: no/api/auth/*routehandlers or auth UI yet (the underlying logic is tested), no opening-theater/purchase-
confirmation UI, no supplier-purchase worker, no admin dashboard, no social/affiliate/
referral application code, migrations generated but not yet applied to a live database in
this environment.
Credentials or external configuration still required
CARDTRADER_MODE=live)X402_FACILITATOR_MODE=live) — or acceptthe direct-chain-verification approach already implemented as the long-term design
SESSION_SECRET/FIELD_ENCRYPTION_KEYgenerated fresh per environmentLegal-review items
See
docs/LEGAL_REVIEW_REQUIRED.mdin full. Headline items: consumer-protection reviewbefore enabling high-value packs or MainNet, IP/trademark counsel sign-off on card-name
usage and pack artwork, responsible-purchasing/gambling-adjacent regulation review,
data-protection review of
docs/PRIVACY_DATA_MAP.md(including the currently-unencryptedeligibility_records.dateOfBirthfield flagged there).Exact deployment steps
See
docs/DEPLOYMENT.md. Local:npm install && docker compose up -d && npm run db:migrate && npm run db:seed && npm run dev.Project rename note
Renamed from "Pack402" to "PackX402" per the repository owner's direction partway through
this build — reflected in the package name, branding, docs, and this repo's name/URL.
🤖 Generated with Claude Code
Session update (2026-08-01)
Since the original PR description was written, this branch gained:
/api/auth/*route handlers wired up on top of the already-testedauth-service.ts: signup, login (request/verify), logout, wallet nonce/verify,session listing, session revoke-all, verify-email. Auth UI (forms) still not built.
src/components/pack-art/):PackArt,PackArtSkeleton,CardBack,CardOverlaySlot,OpeningStage,FourPackStage,ResultEffect,PackCarousel— wired into the landing, marketplace, and pack-detailpages.
PackCarouselis an infinite circular strip with drag/swipe + flick-velocityscrolling, no dot pagination.
public/packs/*.png(Higgsfield-generated, dark-glass pack + X-crest motif, per-tier metallic accent).
Remaining 4 locked tiers (Crown/Vault/Grail/Genesis) are still CSS placeholders.
A follow-up regeneration pass is planned to push the art style further toward a
toon/cel-shaded look — the current set leans photoreal-luxury; an earlier
toon-style exploration was well-received and will become the new template.
src/server/config/pack-tiers.ts):everything above $250 (Crown/Vault/Grail/Genesis) is locked pending supplier-
fulfillment bankroll; Prism–Mythic availability extended accordingly. Docs updated to
match (README,
docs/LEGAL_REVIEW_REQUIRED.md,.env.example).src/proxy.ts's CSP was blockingeval(), which brokeNext.js HMR entirely — now only relaxed in development, unchanged (strict) in
production.
src/server/db/seed.tsrewritten to generically build a pool (commons + one chasecard) for all 14 tiers from an expanded fixture ladder in
src/server/suppliers/cardtrader/fixtures.ts(20 fixtures, ~$0.30–$9,500) — every tiernow seeds correctly, not just Spark/Gold as before.
Verified this session:
npm run typecheck,npm run lint,npm run test(77/77passing) all clean.
npm run buildwas not re-run against this exact commit in thissession (was previously verified clean earlier in the branch's history); recommend a
human re-run before merge given the size of the diff.
Still not started: opening-theater/purchase-confirmation UI, supplier-purchase worker,
admin dashboard, social/affiliate application code, live-DB verification (no
Docker/Postgres in this build environment). See
PROJECT_STATUS.mdfor the currentfull ✅/🟡/⬜ breakdown.