Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ DATABASE_URL=postgresql://user:password@localhost:5432/megastera
# MINERAL_ECONOMY_CUTOVER_AT=
# Keep disabled until the migration, SIWE env, and production smoke test are complete.
# MINERAL_UPGRADES_ENABLED=false
# First finalized JackpotSettled block ingested by the leaderboard worker. Empty disables Galaxy Pulse.
# GALAXY_PULSE_START_BLOCK=
# Secret used by the protected Vercel Cron leaderboard worker endpoint.
# CRON_SECRET=
# Required only before enabling upgrades: exact canonical HTTPS origin, without a trailing slash.
# SIWE_ORIGIN=
# Required only before enabling upgrades: server-only random secret containing at least 32 bytes.
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ packages/planet-generator/generated-*/

# Claude Code workspace state (settings, launch configs, hooks)
.claude/
.vercel
.env*
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ The automated reward contract is a future upgrade; it is not presented as part o

Seasonal rewards can expand beyond monetary payouts over time and may include gameplay assets, planets or other items introduced by future mechanics.

Implementation note: the active backend exposes live standings with a current UTC-day period label and has a separate daily finalization worker for archived rows. Reward settlement is not an automated smart-contract or HTTP path in this repository; the Season 1 countdown and prize copy are presented by the client.

## Why Megapot is core

Megapot is not a secondary widget, a link-out, or an unrelated protocol attached to the game.
Expand Down Expand Up @@ -234,6 +236,7 @@ The browser handles interaction, but it is not the authority for planet creation
- Planet generation is deterministic and idempotent.
- Conflicting persisted provenance fails closed.
- Mining is derived from persisted planet data rather than mutable browser state.
- Same-type collection bonuses are derived from persisted Planet generation times rather than a browser-owned ledger.
- After the mineral-economy cutover, wallet balances and current leaderboard scores are derived from persisted integer-micro account and upgrade history; V1 remains the pre-cutover fallback.
- Generated GIF bytes are stored with an immutable content hash.
- Server credentials and database secrets never enter the Vite client environment.
Expand All @@ -251,7 +254,7 @@ Future seasons can expand that foundation with colonies, stars, ships, trading,
## Repository

```text
megastera/
MegasteraGame/
├── src/ # React application and game UI
├── api/ # Receipt verification, planet API, mining, leaderboard
├── packages/planet-generator # Shared deterministic planet generator
Expand All @@ -265,10 +268,10 @@ megastera/

### Stack

**Frontend:** React 19, TypeScript, Vite, wagmi, viem, RainbowKit
**Protocol:** Megapot on Base
**Backend:** Hono, PostgreSQL, Prisma
**Rendering:** shared deterministic planet generator
**Frontend:** React 19, TypeScript, Vite, wagmi, viem, RainbowKit<br>
**Protocol:** Megapot on Base<br>
**Backend:** Hono, PostgreSQL, Prisma<br>
**Rendering:** shared deterministic planet generator<br>
**Quality:** Vitest, TypeScript, Biome, GitHub Actions

## Run locally
Expand All @@ -290,13 +293,13 @@ Copy the required values from [`.env.example`](.env.example) into your local env
Every pull request and push to `main` runs the repository quality gate:

```bash
pnpm db:generate
pnpm db:validate
pnpm lint
pnpm typecheck
pnpm test
pnpm --filter @megaplanets/planet-generator golden
pnpm build
pnpm db:generate
pnpm db:validate
```

## License
Expand Down
2 changes: 1 addition & 1 deletion api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ The HTTP server defaults to `127.0.0.1:8787`; set `MEGAPLANETS_API_HOST` and `ME
- Receipt verification checks Base mainnet, canonical jackpot/source, event fields, confirmation depth, block hash, and optional wallet recipient.
- Generation is idempotent on `originTxHash:logIndex`; conflicting persisted provenance is rejected.
- GIF bytes are stored in PostgreSQL and served with an immutable content hash.
- Leaderboard reads are derived from ready persisted Planet records, collection milestones, and—after cutover—mineral account and upgrade history.
- Leaderboard reads are derived from ready persisted Planet records and same-type collection milestones; after cutover, mineral account and upgrade history also contribute to spendable scores.
5 changes: 4 additions & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The source tag is always `MEGASTERA`. It is a Megapot attribution value and does

