Orra is a web app that pairs live Pyth oracle data with an on-chain tarot draw. The dashboard shows prices and market context. The reading flow asks for intent, commits a snapshot of the oracle on-chain, and draws a Major Arcana card with verifiable randomness. This repository defaults to Base Sepolia (84532); point env at Base mainnet if you deploy there instead.
- Node.js 18.18 or newer (recommended: 20.x LTS for Vercel)
- npm (or another client that respects
package-lock.json) - Foundry (
forge,cast) for the Solidity project undercontracts/
Dashboard (/) streams Pyth data for searchable assets. You get current price, confidence-style signals, sparklines, and session-aware warnings where relevant.
Reading (/reading) walks through questions and a wallet step on Base Sepolia by default (NEXT_PUBLIC_BASE_RPC_URL). Use a Base mainnet RPC to target mainnet. When you confirm a draw:
- The app freezes the current Pyth tick and hashes it (
feedIdplus packed price, confidence, timestamps, and related fields). That hash is the oracle snapshot commitment. - You pay the Pyth Entropy v2 fee. The
Orracontract requests randomness and stores your address,feedId, and the snapshot hash until the callback runs. - Entropy returns a
bytes32. The contract maps it to one of 22 Major Arcana indices and emits events. Anyone can correlate the draw with the committed oracle state off-chain. - Optional text interpretation runs server-side. The API sends oracle context and reading metadata to a configured LLM provider. No provider keys means interpretation stays disabled; the draw still works on-chain.
Randomness comes from Pyth Entropy, not from the UI. The snapshot ties the narrative to a specific oracle update for the chosen feed.
contracts/src/Orra.sol implements IEntropyConsumer. It calls requestV2, tracks pending readings by sequence number, and in entropyCallback emits CardDrawn with the card index, feed id, snapshot hash, and raw random value. Deploy with Foundry; see contracts/deploy.sh and contracts/env.deploy.example.
- App: Next.js 15, React, TypeScript, Tailwind, wagmi, RainbowKit, viem, ethers
- Data: Pyth (HTTP/stream routes under
app/api/pyth-*;PYTH_PRO_TOKENunlocks Pro features where used) - Chain: Base Sepolia (default) or Base mainnet, driven by
NEXT_PUBLIC_BASE_RPC_URL - Contracts: Solidity 0.8.24, Forge,
@pythnetwork/entropy-sdk-solidity
| Path | Purpose |
|---|---|
app/ |
Next.js routes, API handlers (app/api/), global styles |
components/ |
React UI (dashboard, reading flow, shared chrome) |
hooks/ |
Client hooks (Pyth stream, contract, wallet helpers) |
lib/ |
Shared TS (oracle hash, cards, prompts, contract ABI helpers) |
contracts/ |
Foundry project: Orra.sol, tests, deploy script; see contracts/README.md for forge-std |
public/ |
Static assets |
npm install
cp .env.example .env.local # then edit values
npm run devUse the tables below to fill .env.local. For production, set the same variables on your host (Vercel, Docker, etc.). Next.js reads .env.local automatically in development.
Open http://localhost:3000.
| Variable | Role |
|---|---|
NEXT_PUBLIC_ORRA_CONTRACT_ADDRESS |
Deployed Orra contract (required for draws) |
NEXT_PUBLIC_BASE_RPC_URL |
RPC; default in code is https://sepolia.base.org |
NEXT_PUBLIC_ENTROPY_ADDRESS |
Pyth Entropy contract (IEntropy v2); must match orra.entropy() on-chain |
NEXT_PUBLIC_ORRA_DEPLOY_BLOCK |
Optional; narrows log scans for reading history |
Pyth Entropy uses two different addresses. The app only exposes the Entropy contract above. Deploy also needs ENTROPY_PROVIDER_ADDRESS in contracts/.env (the default provider from the Pyth chainlist). That provider address is not the same as the Entropy contract address. It is stored in Orra at deploy time; this repo’s Orra.sol does not read it after the constructor, but you should still set it to the official value for your chain. NEXT_PUBLIC_ENTROPY_ADDRESS and ENTROPY_ADDRESS should match. ENTROPY_PROVIDER_ADDRESS has no NEXT_PUBLIC_ counterpart.
| Variable | Role |
|---|---|
PYTH_PRO_TOKEN |
Pyth Pro access for ticker/stream routes that need it |
GITHUB_MODELS_TOKEN |
Optional; GitHub Models for /api/interpret |
BLUESMINDS_API_KEY |
Optional; Bluesminds for interpretation |
OPENROUTER_API_KEY |
Optional; OpenRouter for interpretation |
ORRA_ALLOWED_ORIGINS |
Optional comma-separated list of origins allowed to POST /api/interpret in production (e.g. https://yourdomain.com). If unset on Vercel, VERCEL_URL / VERCEL_BRANCH_URL are used automatically. |
ORRA_INTERPRET_TRUST_ANY_ORIGIN |
Set to 1 only as an escape hatch to disable Origin checks (not recommended in production). |
At least one interpretation key is required for AI text. Optional overrides (model IDs, Bluesminds base URL, OpenRouter referer/title) live in app/api/interpret/route.ts. Production /api/interpret responses omit raw provider error chains unless NODE_ENV=development; the server still logs failures.
Template (placeholders only): .env.example.
- Import the Git repository and use the default Next.js framework settings (
npm run build/ output.next). - In Project → Settings → Environment Variables, add every variable from
.env.examplefor Production (and Preview if previews should run the full stack):- All
NEXT_PUBLIC_*values (contract address, RPC URL, entropy address, optional deploy block). - Server-only secrets — mark Sensitive in the Vercel UI:
PYTH_PRO_TOKEN,GITHUB_MODELS_TOKEN,BLUESMINDS_API_KEY,OPENROUTER_API_KEY, plus any optional Bluesminds / OpenRouter overrides. - Never prefix API keys with
NEXT_PUBLIC_— that would expose them in the browser bundle.
- All
- Custom domain: add
https://yourdomain.comtoORRA_ALLOWED_ORIGINSif you disable or replace Vercel’s default host checks. - After deploy: in DevTools → Sources, search the client JS for
OPENROUTER_API_KEY,GITHUB_MODELS_TOKEN,BLUESMINDS_API_KEY, orPYTH_PRO_TOKEN. They must not appear as string literals (onlyNEXT_PUBLIC_*may). - Optional: Vercel Deployment Protection or Firewall rules on preview URLs if you do not want anonymous traffic hitting Pyth/interpret proxies.
cd contracts
forge install # first time, if needed — see contracts/README.md if lib/forge-std is missing
forge build
forge testDeploy: configure contracts/.env from env.deploy.example (PRIVATE_KEY, BASE_RPC_URL, ENTROPY_ADDRESS, ENTROPY_PROVIDER_ADDRESS). Run ./deploy.sh. Set NEXT_PUBLIC_ORRA_CONTRACT_ADDRESS in the app to the deployed address.
| Command | Description |
|---|---|
npm run dev |
Next.js dev server |
npm run build / npm start |
Production build and serve |
npm run lint |
ESLint |
npm test |
Vitest — pure lib/ helpers (*.test.ts) |
forge test (in contracts/) |
Solidity tests |
See package.json for the canonical Git remote and issue URL.
Issues and pull requests are welcome. For large behavior or contract changes, open an issue first so the design stays aligned.
Do not commit private keys or API tokens. Use GitHub Security advisories for sensitive reports if that feature is enabled on the repository.
Sound on the reading route (/reading) uses clips from Freesound. Full credits, licenses (CC BY 4.0 ambient, CC BY-NC 4.0 for the reveal sting — non-commercial unless you license separately), and suggested attribution text are in public/audio/reading/ATTRIBUTION.md.
Licensed under the Apache License, Version 2.0. See NOTICE for copyright. Third-party code (e.g. contracts/lib/forge-std) remains under its respective licenses.