Configuration
keys, settings, publishing.
Where each setting lives: build-time keys in .env, backend settings in functions/.env, and everything else in the admin panel, delivered to the app through Remote Config.
The commands on this page are written for macOS and Linux shells. On Windows, run them in Git Bash or WSL, where they work as written. In PowerShell, set a variable with $env:NAME="value" instead of export. Flutter, Node.js and the Firebase and FlutterFire CLIs run natively on Windows; iOS and macOS builds need a Mac.
01Three places, one rule
| Where | What goes there | Change needs |
|---|---|---|
crypto_wallet/.env | Client API keys (Reown, 0x, 1inch, RPC providers…) | A new build — or Admin → System Config → Remote API Keys |
crypto_wallet/functions/.env + Secret Manager | Server secrets: store validation, owner UID | Redeploying the functions |
| Admin panel → Firestore → Remote Config | Fee, prices, branding, chains, tokens, dApps, switches | Save + Publish to mobile app. No new build. |
Everything passed with --dart-define is compiled into the app and can be extracted from it, and keys entered under Admin → System Config → Remote API Keys are served publicly in /config.json. Put only client-side keys in either place and restrict each key in its provider dashboard (bundle ID, domain, rate limits). Real secrets never go into the app (Secrets & backups).
02App keys (.env)
cd crypto_wallet
cp .env.example .env
# every run and build then adds:
flutter run --dart-define-from-file=.env
| Variable | Purpose | Get it at |
|---|---|---|
REOWN_PROJECT_ID Required | WalletConnect pairing | cloud.reown.com |
ZEROX_API_KEY | 0x swap quotes on EVM chains. Spell it exactly like this. | dashboard.0x.org |
ONEINCH_API_KEY | 1inch swap quotes on EVM chains | portal.1inch.dev |
ALCHEMY_API_KEY | Main EVM RPC | alchemy.com |
HELIUS_API_KEY | Solana RPC and asset data | helius.xyz |
MORALIS_API_KEY | NFT metadata, prices, history | admin.moralis.io |
ETHERSCAN_API_KEY, BSCSCAN_API_KEY, POLYGONSCAN_API_KEY, ARBISCAN_API_KEY, OPTIMISM_ETHERSCAN_API_KEY, BASESCAN_API_KEY | Transaction history per chain | Each explorer's API page |
BIRDEYE_API_KEY | Optional Solana market data | birdeye.so |
COINGECKO_API_KEY | Optional. Public CoinGecko endpoints work without it. | coingecko.com/en/api |
TRANSAK_API_KEY | Buy crypto screen | Transak partner dashboard |
ADMIN_CONFIG_URL Recommended | Address of your admin's config.json. Empty (the default) = Remote Config only. | Your admin hosting address (how) |
IAP_PRO_WEEKLY_ID | Store product ID of the weekly PRO subscription | You choose it (Earnings → PRO) |
IAP_PRO_MONTHLY_ID, IAP_PRO_ANNUAL_ID | Extra plans. Leave empty unless you sell them. |
Leave these empty. They are in .env.example but this version does not use them:
TENDERLY_*, INFURA_API_KEY, QUICKNODE_*, OPENSEA_API_KEY, FCM_SERVER_KEY, MIXPANEL_TOKEN, AMPLITUDE_API_KEY, FORCE_ENABLE_*, FORCE_DEBUG_LOGGING, APP_ENV, FEE_WALLET_ADDRESS, FEE_BPS.
The fee is set in the admin, not here. CLAUDE_API_KEY must never be in the app's .env; it is a backend secret.
Without keys
| Missing | What happens |
|---|---|
REOWN_PROJECT_ID | WalletConnect screens open but cannot connect. |
ZEROX_API_KEY | 0x is skipped; 1inch is tried. |
| Both swap keys | EVM quotes come only from direct DEX routers where the app knows one (no fee is collected on those), otherwise an estimate marked ~ Indicative via CoinGecko that cannot be executed. |
03Backend settings (functions/.env)
Read by the Cloud Functions at deploy time. Never shipped inside the app. The provider accounts behind these keys (Apple, Google, Anthropic) and your Firebase Blaze plan are your own, and they may charge usage fees that you pay.
| Variable | Purpose | Where to get it |
|---|---|---|
APP_NAME | Name shown in authenticator apps for admin sign-in codes | Your choice |
ANDROID_PACKAGE_NAME | Your Android application ID. Google Play purchases are checked against this app only. | Your application ID |
APP_STORE_ALLOW_SANDBOX | true only while testing with TestFlight or sandbox purchases; empty in production, so sandbox purchases never unlock PRO. | Your choice |
APPLE_BUNDLE_ID | Your iOS bundle ID. App Store purchases for any other app are refused. Defaults to ANDROID_PACKAGE_NAME. | Your bundle ID |
PRO_WEEKLY_PRODUCT_ID, PRO_MONTHLY_PRODUCT_ID, PRO_ANNUAL_PRODUCT_ID | Store product IDs the backend accepts. Same values as IAP_PRO_*. | App Store Connect, Play Console |
APP_STORE_KEY_ID, APP_STORE_ISSUER_ID, APP_STORE_PRIVATE_KEY | Checks iOS purchases. The private key is the text of your .p8 file, in double quotes, line breaks written as \n. | An App Store Connect API key for the App Store Server API (Apple: creating API keys) |
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON | Checks Android purchases. The service-account JSON on one line. | A Google Cloud service account with access to your Play Console app (Google: Play Developer API setup) |
WEBHOOK_SECRET | Signs the optional "new PRO subscriber" webhook. 16+ characters. | Make one: openssl rand -hex 32 |
| Secret (Secret Manager) | Purpose |
|---|---|
OWNER_UID | Firebase UID of the owner, who can always manage admin rights. |
CLAUDE_API_KEY | Anthropic key for the optional Portfolio Brain PRO feature. |
ADMIN_SETUP_TOKEN | One-time token that bootstrapFirstAdmin requires to create the first admin (how). 24 characters or more; unset after the first admin exists. |
Portfolio Brain limits
callPortfolioBrain answers only callers that have an active, paid PRO plan verified by the server (store purchase, crypto payment or admin grant; the free trial does not include Portfolio Brain) and a valid App Check token, and it accepts structured portfolio data only, never a free-text prompt (questions are capped at 500 characters). Two daily caps in functions/.env bound your Anthropic bill; counters are kept per UTC day in the _ai_usage_daily collection.
| Setting | Default | Meaning |
|---|---|---|
PORTFOLIO_BRAIN_USER_DAILY_LIMIT | 30 | Calls per user per day |
PORTFOLIO_BRAIN_GLOBAL_DAILY_LIMIT | 1000 | Calls per day for your whole deployment |
App Check
The app activates Firebase App Check right after Firebase starts (lib/core/firebase/app_check_service.dart). Portfolio Brain needs it; nothing else depends on it.
- Register your appsIn App Check (Firebase guide) register Play Integrity for Android (add your release SHA-256 fingerprint in the project settings), App Attest for iOS and macOS (DeviceCheck is the fallback), reCAPTCHA v3 for the web.
- iOS: App Attest capabilityApp Attest works only when the app is signed with the App Attest entitlement.
ios/Runner/Runner.entitlementsalready containscom.apple.developer.devicecheck.appattest-environment(production; the debug file usesdevelopment). In Xcode openios/Runner.xcworkspace→ Runner target → Signing & Capabilities and check that App Attest is listed (add it with + Capability if it is not), so it is also enabled on your App ID and in your provisioning profile. Without it, iOS release builds get no App Check token and Portfolio Brain falls back to its on-device summary. - Web site keyBuild the web app with
--dart-define=APP_CHECK_WEB_RECAPTCHA_SITE_KEY=<your site key>. Without it App Check stays off on the web and Portfolio Brain falls back to its on-device summary there. - Debug buildsDebug builds use the debug provider: run once, copy the debug token from the console log into App Check as a debug token (Firebase: debug provider).
cd crypto_wallet
firebase functions:secrets:set OWNER_UID
firebase deploy --only functions # after every change to functions/.env or a secret
04Runtime config: how a change reaches phones
- SaveEach admin page has its own save button (Apply Fee Config, Save Branding…). Saving writes to Firestore (
app_config/runtime_admin_config) and updates the live/config.jsonat once. - PublishPublish to mobile app (bottom-right of every page) calls
publishAdminRemoteConfig, which updates theadmin_configvalue in Firebase Remote Config. - Users get itRelease builds fetch Remote Config at most once an hour; debug builds on every start. An open app also applies Remote Config's live updates. The
config.jsoncopy is refreshed in the background on each cold start.
To see a change on a test phone: publish, then fully close and reopen the app.
05Remote Config values
You normally change these through the admin pages. The table helps when you look at the admin_config parameter in Remote Config (Firebase: Remote Config parameters).
| Key | Meaning | Admin page |
|---|---|---|
maintenance_mode, maintenance_message | Shows a maintenance screen instead of the wallet | Remote Config, System Config |
swap_enabled, bridge_enabled, buy_enabled, sell_enabled, send_enabled, walletconnect_enabled, discover_enabled, push_enabled | On/off switch per feature | Remote Config |
min_app_version | Force-update gate (x.y.z). Empty = off. | Remote Config |
announcement_banner, announcement_url | Banner text and HTTPS link inside the app | Remote Config, Campaigns |
fee_wallet_address, fee_bps | EVM fee wallet and fee size (100 bps = 1 %). Default: empty and 0. Maximum 200 bps (2 %). | Monetization |
solana_fee_wallet_address | Solana fee wallet: its token accounts receive the fee on Jupiter swaps. Default: empty. | Monetization |
pro_fee_waiver | PRO users swap without your fee | Monetization |
pro_weekly_price_eur, pro_trial_days | PRO price shown in the app; free trial length in days (0 = no trial: no trial button, server refuses; max 30) | Monetization, System Config |
pro_crypto_payment_*, pro_crypto_required_amount_eur (weekly), pro_crypto_monthly_amount_eur, pro_crypto_annual_amount_eur | Pay-PRO-in-crypto settings; a plan priced at 0 is not sold for crypto | System Config |
admin_chains, admin_tokens, admin_dapps, featured_tokens, default_home_tokens | Content lists pushed to the app | Chains, Tokens, DApps |
app_name, tagline, primary_color, logo_url, support_url, website | Branding shown in the app | Branding |
support_email, terms_url, privacy_url, about_text, swap_disclaimer | Stored, but the wallet in this version does not display them (details) | Branding, System Config |
config_version | Goes up with each publish | Publish Config |
Editing the JSON by hand works, but the next publish from the admin overwrites it, and the admin checks addresses and ranges for you.
06Where data is stored
| Firestore collection | Contains |
|---|---|
app_config/runtime_admin_config | The full admin config; source of /config.json |
app_config/global | System Config values, including the Remote API Keys. Public read, like all of app_config. |
admin_config/* | Admin-only: IP allowlist. Exception: admin_config/onramp (Transak settings) is public read. |
chains, tokens, dapps, admin_nft_collections | Content you curate |
swap_config, branding/global | Swap routing, bridge settings, branding |
campaigns, scheduled_campaigns, notification_templates | Campaigns and push messages |
fee_events, users, premium_entitlements, pro_crypto_payments | Written by the app and functions; shown in Revenue, Users, Analytics |
admin_users, admin_audit_log, config_versions | Admins and roles, Activity Log entries, config snapshots |
Who can read and write each collection is decided by crypto_wallet/firestore.rules.