## Backend API

The Vercel entrypoint `api/index.ts` delegates to `server/api/index.ts`, which mounts only:
The Vercel entrypoint `api/index.ts` delegates to `server/api/index.ts`, which mounts:

- backend Planet generation, collection, GIF, and wallet mining routes;
- the server-disabled Planet upgrade endpoint;
Expand All @@ -52,6 +52,8 @@ The Vercel entrypoint `api/index.ts` delegates to `server/api/index.ts`, which m

No active module signs vouchers, pins media, reads a Planet contract, projects Planet events, scans all tickets continuously, or writes transfer/accrual ledgers. The upgrade endpoint remains server-disabled because it does not yet verify a request-wallet signature.

`server/api/leaderboardWorker.ts` is the scheduler entrypoint. The protected `/api/internal/leaderboard-worker` Vercel endpoint runs it from the daily production Cron; `pnpm api:leaderboard-worker` remains available for local/manual runs. It finalizes completed UTC-day periods into `LeaderboardPeriod` and `LeaderboardEntry`.

## Frontend boundaries

`src/pages/Play.tsx` owns checkout and, after canonical receipt recovery in the purchase hooks, requests backend generation with bounded finality retries. The active UI has two stages: Buy tickets and Explore planets. On success it shows the generated cards full screen with `Explore again` and `My planets` actions.
Expand All @@ -71,6 +73,7 @@ The `BackendPlanet` model is linked one-to-one to `TicketPurchase` and stores:
- ready/failed status plus bounded generation error text.

This worktree additionally stores `upgradeLevel` and `upgradeBonusBps` on `BackendPlanet`, opening/current balances in `MineralAccount`, the authoritative cutover in `MineralEconomyCutover`, and idempotent `(planetId, targetLevel)` charges in `PlanetUpgradePurchase`. V2 calculations use integer micros and keep the V1 opening-balance calculation as the cutover anchor.
Same-type collection bonuses are derived at read time from Planet generation timestamps; they are not stored as accrual or ledger rows. The active leaderboard and wallet/Planet mining reads therefore remain reproducible from immutable Planet data.

Legacy Prisma tables and migrations are retained only for database compatibility. No active code writes the legacy Planet, voucher, artifact, projector, or accrual paths.

Expand Down
14 changes: 10 additions & 4 deletions docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ MEGAPLANETS_API_HOST # optional standalone server host
MEGAPLANETS_API_PORT # optional standalone server port
MINERAL_ECONOMY_CUTOVER_AT # optional exact UTC-midnight ISO timestamp; empty keeps V1
MINERAL_UPGRADES_ENABLED=false # reserved; public upgrades remain server-disabled
GALAXY_PULSE_START_BLOCK # optional first JackpotSettled block; empty disables Galaxy Pulse
CRON_SECRET # required by the protected Vercel leaderboard worker Cron
```

Frontend values use `VITE_*` only for public configuration, including `VITE_CHAIN`, wallet/RPC settings, `VITE_API_BASE_URL`, and optional `VITE_BACKEND_API_BASE_URL`. The Megapot Data API defaults to `https://api.megapot.io/v1` for Base mainnet. If `VITE_API_BASE_URL=/api/megapot` is used, the checked-in proxy targets that same mainnet host and may use server-only `MEGAPOT_API_KEY`. Never put `DATABASE_URL`, server tokens, or private keys in a Vite variable.
Expand All @@ -39,12 +41,14 @@ Check:
- `GET /api/leaderboard/current` for live standings; and
- `/api/megapot/*` for the same-origin Megapot Data API proxy.

There is intentionally no readiness route that probes a Planet contract and no indexer process to start.
There is intentionally no readiness route that probes a Planet contract. In production, Vercel Cron calls `/api/internal/leaderboard-worker` daily at 00:05 UTC with `CRON_SECRET`; the endpoint runs the same idempotent leaderboard worker used by `pnpm api:leaderboard-worker`.

## Purchase and generation troubleshooting

