Oracle3
Oracle3: 예측시장 거래 엔진·MCP 서버
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A premium, dark-first web app for tracking the market value and true investment performance of a Pokémon collection — singles, sealed product and graded cards.
Built with Next.js (App Router) · TypeScript · Tailwind CSS v4 · shadcn/ui-style Radix primitives · Supabase · TCG API via the official tcgapi SDK · Recharts · Lucide.
The app runs immediately in demo mode with zero configuration. Demo data is sample data, clearly labeled everywhere, and is never mixed into a real collection.
npm install
npm run dev # http://localhost:3000 — demo mode
npm test # investment-calculation unit tests
npm run typecheck && npm run lint
| Mode | When | Storage |
|---|---|---|
| Demo | Default for signed-out visitors | Sample dataset regenerated relative to today + sandbox edits in localStorage (holo:demo:v1). Product ids use the sample: namespace. |
| My collection — browser | "Use my collection" without Supabase / sign-in | localStorage (holo:local:v1). Real TCG API products. |
| My collection — synced | Supabase configured and signed in | Supabase Postgres with row-level security. |
Switch modes from the sidebar (desktop) or Settings.
Copy .env.example to .env.local.
| Variable | Scope | Purpose |
|---|---|---|
TCGAPI_KEY | server | TCG API key. Enables live search, prices, history and card scanning. Never sent to the browser. |
TCGAPI_GAMES | server | Optional comma-separated game slugs to search. By default every Pokémon catalog (English, Japanese, …) and Disney Lorcana found via GET /games is searchable; the search panel picks Pokémon, Lorcana or both. |
TCGAPI_GAME | server | Game slug for top movers, default pokemon. |
NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY | public | Supabase project. Data is protected by RLS. |
SUPABASE_SERVICE_ROLE_KEY | server | Writes to the shared catalog/price tables and runs the daily sync. |
CRON_SECRET | server | Authenticates the Vercel Cron call to /api/cron/sync-prices. |
APNS_KEY_ID, APNS_TEAM_ID, APNS_BUNDLE_ID, APNS_PRIVATE_KEY | server | Push notifications to the iPhone app (price targets hit). Optional; see ios/README.md. |
supabase/migrations/ in order (supabase db push, or paste each into the SQL editor). 20261025000000_share_links.sql enables read-only share links (Settings → Share; public pages at /s/<token>).https://<your-domain>/auth/callback, https://*-<your-team>.vercel.app/auth/callback (previews) and http://localhost:3000/auth/callback as redirect URLs. Sign-in uses email magic links that work in any browser (implicit-flow links, finished by /auth/finish); sessions last until sign-out.
{{ .Token }} so the email also carries a code. The login page accepts it, which works even when the link opens in a different browser (e.g. a mail app's in-app browser).src/lib/supabase/database.types.ts with npx supabase gen types typescript if you change the schema.Import the repo, add the env vars, deploy. vercel.json schedules the daily price sync (17 9 * * * UTC); Vercel sends Authorization: Bearer $CRON_SECRET automatically.
All calculations live in src/lib/finance as pure, tested functions.
null, shown as "—", excluded from value and gains, and called out in the UI. It is never treated as $0.Audit trail. Transactions are append-only. A correction is a new row pointing at the original (correctsId) with replacement values; quantity 0 voids. Supabase RLS has no update/delete policies on transactions.
Prices over time. PriceResolver uses only real observations: the current quote for today, stored daily snapshots (carried forward at most 10 days, then treated as unknown), yesterday's price recovered from the source's own 24h change, or a manual valuation for graded cards. It never interpolates or back-fills history.
Unit tests: src/lib/finance/__tests__/finance.test.ts covers the "$1,000 box at market = $0 gain" case, FIFO partial sales, fee loading, corrections/voids, missing prices, TWR with large mid-period purchases, sales, and newly-priced items.
src/lib/pricing/types.ts — PricingProvider interface (search, product, prices per printing, history, top movers) and normalized NormalizedPrice (market, low, 24h/7d/30d %, source update time, fetch time, source, live | stale | unavailable).src/lib/pricing/tcgapi.ts — TCG API implementation using the official SDK (GET /v1/search, /v1/cards/:id, /v1/cards/:id/prices, /v1/cards/:id/history, /v1/prices/top-movers, Pro-only /v1/bulk/prices with automatic fallback). Sealed vs. singles uses type=Sealed Products / Cards.
rate_limit budget from responses (shown in Settings) and backs off briefly after a 429, honoring Retry-After up to 10 minutes.${source}:${externalId} with the exact printing; nothing is matched by name. Another source plugs in by implementing PricingProvider and registering it in src/lib/pricing/index.ts — the collection tables don't change./api/scan)Photos go to TCG API's POST /v1/identify (one prepaid scan each, separate from the daily request limit). The route keeps the key server-side, requires sign-in when Supabase is configured, and rate-limits each client so a burst of rapid scans works but nobody can drain the balance. GET /api/scan returns the scans left (free). Scanning is in Add to Collection ("Scan cards", for adding a stack at once), the Deal page and the Trade checker.
/api/tcg/products/:id/ebay)GET /v1/cards/:id/prices?include=ebay_sales (TCG API Pro plan and up, 2 request credits a call) gives the last 30 days of eBay sales: the median, count and last sale for ungraded copies and each grade, plus the listings. It's fetched only when needed and cached for 12 hours in the server, at the CDN and on the device. EBAY_SOURCE in src/lib/ebay-sample.ts is "tcgapi"; demo products always get sample sales.
Slabs are valued from the same data (GRADED_SOURCE = "ebay" in src/lib/graded.ts): each slab gets the 30-day median for its exact grade (else the last sale), with history following the raw card's trend scaled to that price. A grade with no recent sales stays unpriced so you can set your own value. Scans ask for include=ebay, so a scanned slab is priced at its label's grade. The full graded price table and "What's it worth?" fall back to the typical grade curve, always labeled as an estimate, until a price guide like PriceCharting is connected.
Worth grading (/grading) asks for eBay summaries for every real card on the page in one call (GET /api/tcg/ebay?ids=…, backed by GET /v1/bulk/prices?include=ebay: 1 credit plus 1 per card with sales, up to 500 cards), so its PSA 10 and PSA 9 figures and the "sells steadily / hard to sell" labels come from real sales. A grade with no sales falls back to the curve and is marked "≈ estimated".
"What's it worth?" prices played conditions from TCGplayer's per-condition listings (/api/tcg/products/:id/conditions, GET /v1/cards/:id/prices/conditions): the listing median when there are 3+ listings, else the lowest one. Without those it uses eBay sales in that condition, then a labeled share of Near Mint. Both new routes are cached for 12 hours like the rest.
/api/cron/sync-prices)product_prices and today's price_snapshots (unknown prices are never written).portfolio_snapshots from stored data.profiles, user_preferences, products (catalog cache), product_prices (latest), price_snapshots (daily history), holdings (product + printing + condition + grade), transactions (append-only: purchase / sale / adjustment / correction), purchase_lots (view over purchase transactions; remaining quantity derived by FIFO replay so it can't drift), portfolio_snapshots, watchlist_items. RLS restricts every user table to auth.uid(); the shared catalog is read-only to clients and written by the server with the service role.
ios/ holds a native SwiftUI app that uses the same Supabase account and collection, and calls this app's API with the user's login token (getRequestUser accepts either the website's cookie or Authorization: Bearer). See ios/README.md for building in Xcode and shipping to TestFlight. .github/workflows/ios.yml compiles and tests it on a GitHub-hosted Mac.
src/app routes: overview, collection, product/[id], analytics, watchlist, activity, settings, login, api/*
src/components/ui shadcn-style primitives (Button, Dialog, Select, Segmented, …)
src/components/flows add-to-collection search + purchase form, sale / adjust / correction dialogs
src/components/portfolio, product, charts, shell
src/lib/finance ledger (FIFO), price resolver, timeline & analytics + tests
src/lib/pricing provider interface, TCG API provider, cache
src/lib/store repositories (demo / local / Supabase) and the React store
src/lib/supabase clients, typed schema, mappers, loader
supabase/migrations schema + RLS
tcgapi SDK's typed endpoints. TCG API's price_change_* fields are treated as percentages (as in the SDK's top-movers example); verify against your account.images.pokemontcg.io. Live mode uses TCG API's image_url.