Operator console
run the app without code.
A small Node server you host yourself. It serves the MIKODES Admin Kit console at /admin, writes the app's settings to Firestore and gives you pages for revenue, users, alerts, sponsored slots, affiliate clicks, push campaigns, news and provider usage.
01What the console does
- Settings → Firestore. The console is the only writer of the app's configuration. On every save, and once at start, it writes
config/public(served to the app by the API's/v1/config) andconfig/private(read only by Cloud Functions: provider keys, feed URLs, AI key, RPC overrides, store verification and ad reporting keys). Each write bumpsversionand setsupdatedAt. - Invalid lines are skipped, never guessed. A news source without a URL, a waterfall that names a network without that format, a sponsored slot that ends before it starts: each is left out and listed on the Status page.
- Server-side access only. It uses the Firebase Admin SDK on the server. The browser never talks to Firestore.
- Keys are write-only. Only the owner can set them. They are encrypted with
ADMIN_SECRET_KEY, never shown again, never sent to the browser and written only toconfig/private. Each key has a Test button. - Money switches default to off: ads and every placement, Pro, in-app purchases, affiliates and sponsored slots. Every number has a hard minimum and maximum.
- Every write is audited in the kit's append-only audit log.
What it never does: it never writes a backend field (proStore, alertsCount, stats, purchases and so on), never sends a push message itself, never deletes a user's alert, never shows a key it stored, and never estimates, converts or projects revenue (admin/README.md, test admin/test/writers.test.ts).
02Environment variables
Copy admin/.env.example to admin/.env (never commit it) or set these in your host's environment.
| Variable | Purpose | Where to get it |
|---|---|---|
ADMIN_SECRET_KEY Required | 32 random bytes that encrypt the secrets the console stores (provider keys, AI key, store keys, RPC URLs). | Generate: openssl rand -hex 32. Back it up. Without it the stored secrets cannot be read. If it is missing, the console generates data/.admin-secret-key and warns in the log; move it into your environment. |
GOOGLE_APPLICATION_CREDENTIALS | Path to a service-account key file of your Firebase project. | Firebase console → Project settings → Service accounts → Generate new private key. Leave empty on Cloud Run or GCE to use the attached service account. |
FIREBASE_PROJECT_ID | Optional. Set it when the credentials do not name the project. | Your project id. |
FIRESTORE_EMULATOR_HOST | Local development only: use the Firestore emulator, for example 127.0.0.1:8080, with no credentials. Project demo-cryptotracker unless FIREBASE_PROJECT_ID is set. | — |
ADMIN_DATA | sample = built-in sample data and no Firebase at all (public demo). Empty = Firestore. | Leave empty on your install. |
ADMIN_DEMO_PASSWORD | Only on a public demo host: makes the console a read-only demo (account demo@cryptotracker.demo, a viewer). | Leave empty on your install. |
PORT | HTTP port. | Default 8790. |
DATABASE_URL | Optional Postgres for the console's own data (team, sessions, settings, audit) instead of data/admin.sqlite. | Neon, Supabase, Cloud SQL… Use a pooled URL with sslmode=require. |
Keep its key file on the console's server only. Never put it in the app, in git or in a support message. The Firestore indexes the console's queries need are in firebase/firestore.indexes.json and are deployed with the backend.
03Run it and create the owner
cd admin
npm ci
cp .env.example .env # fill in ADMIN_SECRET_KEY and GOOGLE_APPLICATION_CREDENTIALS
npm start # http://localhost:8790/admin
- Find the setup codeOn first start the log prints a one-time setup code. Each start before an owner exists prints a new one; any printed code works.
- Create the ownerOpen
/admin, choose Create the owner, enter the code, your e-mail and a password of 12 or more characters. - Add your teamUnder Team, add managers and viewers.
- Fill in the sections in this orderLegal (privacy and terms URLs), Data providers (a free CoinGecko Demo key lifts the rate limit), News sources, AI summaries (optional), then the money sections when you are ready. Status shows what is still missing.
Compiled build instead of tsx: npm run build && npm run start:dist.
04Roles and sign-in security
| Role | Can |
|---|---|
| Viewer | Read every page and setting (keys stay hidden). |
| Manager | Act on the product pages (users, alerts, sponsored, push, revenue entries) and change settings. |
| Owner | Everything, plus set secrets and store identifiers, and manage the team. |
The kit adds two-factor sign-in codes, a lockout after 5 failed sign-ins within 15 minutes, settings history and export/import (vendored Admin Kit 0.4.0, admin/vendor/mikodes-admin/; do not edit those files, fixes arrive with product updates).
05Deploy options
| Where | How |
|---|---|
| Any Node 22 host (VPS, Render, Railway, Fly) | npm ci && npm start. Data lives in data/admin.sqlite: put data/ on a persistent disk, back it up together with ADMIN_SECRET_KEY, and run one instance. |
| Docker | See below. The volume keeps /app/data. |
| Serverless or several instances (Cloud Run with autoscaling) | Set DATABASE_URL to Postgres; tables are named mk_cryptotracker_*. Give the service an account with the Cloud Datastore User role and leave GOOGLE_APPLICATION_CREDENTIALS empty. The console uses only Firestore (it never sends push itself). |
cd admin
docker build -t cryptotracker-admin .
docker run -p 8790:8790 -v cryptotracker-admin-data:/app/data --env-file .env cryptotracker-admin
Put HTTPS in front of it (a reverse proxy or the platform's TLS). The session cookie is marked Secure over HTTPS and the server honours x-forwarded-proto.
06Settings sections
| Section | What you set |
|---|---|
| Brand | App name (default "Cryptocurrency Tracker"), accent colour (default #10B981), support e-mail. See Rebranding. |
| Legal | Terms URL, privacy policy URL, the disclaimer shown under prices, the brief and every report, and the four labels: "AI-generated", "Automatic summary", "Sponsored", "Affiliate link". The labels cannot be empty. |
| Features | Switch whole parts of the app on or off: portfolio, price alerts, news, wallet tracking (public addresses), converter, market brief and coin reports, home-screen widgets, cloud backup of the user's lists. All on by default. |
| Sign-in methods | Which sign-in buttons the app shows: Google, Apple (iOS and web), e-mail and password. All on by default; keep a method on only after enabling that provider in Firebase Authentication. With all three off the app says sign-in is not offered (users stay anonymous). |
| Market | Default currency, the fiat currencies offered (30 by default), coins per page (10–250), refresh interval (15–3600 s), category chips (CoinGecko category ids). |
| Data providers | Primary provider, failover, CoinGecko plan and key, CoinMarketCap key, per-minute and monthly budgets, cache times. See Data providers. |
| News sources | RSS feeds, refresh interval, how long headlines are kept. See News sources. |
| AI summaries | On/off, brief, coin reports, provider, key, model, caps and per-user allowances. See AI summaries. |
| Ads | Master switch, one network or a waterfall per format, every placement. See Ads. |
| AdMob / AppLovin MAX / Unity Ads / ironSource LevelPlay ids | App ids, SDK keys and unit ids per network and platform. |
| Pro and purchases | Pro on/off, free and Pro limits, home-screen widgets for Pro only, perks, store product ids, AI pack size, store-verification keys. See Purchases. |
| Affiliates | On/off, the Buy button, the disclosure and the exchange links. See Affiliates. |
| Sponsored | Sponsored coins and sponsored news (easier on the Sponsored page). See Sponsored. |
| Wallet chains | Which chains users can look up, and your own RPC URLs. See Wallets. |
| Ad revenue reporting | Optional AdMob and AppLovin MAX reporting keys. See Revenue centre. |
| Notifications | The kit's standard operator notifications: an e-mail address for console alerts, and an optional webhook URL with a signing secret. These are for you, not for app users (app push is under Push campaigns). |
07Product pages
| Page | What it shows and does |
|---|---|
| Overview | Real counts only: users, active and new today, Pro users, active alerts, alerts triggered and AI calls today, CoinGecko calls and CoinMarketCap credits this month. |
| Revenue centre | Realised revenue only. Record sponsor bookings, affiliate and ad payouts, pull ad reports. See Revenue centre. |
| Users | Search by user id (users sign in anonymously). Grant Pro until a date or for life, revoke the console grant, add or remove AI credits, grant remove-ads, ban or unban. Every action needs a reason and is audited. |
| Alerts | Active alerts by type and coin, triggers in the last 7 days, recent triggers. Disable switches an alert off; the console never edits or deletes a user's alert otherwise. |
| Sponsored | Sponsored coins and news with schedule, state, impressions and clicks; record a sponsor booking. |
| Affiliate clicks | Taps per exchange link. Payouts are recorded in the revenue centre. |
| Push campaigns | Create a message to everyone, Pro or free users, with an optional coin deep link, at a time or Send now; cancel while scheduled. See Push campaigns. |
| News | The newest stored headlines and the last item seen per source. |
| Provider usage | Calls, credits and errors per provider and month against your budgets. |
| Status | Firestore reachable, last config sync, CoinGecko with the configured plan, the CoinMarketCap key, the AI key (a passing test is reused for 6 hours so the page does not spend tokens on every visit), each enabled feed, each RPC override, ad ids for what is on, store verification, ad reporting, revenue events without an amount, legal URLs, and every skipped settings line. |
08Public demo console
To show the console to others without Firebase, run a separate instance with ADMIN_DATA=sample and ADMIN_DEMO_PASSWORD=<a password you publish>. Visitors sign in as demo@cryptotracker.demo, a viewer. No owner exists and every write answers 403. Sample users, alerts and campaigns are labelled "Sample" (ids start with sample-). The sample has no revenue at all, no news headlines, no provider usage and no sponsored or affiliate numbers. Its settings produce exactly the demo app's configuration (firebase/functions/src/api/demo-config.ts, checked by admin/test/sample.test.ts). Never set these two variables on your real console.
09Tests
cd admin
npm test # manifest, defaults = contract, config mapping, writers, checks, revenue, sample, page routes
npm run test:emulator # real writes against the Firestore emulator (needs the Firebase CLI and Java)
npm run build # TypeScript check + compile to dist/
When these docs were written, npm test reported 65 passing tests (9 emulator tests are skipped without the emulator), checked again at the 2.0.0 release commit.