The frontend must send the execution transaction hash and exact `TicketPurchased` log index. The API then checks receipt status, Base mainnet, canonical jackpot, `MEGASTERA` source tag, ticket fields, confirmation depth, canonical block hash, and optional recipient. The Play screen retries the same reference while the receipt reaches finality; the backend persists the proof before rendering the GIF, so a generation/storage failure leaves a retryable pending collection row.

Play checkout checks the current USDC allowance against the exact purchase total before showing the purchase action. If the allowance is insufficient, the approval transaction grants a reusable allowance to the route-specific Megapot spender; bulk-order simulation runs only after that approval gate resolves. The UI distinguishes insufficient USDC balance, a rejected wallet transaction, and wallet/network failures.

If generation returns `422`, inspect server logs for the request and RPC stage; do not retry with a different log index unless the receipt actually contains another canonical ticket event. Repeating the same valid request is safe: the key is `originTxHash:logIndex`, and an existing ready row is returned. If the receipt is not yet final, My Planets keeps the browser-confirmed receipt as a pending card and the catch-up pass retries it later.

If the database already contains a conflicting ticket or Planet row, preserve it and investigate the immutable provenance mismatch. Do not delete production data as a first response.
Expand All @@ -59,11 +63,13 @@ With no `MINERAL_ECONOMY_CUTOVER_AT`, mining and leaderboard reads follow the V1
baseMineralsPerDay × elapsed milliseconds × 1_000_000 / 86_400_000
```

The start time is the backend generation timestamp. Same-type collection milestones apply at 3 (+5%), 5 (+7.5%), and 10 (+10%) matching Planets. The browser never writes accrual or ledger rows. Before cutover, public leaderboard routes calculate current rows from ready backend Planet `generatedAt` and `baseMineralsPerDay` values and apply the V1 collection calculation. Live leaderboard reads are uncached.
The start time is the backend generation timestamp. The base rate is `baseMineralsPerDay × 1_000_000` micros/day. Same-type collection milestones apply at 3 (+5%), 5 (+7.5%), and 10 (+10%) matching Planets. Earned micros are calculated over each rate interval with integer arithmetic; the browser only interpolates the returned snapshot for display and never writes accrual or ledger rows. Before cutover, public leaderboard routes calculate current rows from ready backend Planet `generatedAt` and `baseMineralsPerDay` values and apply the V1 collection calculation. Live leaderboard reads are uncached.

When the prepared cutover is active, the wallet mining route settles `MineralAccount` balance in integer micros from the cutover forward. New Planet generation settles the owner's account before inserting the new Planet. Level 1/2/3 persistence primitives retain +10%/+25%/+50% bonuses and idempotent `(planetId, targetLevel)` charges, but `POST /api/planets/:planetId/upgrade` remains server-disabled until owner-bound authorization exists. A configured cutover that was not persisted before PostgreSQL reaches it fails closed.

The cutover migration must be applied before setting the environment variable. Before cutover, the current leaderboard remains V1; after cutover, it ranks spendable mineral scores reconstructed as opening balance plus historical production minus upgrade costs, and reports the effective per-day rate after collection and upgrade events.
The cutover migration must be applied before setting the environment variable. Before cutover, the current leaderboard remains V1; after cutover, it ranks spendable mineral scores reconstructed as opening balance plus historical production minus upgrade costs, and reports the effective per-day rate after collection, upgrade, and Galaxy Pulse events.

Galaxy Pulse starts strictly at `GALAXY_PULSE_START_BLOCK`; there is no historical backfill. The leaderboard worker verifies finalized `JackpotSettled` receipts, derives a deterministic seed from `drawingId` and `winningNumbers`, and advances the sole indexer cursor. Wallet mining reads only the latest authoritative database snapshot. Balance mutations and leaderboard finalization fail closed until the database round matches the current settled drawing reported by the Megapot Data API.

### Cutover preparation

Expand Down Expand Up @@ -92,7 +98,7 @@ pnpm minerals:backfill

It refuses to run when the cutover variable is absent or when PostgreSQL `clock_timestamp()` is before the configured cutover. Account creation is create-only and idempotent: existing `MineralAccount` balances are never reset; concurrent duplicates are skipped. If the configured cutover conflicts with the persisted singleton, the command fails closed. Keep the transaction short and rerun after correcting configuration or clock/migration state.

`pnpm api:leaderboard-worker` is the separate finalization command for completed UTC days; it writes archived `LeaderboardPeriod`/`LeaderboardEntry` rows when scheduled, but no HTTP history/finalization route is exposed.
`pnpm api:leaderboard-worker` first ingests finalized Galaxy Pulse rounds, then finalizes completed UTC days. It writes archived `LeaderboardPeriod`/`LeaderboardEntry` rows when scheduled, but no HTTP history/finalization or separate Galaxy Pulse route is exposed.

Ticket status uses `useJackpotState` for the live drawing countdown/phase and the Base mainnet Megapot Data API for wallet ticket/win history. The wallet ticket list follows opaque cursors to completion for My Planets and stops on API errors without showing a false empty collection. `Claim winnings` remains an on-chain `Jackpot.claimWinnings(uint256[])` call after simulation, capped at 50 IDs per batch; confirmed receipts invalidate the wallet ticket/win queries.

Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"scripts": {
"bootstrap": "pnpm install",
"dev": "vite",
"prebuild": "pnpm --filter @megaplanets/planet-generator build",
"prebuild": "node scripts/deploy-production-migrations.mjs && pnpm --filter @megaplanets/planet-generator build",
"predev": "pnpm --filter @megaplanets/planet-generator build",
"pretest": "pnpm --filter @megaplanets/planet-generator build",
"build": "prisma generate && tsc -b && vite build && node scripts/prepare-site-worker.mjs",
Expand All @@ -18,7 +18,6 @@
"minerals:prepare": "node --import tsx/esm server/api/mineralCutoverPrepare.ts",
"minerals:backfill": "node --import tsx/esm server/api/mineralBackfill.ts",
"minerals:backfill:dry-run": "pnpm minerals:backfill --dry-run",
"test:postgres": "pnpm --filter @megaplanets/planet-generator build && MINERAL_ECONOMY_REQUIRE_POSTGRES=1 vitest run server/api/mineralConcurrency.postgres.test.ts",
"preview": "vite preview",
"typecheck": "tsc -b --noEmit && tsc -p tsconfig.vercel.json --noEmit",
"typecheck:vercel": "tsc -p tsconfig.vercel.json --noEmit",
Expand Down
10 changes: 10 additions & 0 deletions prisma/migrations/20260822160000_galaxy_pulse_rounds/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE "galaxy_pulse_rounds" (
"drawingId" DECIMAL(78,0) NOT NULL,
"entropy" CHAR(66) NOT NULL,
"settlementTxHash" CHAR(66) NOT NULL,
"settledAt" TIMESTAMPTZ(3) NOT NULL,

CONSTRAINT "galaxy_pulse_rounds_pkey" PRIMARY KEY ("drawingId")
);

CREATE INDEX "galaxy_pulse_rounds_settledAt_idx" ON "galaxy_pulse_rounds"("settledAt");
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "galaxy_pulse_rounds" RENAME COLUMN "entropy" TO "seed";
10 changes: 10 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,16 @@ model IndexerCursor {
@@map("indexer_cursors")
}

model GalaxyPulseRound {
drawingId Decimal @id @db.Decimal(78, 0)
seed String @db.Char(66)
settlementTxHash String @db.Char(66)
settledAt DateTime @db.Timestamptz(3)

@@index([settledAt])
@@map("galaxy_pulse_rounds")
}

model DailySnapshotRecord {
id String @id @default(uuid()) @db.Uuid
blockNumber BigInt
Expand Down
Binary file added public/galaxy-pulse/desert.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/galaxy-pulse/gaia.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/galaxy-pulse/gas-giant.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/galaxy-pulse/nebula.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/galaxy-pulse/oceanic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/galaxy-pulse/rocky.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/galaxy-pulse/toxic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/galaxy-pulse/triplex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/galaxy-pulse/void.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/galaxy-pulse/volcanic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-001.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-002.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-003.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-004.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-005.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-006.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-007.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-008.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-009.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-010.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-011.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-012.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-013.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-014.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-015.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/landing/gifs-512/planet-016.gif
Binary file added public/landing/gifs-512/planet-017.gif
Binary file added public/landing/gifs-512/planet-018.gif
Binary file added public/landing/gifs-512/planet-019.gif
Binary file added public/landing/gifs-512/planet-020.gif
Binary file added public/landing/gifs-512/planet-021.gif
Binary file added public/landing/gifs-512/planet-022.gif
Binary file added public/landing/gifs-512/planet-023.gif
Binary file added public/landing/gifs-512/planet-024.gif
Binary file added public/landing/gifs-512/planet-025.gif
Binary file added public/landing/gifs-512/planet-026.gif
Binary file added public/landing/gifs-512/planet-027.gif
Binary file added public/landing/gifs-512/planet-028.gif
Binary file added public/landing/gifs-512/planet-029.gif
Binary file added public/landing/gifs-512/planet-030.gif
Binary file added public/landing/gifs-512/planet-031.gif
Binary file added public/landing/gifs-512/planet-032.gif
Binary file added public/landing/gifs-512/planet-033.gif
Binary file added public/landing/gifs-512/planet-034.gif
Binary file added public/landing/gifs-512/planet-035.gif
Binary file added public/landing/gifs-512/planet-036.gif
Binary file added public/landing/gifs-512/planet-037.gif
Binary file added public/landing/gifs-512/planet-038.gif
Binary file added public/landing/gifs-512/planet-039.gif
Binary file added public/landing/gifs-512/planet-040.gif
Binary file added public/landing/gifs-512/planet-041.gif
Binary file added public/landing/gifs-512/planet-042.gif
Binary file added public/landing/gifs-512/planet-043.gif
Binary file added public/landing/gifs-512/planet-044.gif
Binary file added public/landing/gifs-512/planet-045.gif
Binary file added public/landing/gifs-512/planet-046.gif
Binary file added public/landing/gifs-512/planet-047.gif
Binary file added public/landing/gifs-512/planet-048.gif
Binary file added public/landing/gifs-512/planet-049.gif
Binary file added public/landing/gifs-512/planet-050.gif
Binary file added public/landing/gifs-512/planet-051.gif
Binary file added public/landing/gifs-512/planet-052.gif
Binary file added public/landing/gifs-512/planet-053.gif
Binary file added public/landing/gifs-512/planet-054.gif
Binary file added public/landing/gifs-512/planet-055.gif
Binary file added public/landing/gifs-512/planet-056.gif
Binary file added public/landing/gifs-512/planet-057.gif
Binary file added public/landing/gifs-512/planet-058.gif
Binary file added public/landing/gifs-512/planet-059.gif
Binary file added public/landing/gifs-512/planet-060.gif
Binary file added public/landing/gifs-512/planet-061.gif
Binary file added public/landing/gifs-512/planet-062.gif
Binary file added public/landing/gifs-512/planet-063.gif
Binary file added public/landing/gifs-512/planet-064.gif
Binary file added public/landing/gifs-512/planet-065.gif
Binary file added public/landing/gifs-512/planet-066.gif
Binary file added public/landing/gifs-512/planet-067.gif
Binary file added public/landing/gifs-512/planet-068.gif
Binary file added public/landing/gifs-512/planet-069.gif
Binary file added public/landing/gifs-512/planet-070.gif
Binary file added public/landing/gifs-512/planet-071.gif
Binary file added public/landing/gifs-512/planet-072.gif
Binary file added public/landing/gifs-512/planet-073.gif
Binary file added public/landing/gifs-512/planet-074.gif
Binary file added public/landing/gifs-512/planet-075.gif
Binary file added public/landing/gifs-512/planet-076.gif
Binary file added public/landing/gifs-512/planet-077.gif
Binary file added public/landing/gifs-512/planet-078.gif
Binary file added public/landing/gifs-512/planet-079.gif
Binary file added public/landing/gifs-512/planet-080.gif
Binary file added public/landing/gifs-512/planet-081.gif
Binary file added public/landing/gifs-512/planet-082.gif
Binary file added public/landing/gifs-512/planet-083.gif
Binary file added public/landing/gifs-512/planet-084.gif
Binary file added public/landing/gifs-512/planet-085.gif
Binary file added public/landing/gifs-512/planet-086.gif
Binary file added public/landing/gifs-512/planet-087.gif
Binary file added public/landing/gifs-512/planet-088.gif
Binary file added public/landing/gifs-512/planet-089.gif
Binary file added public/landing/gifs-512/planet-090.gif
Binary file added public/landing/gifs-512/planet-091.gif
Binary file added public/landing/gifs-512/planet-092.gif
Binary file added public/landing/gifs-512/planet-093.gif
Binary file added public/landing/gifs-512/planet-094.gif
Binary file added public/landing/gifs-512/planet-095.gif
Binary file added public/landing/gifs-512/planet-096.gif
Binary file added public/landing/gifs-512/planet-097.gif
Binary file added public/landing/gifs-512/planet-098.gif
Binary file added public/landing/gifs-512/planet-099.gif
Binary file added public/landing/gifs-512/planet-100.gif
Binary file added public/landing/png/planet-001.png
Binary file added public/landing/png/planet-002.png
Binary file added public/landing/png/planet-003.png
Binary file added public/landing/png/planet-004.png
Binary file added public/landing/png/planet-005.png
Binary file added public/landing/png/planet-006.png
Binary file added public/landing/png/planet-007.png
Binary file added public/landing/png/planet-008.png
Binary file added public/landing/png/planet-009.png
Binary file added public/landing/png/planet-010.png
Binary file added public/landing/png/planet-011.png
Binary file added public/landing/png/planet-012.png
Binary file added public/landing/png/planet-013.png
Binary file added public/landing/png/planet-014.png
Binary file added public/landing/png/planet-015.png
Binary file added public/landing/png/planet-016.png
Binary file added public/landing/png/planet-017.png
Binary file added public/landing/png/planet-018.png
Binary file added public/landing/png/planet-019.png
Binary file added public/landing/png/planet-020.png
Binary file added public/landing/png/planet-021.png
Binary file added public/landing/png/planet-022.png
Binary file added public/landing/png/planet-023.png
Binary file added public/landing/png/planet-024.png
Binary file added public/landing/png/planet-025.png
Binary file added public/landing/png/planet-026.png
Binary file added public/landing/png/planet-027.png
Binary file added public/landing/png/planet-028.png
Binary file added public/landing/png/planet-029.png
Binary file added public/landing/png/planet-030.png
Binary file added public/landing/png/planet-031.png
Binary file added public/landing/png/planet-032.png
Binary file added public/landing/png/planet-033.png
Binary file added public/landing/png/planet-034.png
Binary file added public/landing/png/planet-035.png
Binary file added public/landing/png/planet-036.png
Binary file added public/landing/png/planet-037.png
Binary file added public/landing/png/planet-038.png
Binary file added public/landing/png/planet-039.png
Binary file added public/landing/png/planet-040.png
Binary file added public/landing/png/planet-041.png
Binary file added public/landing/png/planet-042.png
Binary file added public/landing/png/planet-043.png
Binary file added public/landing/png/planet-044.png
Binary file added public/landing/png/planet-045.png
Binary file added public/landing/png/planet-046.png
Binary file added public/landing/png/planet-047.png
Binary file added public/landing/png/planet-048.png
Binary file added public/landing/png/planet-049.png
Binary file added public/landing/png/planet-050.png
Binary file added public/landing/png/planet-051.png
Binary file added public/landing/png/planet-052.png
Binary file added public/landing/png/planet-053.png
Binary file added public/landing/png/planet-054.png
Binary file added public/landing/png/planet-055.png
Binary file added public/landing/png/planet-056.png
Binary file added public/landing/png/planet-057.png
Binary file added public/landing/png/planet-058.png
Binary file added public/landing/png/planet-059.png
Binary file added public/landing/png/planet-060.png
Binary file added public/landing/png/planet-061.png
Binary file added public/landing/png/planet-062.png
Binary file added public/landing/png/planet-063.png
Binary file added public/landing/png/planet-064.png
Binary file added public/landing/png/planet-065.png
Binary file added public/landing/png/planet-066.png
Binary file added public/landing/png/planet-067.png
Binary file added public/landing/png/planet-068.png
Binary file added public/landing/png/planet-069.png
Binary file added public/landing/png/planet-070.png
Binary file added public/landing/png/planet-071.png
Binary file added public/landing/png/planet-072.png
Binary file added public/landing/png/planet-073.png
Binary file added public/landing/png/planet-074.png
Binary file added public/landing/png/planet-075.png
Binary file added public/landing/png/planet-076.png
Binary file added public/landing/png/planet-077.png
Binary file added public/landing/png/planet-078.png
Binary file added public/landing/png/planet-079.png
Binary file added public/landing/png/planet-080.png
Binary file added public/landing/png/planet-081.png
Binary file added public/landing/png/planet-082.png
Binary file added public/landing/png/planet-083.png
Binary file added public/landing/png/planet-084.png
Binary file added public/landing/png/planet-085.png
Binary file added public/landing/png/planet-086.png
Binary file added public/landing/png/planet-087.png
Binary file added public/landing/png/planet-088.png
Binary file added public/landing/png/planet-089.png
Binary file added public/landing/png/planet-090.png
Binary file added public/landing/png/planet-091.png
Binary file added public/landing/png/planet-092.png
Binary file added public/landing/png/planet-093.png
Binary file added public/landing/png/planet-094.png
Binary file added public/landing/png/planet-095.png
Binary file added public/landing/png/planet-096.png
Binary file added public/landing/png/planet-097.png
Binary file added public/landing/png/planet-098.png
Binary file added public/landing/png/planet-099.png
Binary file added public/landing/png/planet-100.png
6 changes: 6 additions & 0 deletions scripts/deploy-production-migrations.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { spawnSync } from 'node:child_process';

if (process.env.VERCEL_ENV === 'production') {
const result = spawnSync('pnpm', ['db:migrate:deploy'], { stdio: 'inherit' });
process.exitCode = result.status ?? 1;
}
13 changes: 13 additions & 0 deletions server/api/backendConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ describe('Minerals Economy configuration', () => {
const config = loadBackendPlanetConfig(baseEnv);
expect(config.mineralEconomyCutoverAt).toBeNull();
expect(config.mineralUpgradesEnabled).toBe(false);
expect(config.galaxyPulseStartBlock).toBeNull();
});

it('enables Galaxy Pulse from one explicit non-negative start block', () => {
expect(loadBackendPlanetConfig({
...baseEnv,
GALAXY_PULSE_START_BLOCK: '34567890',
}).galaxyPulseStartBlock).toBe(34_567_890n);

expect(() => loadBackendPlanetConfig({
...baseEnv,
GALAXY_PULSE_START_BLOCK: '-1',
})).toThrow('GALAXY_PULSE_START_BLOCK must be a non-negative integer');
});

it('parses a cutover and opt-in upgrade flag without changing Planet requirements', () => {
Expand Down
11 changes: 11 additions & 0 deletions server/api/backendConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type BackendPlanetConfig = {
/** Optional in dependency overrides so pre-v2 Planet APIs remain source-compatible. */
mineralEconomyCutoverAt?: Date | null;
mineralUpgradesEnabled?: boolean;
galaxyPulseStartBlock?: bigint | null;
siweOrigin?: string;
siweSessionSecret?: string;
};
Expand Down Expand Up @@ -48,6 +49,15 @@ function parseMineralUpgradesEnabled(env: Record<string, string | undefined>): b
throw new Error('MINERAL_UPGRADES_ENABLED must be a boolean.');
}

function parseGalaxyPulseStartBlock(env: Record<string, string | undefined>): bigint | null {
const raw = env.GALAXY_PULSE_START_BLOCK?.trim();
if (!raw) return null;
if (!/^\d+$/.test(raw)) {
throw new Error('GALAXY_PULSE_START_BLOCK must be a non-negative integer.');
}
return BigInt(raw);
}

function parseSiweOrigin(
env: Record<string, string | undefined>,
requiredForUpgrades: boolean,
Expand Down Expand Up @@ -124,6 +134,7 @@ export function loadBackendPlanetConfig(
rpcFallbackUrls,
databaseUrl,
confirmations,
galaxyPulseStartBlock: parseGalaxyPulseStartBlock(env),
...mineralEconomy,
siweOrigin: parseSiweOrigin(env, mineralEconomy.mineralUpgradesEnabled),
siweSessionSecret: parseSiweSessionSecret(env, mineralEconomy.mineralUpgradesEnabled),
Expand Down
Loading
Loading