The dextoro system, explained.
This page explains everything you are buying: the mobile app your users trade in, the dashboard you run it from, and the backend that ties it together. The default view is plain English. Flip the Simple / Technical switch at the top right for the engineer-level detail (file names, functions, settings).
The mobile app
The iOS and Android trading app on the App Store. Users sign in with email, get a wallet, and buy and sell Solana tokens.
The admin dashboard
A private website where you curate tokens, set the live app version, manage staff, and watch system health.
The backend
One Supabase project: the database, the server functions that run trades, login, and the live data the app shows.
The old "launchpad" product (token launch site, market-data, staking, seedbot, edge-cache) is shut down and is not part of this sale. The dextoro.com domain is also excluded, the app currently leans on it for links, so you will stand up your own domain (covered under Known issues and Operations).
Everything here was checked against the live code on 2026-06-08. Where older internal docs disagreed with the code, the code wins.
First steps, in order
A do-this-in-order checklist for a new owner. It pulls together actions that also appear in Operations and Known issues, so you have one path to follow. Plain action per line; turn on Technical for the exact settings and files.
-
Transfer the App Store app to your own Apple account.
The app, its store listing and its id move to your Apple team. The app keeps the same name and id.
Technical- Transfer in App Store Connect. Bundle
com.dextoro.appand App Store id6739891028stay the same. - Apple Team id changes from
65F2J667K9to yours. Your new Team id then has to go into the domain link-verification file (AASA) and the next build.
- Transfer in App Store Connect. Bundle
-
Take over (or replace) the
iosdextoro@gmail.comGoogle account.This one email is the hub for three things, so secure it before anything else: the admin dashboard seed login, the BirdEye account, and the Google Cloud project that holds the Google Play credential.
Technical- Seed
super_admininadmin_users=iosdextoro@gmail.com. - Same email owns the BirdEye account and Google Cloud project
dextoro-635a3(the Play service account). - Either take ownership of the mailbox, or migrate each service to your own account one by one.
- Seed
-
Issue your own key at every outside service, swap it in, then revoke the old keys.
For each service in the Services section, create your own account/key, paste it into the backend or the build settings, confirm things still work, then revoke the previous key last.
Technical- Services: Turnkey, Alchemy, Helius, BirdEye, Jupiter, OneSignal, Upstash, Sentry, MoonPay.
- Backend keys go in Supabase secrets; baked-in ones go in EAS (see Appendix). Redeploy functions after Supabase changes; rebuild the app after EAS changes.
- Revoke old keys only after the new ones are confirmed live.
-
Rotate the leaked Google Play key and purge it from git history.
The Play upload credential is committed in the app repo's history, so anyone with the repo has it. Rotate it and scrub it out.
Technicalservice-account-file.jsonis tracked and appears in 2 historical commits (projectdextoro-635a3).- Rotate the key in Google Cloud (required regardless), purge the file from history with git-filter-repo or BFG, and store the new key as an EAS secret, not in the repo.
-
Upgrade BirdEye to a paid plan and rotate its key.
This restores the price charts, which are degraded today because the free quota is maxed out.
Technical- Upgrade the BirdEye plan, then rotate
BIRDEYE_API_KEY(it is baked into the binary in dead code), updating both the Supabase secret and the EAS value.
- Upgrade the BirdEye plan, then rotate
-
Set a paid Jupiter key.
Trades work on the free public router today; a paid key raises the limits so trading stays smooth under load.
Technical- Set
JUPITER_SWAP_API_URL=api.jup.ag/swap/v1+JUPITER_SWAP_API_KEY(EAS) for the app swap path, andJUPITER_API_KEY(Supabase) for the token list/prices.
- Set
-
Change the seed super-admin password and keep super_admin to yourself.
Reset the dashboard login password and make sure you are the only super admin. Invite staff at lower roles.
Technical- Reset the password for the seed login (
iosdextoro@gmail.com) or create your own super admin and demote/remove the seed. - Invite staff as
managerorviewerfrom User Management (super_admin only).
- Reset the password for the seed login (
-
Fund the fee payer wallet with SOL.
This is the wallet that pays network fees for users. If it runs empty, trades start failing, so keep some SOL in it.
Technical- Send SOL to the fee payer wallet (pubkey is in
EXPO_FEE_PAYER_PUBKEY/ the dashboard; private key is the Supabase secretFEE_PAYER_PRIVATE_KEY).
- Send SOL to the fee payer wallet (pubkey is in
-
Stand up your own domain and ship a new build to repoint to it.
Because
dextoro.comis not included, set up your own domain with the link-verification files and the legal and landing pages, then build and submit a new app version that points at it.Technical- Serve
/.well-known/apple-app-site-association(with your Apple Team id),/.well-known/assetlinks.json, the/applinkslanding page, the/moonpayreturn, and real/termsand/privacypages. - Repoint mainly 3 files:
constants/urlConstant.ts,app.json,hooks/hooksWithContext/useReviewFlow.tsx; set up your own Branch app and MoonPay return URL. Full steps:DOMAIN_MIGRATION_RUNBOOK.md.
- Serve
-
Confirm on-chain that trading fees land in your treasury wallet.
After your build is live, make a small test trade and check that the fee actually arrives at your treasury wallet. Remember the dashboard treasury field is display-only, so do not trust it alone.
Technical- The destination is the baked-in
TREASURY_WALLETEAS value (project scope wins; watch the account-scope duplicate, see Operations). Verify the fee transfer on a block explorer, not just in the dashboard.
- The destination is the baked-in
Each item is expanded in Operations and Known issues. The treasury "ghost control" trap and the duplicate TREASURY_WALLET landmine are explained in full under Operations.
How the pieces fit together
Two apps talk to one backend. The backend talks out to a handful of specialist services. Click any box for what it is and how to manage it.
What users touch, and what you run it from
Supabase: the brain of the system
One Supabase project (ref ehauakpcvvuoocxjytwb) holds the database, runs the server functions that build and submit trades, handles login, stores avatars, and pushes live updates to the app. You manage it from the Supabase dashboard.
Server functions (the things the apps call)
These are small server programs ("edge functions"). Plain purpose on the left. Turn on Technical for which are login-protected and what they call.
| Function | What it does | Notes |
|---|
The main database tables
| Table | Holds |
|---|
Live updates (Realtime)
When something changes a user's balance (a deposit, a trade), the backend pushes it to that user's app instantly, so balances and the activity list update without a manual refresh. Implemented with Supabase Realtime listening to INSERTs on the activities table filtered by wallet (hooks/useRealtimeBalance.ts), kept warm by a 60s pg_cron job. Live price ticking on the home and token screens is a separate, older mechanism, see Known issues.
The specialist services dextoro relies on
Each of these is a separate company with its own account and dashboard. On takeover you generally create your own account, issue your own keys, and point the system at them. Each card says what it does, where its dashboard is, and how you manage it.
How the system actually works, step by step
Four common journeys, click through each step. The plain story is the default. Turn on Technical to see the exact function and file behind every step.
Running dextoro day to day
Where the money goes: the treasury wallet
Every buy or sell charges a small platform fee. That fee is sent to your treasury wallet, a Solana wallet you control. Fees pile up there automatically as people trade. The admin Infrastructure page shows the treasury balance and total fees collected.
The fee destination is baked into the app when it is built (it comes from a build setting called TREASURY_WALLET). The treasury field you see in the admin Settings page, and the matching admin_settings.treasury_wallet value, are display and record only right now. The app does not read them when it builds a trade. The server-side enforcement that was designed to make the dashboard the source of truth has not shipped yet.
So if a non-technical owner edits the treasury in the dashboard, it will look like it saved, but fees will keep going to the old wallet. The only way to actually change where fees land today is: update the TREASURY_WALLET build setting in EAS and ship a new app build (then confirm on-chain). See "Shipping an update" below.
In EAS there is a TREASURY_WALLET at project scope and a second one at account scope (shared, marked secret). Project scope wins by precedence, so today fees go where the project value points. But if anyone deletes or recreates the project-scope variable, the account-scope value silently takes over and fees can quietly route to a different wallet. This does not show up anywhere in the code, it lives only in EAS. After any EAS change or rebuild, check both variables and verify the on-chain fee destination.
The fee payer (gasless trading)
Solana charges a tiny network fee for every action, and setting up a new token in a wallet costs a small refundable deposit. The fee payer is a wallet that quietly covers those costs and co-signs every trade, so your users can trade without first buying SOL for "gas". It needs to stay funded with SOL.
- Keep it funded: send SOL to the fee payer wallet when it runs low. If it empties, trades start failing.
- Where its key lives: the private key is a Supabase secret (
FEE_PAYER_PRIVATE_KEY), never in the app. - To rotate: generate a new keypair, fund it, set
FEE_PAYER_PRIVATE_KEY(Supabase) and the matching pubkey inadmin_settings.fee_payer_pubkeyandFEE_PAYER_PUBKEY(EAS), then ship a build. Thetransactionfunction refuses to co-sign if the pubkey does not matchadmin_settings.
Who can do what (admin roles)
| Role | Can do |
|---|---|
| super_admin | Everything: tokens, tags, app version, settings (treasury / fees), and staff/user management. |
| manager | Curate tokens and tags. Cannot manage staff, settings, or the live version. |
| viewer | Read-only. Look, but no changes. |
Enforced both in the UI and in the database via the admin_role() function and row-level security on the admin_* tables. Staff are invited from the User Management page (super_admin only), which calls the admin-users function. The seed super admin is iosdextoro@gmail.com.
Shipping an update
| What | How | Speed |
|---|---|---|
| Mobile app | Build with EAS, submit to the App Store and Google Play, wait for review. Bump version + build number in app.json first (appVersionSource=local, so it is manual). One build per day, batch changes in. | Hours, not instant (Apple review ~2 to 6h) |
| Admin dashboard | Push to the main branch. Cloudflare rebuilds and deploys it automatically. | A couple of minutes |
| Backend functions | Deploy the function. supabase functions deploy <name> (reads verify_jwt from config.toml). Database changes are applied by hand (no migration chain). Secrets are set in the Supabase Dashboard, then redeploy. | Seconds |
Monitoring: what healthy looks like
- Admin Infrastructure page: the dashboard has a health page that should show green across the chain connection, the deposit watcher, the cache, the database, the scheduled jobs, live updates, and crash reporting. It refreshes about every 20 seconds and also shows user counts, trade volume, and the treasury balance. Backed by the
admin-healthfunction, which holds the keys server-side and only returns status to the browser. - Sentry: shows crashes and errors from the live apps. Healthy is a high crash-free rate and no sudden spike in new issues. Org
ios-dextoro, projectdextoro-app. Email alerts are not fully configured yet, that is a quick setup task. - Fee payer + treasury: the fee payer wallet should always have some SOL; the treasury balance should climb over time as people trade.
Where the secrets live
No secret values are in this document. Here is where each kind lives and how to rotate it. Full list in the Appendix.
- Backend secrets (API keys for the outside services, the fee payer key, login signing): in the Supabase Dashboard under the project's secrets. Rotate by issuing a new key at the service, pasting it in Supabase, then redeploying the functions.
- App build settings (what gets baked into the phone app, like the treasury wallet, the public RPC key, the Supabase URL): in EAS as environment variables. Changing them requires a new app build.
- Admin site settings (just the Supabase URL and public key): in Cloudflare Pages build settings.
Cross-reference: a fuller owner guide for the dashboard already exists in the admin repo at dextoro-app-admin/docs/OWNER_GUIDE.md.
As-is items, in the open
This is an as-is sale, so here is the honest list. Your engineer will find these anyway. None of them stop the core flows (login, trade, deposit) from working today.
| Item | What it means | What to do |
|---|---|---|
| New-coin buy can fail | A buy of a brand new token can fail in the fee / token-account setup path inside the trade function. It is not fully diagnosed. | Diagnose this before redeploying the trade function (for example before adding the referral validator). Lives in the transaction edge function's ATA / fee path. |
| BirdEye over its limit | The prices/charts provider is over its free monthly quota, so charts and some prices are degraded until it resets or you upgrade. | Upgrade BirdEye to a paid plan, then rotate the key. Key is baked into the binary in dead client code; rotate after upgrade. Over-cap caused a chart crash (guard shipped). |
| dextoro.com is excluded | The app's links (referrals, share links, deep links, the card-purchase return, legal/support links) point at dextoro.com, which is not part of the sale. |
Stand up your own domain and serve the link-verification files and pages, set up your own Branch and MoonPay return URL, then ship a new build. Repoint is mainly 3 files: constants/urlConstant.ts, app.json, hooks/hooksWithContext/useReviewFlow.tsx; plus AASA (your Apple Team ID) + assetlinks + landing/legal pages. A runbook exists: DOMAIN_MIGRATION_RUNBOOK.md. |
| Google Play key is in git history | The Google Play upload credential (service-account-file.json) is committed into the app repo and is present in its git history. Whoever gets the repo gets a live credential. |
Before close: purge it from git history and rotate the key in Google Cloud. Project dextoro-635a3; file is tracked and appears in 2 historical commits. Replace with an EAS secret going forward. |
| Swaps run on the free price router | Trades use Jupiter's free public endpoint, which has lower rate limits. Trades work, but under heavy load this can throttle. | Buy a Jupiter plan and set the paid URL + key. App falls back to lite-api.jup.ag/swap/v1 because the env URL is intentionally mis-set; set JUPITER_SWAP_API_URL=api.jup.ag/swap/v1 + key (common/index.ts). |
| Live price ticking is partial | Balances and deposits update live, but the constantly-moving prices on the home and token screens relied on old servers that are no longer running. | Decide a host for live prices (Supabase Realtime or a small worker) and wire it up. Legacy EXPO_TOKEN_WEBSOCKET_SERVER / EXPO_BACKEND_WEBSOCKET_SERVER (AWS) referenced in useTokenList.tsx / useActivity.ts are offline; Supabase Realtime covers balance/deposit. |
| Referral push not built | Deposit notifications work. The "you earned a referral reward" push was intentionally left for the buyer to add. | Build it on the existing push sender when ready. Deferred along with the trade function redeploy because of the new-coin buy issue above. |
| Old Firebase push half-removed | Push moved to OneSignal. Leftover Firebase plumbing is dormant but not fully deleted. | Finish removing the old Firebase pieces on a routine build. The push-token function is a stub returning empty. |
| Version number mismatch (cosmetic) | The app's two version files disagree (app.json says 2.5.4, package.json says 2.5.2). Harmless, but tidy it. |
Align them on the next build. App Store version is driven by app.json (2.5.4 / build 162 in source). |
Plain definitions
Reference
Public identifiers (safe to share)
| Identifier | Value | Note |
|---|---|---|
| App Store id | 6739891028 | Stays with the app on transfer |
| iOS bundle | com.dextoro.app | Unchanged on transfer |
| Android package | com.dextoro.pro | |
| Supabase project | ehauakpcvvuoocxjytwb | URL: ehauakpcvvuoocxjytwb.supabase.co |
| Branch link domains | dextoro.app.link (+ alternates) | Branch-owned, not dextoro.com |
| Intercom app id | nplqj2mh | Support chat (public id) |
| Sentry | org ios-dextoro / project dextoro-app | Crash monitoring |
| Apple Team id | 65F2J667K9 | Changes to the buyer's team on transfer |
| Treasury wallet | confirm in EAS / dashboard | Live value lives in the EAS build setting, not in the code (committed value is a placeholder) |
| Fee payer wallet | confirm in EAS / dashboard | Public key in EAS; private key in Supabase secrets |
Secrets inventory (names only, never values)
| Secret | Lives in | For |
|---|---|---|
TURNKEY_API_PRIVATE_KEY / _PUBLIC_KEY / _ORGANIZATION_ID | Supabase secrets | Wallets + login |
FEE_PAYER_PRIVATE_KEY | Supabase secrets | Co-signing + gas (the relayer wallet) |
ALCHEMY_API_KEY | Supabase secrets + EAS | Main chain connection |
HELIUS_API_KEY / HELIUS_WEBHOOK_ID / HELIUS_WEBHOOK_SECRET | Supabase secrets | Deposit watcher + its auth |
BIRDEYE_API_KEY | Supabase secrets | Prices + charts (rotate after upgrade) |
JUPITER_API_KEY / JUPITER_SWAP_API_KEY | Supabase secrets / EAS | Token data + paid swap routing |
ONESIGNAL_REST_API_KEY | Supabase secrets | Sending push (App ID is public) |
UPSTASH_REDIS_REST_URL / _TOKEN | Supabase secrets | The fast cache |
SENTRY_API_TOKEN | Supabase secrets | Health page reads crash stats |
SUPABASE_SERVICE_ROLE_KEY / JWT_SECRET | Supabase secrets | Internal admin access + session signing |
TREASURY_WALLET / FEE_PAYER_PUBKEY | EAS build env (baked into app) | Fee destination + relayer pubkey |
MOON_PAY_API_KEY, SUPABASE_URL, SUPABASE_ANON_KEY, etc. | EAS build env | App runtime config |
VITE_SUPABASE_URL / VITE_SUPABASE_ANON_KEY | Cloudflare Pages build env | Admin site config (both public) |
| Google Play service account | Currently in the repo (move to EAS secret) | Play submission, see Known issues |
Rotation rule of thumb: on takeover, issue your own key at each service, replace the value in Supabase (or EAS for the baked-in ones), redeploy or rebuild, then revoke the old key.