SuperWallet docs
v1.1.0
Wallet demoAdmin demo Get help
● Ship · Online and in the stores

Deployment
hosting, Android, iOS, macOS.

Put the admin panel and the web wallet online, and build signed Android and iOS releases for Google Play, the App Store and TestFlight.

On Windows

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.

01Before any release

  • Rebrand done (checklist).
  • crypto_wallet/.env filled with your keys, including ADMIN_CONFIG_URL if you use it.
  • flutterfire configure run in both apps for your production Firebase project.
  • Version raised in crypto_wallet/pubspec.yaml: version: 1.0.0+9 means version 1.0.0, build 9. Every store upload needs a higher build number.
  • Checks pass:
cd crypto_wallet
flutter analyze
flutter test

02Admin panel on Firebase Hosting

crypto_wallet_admin/firebase.json has a hosting target admin. Its pre-deploy step builds the panel with PRODUCTION_MODE=true and the demo login off, and sends /config.json to the getRuntimeConfigJson function.

cd crypto_wallet_admin
firebase hosting:sites:create <admin-site>
firebase target:apply hosting admin <admin-site>
firebase deploy --only hosting:admin
  • Add <admin-site>.web.app (and any custom domain) to Authentication → Settings → Authorized domains, or sign-in fails.
  • Set ADMIN_CONFIG_URL=https://<admin-site>.web.app/config.json in crypto_wallet/.env and rebuild the app (how).
  • Custom domain: Hosting → your site → Add custom domain, add the DNS records at your registrar, wait for the certificate.
Never deploy a demo build as your admin

Demo mode (sample data, a local demo login) exists only when the panel is built with the demo build switch (--dart-define=ADMIN_CLIENT_DEMO_LOGIN_ENABLED=true). The package contains no demo email or password: if you ever build a demo, you choose them yourself with ADMIN_CLIENT_DEMO_EMAIL and ADMIN_CLIENT_DEMO_PASSWORD. Use such a build only on a separate site, never as your real admin.

Optional demo admin. firebase.json has a second hosting target, admin-demo: the same panel with a browser-local demo login that never writes to Firestore. It is off unless you build it with --dart-define=ADMIN_CLIENT_DEMO_LOGIN_ENABLED=true plus --dart-define=ADMIN_CLIENT_DEMO_EMAIL=… and --dart-define=ADMIN_CLIENT_DEMO_PASSWORD=… (12+ characters), then firebase deploy --only hosting:admin-demo. ADMIN_DEMO_MODE=true separately enables the offline preview workspace. No demo credentials are stored in the package.

03Web wallet on Firebase Hosting

cd crypto_wallet
flutter build web --release --dart-define-from-file=.env
firebase hosting:sites:create <wallet-site>
firebase target:apply hosting app <wallet-site>
firebase deploy --only hosting:app

crypto_wallet/firebase.json already has the app target with single-page routing, caching and security headers. Add the domain to Authorized domains. For web push, fill the firebaseConfig block in web/firebase-messaging-sw.js and add --dart-define=WEB_PUSH_VAPID_KEY=<key> to the build.

04Web wallet or admin on Vercel

Both builds are static sites and run on any HTTPS host. With Vercel, build first, add a vercel.json to the build folder, then deploy that folder. flutter build web empties build/web, so copy vercel.json in after every build.

Wallet

cd crypto_wallet
flutter build web --release --dart-define-from-file=.env
cat > build/web/vercel.json <<'EOF'
{
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
EOF
npx vercel deploy build/web --prod

Admin panel

The admin must be built with the production flags, and /config.json has to reach your function:

cd crypto_wallet_admin
flutter build web --release --dart-define=PRODUCTION_MODE=true --dart-define=ADMIN_CLIENT_DEMO_LOGIN_ENABLED=false
cat > build/web/vercel.json <<'EOF'
{
  "rewrites": [
    { "source": "/config.json",
      "destination": "https://europe-west1-<your-project-id>.cloudfunctions.net/getRuntimeConfigJson" },
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}
EOF
npx vercel deploy build/web --prod

Add the Vercel domain to Firebase Authorized domains. The security headers in firebase.json (frame, content-type, referrer and permissions policies, CSP) do not travel automatically; set them in Vercel under headers if you want the same protection. Copy the policy from firebase.json instead of writing a looser one: it allows no 'unsafe-inline' or 'unsafe-eval' at all and names its script origins (script-src 'self' 'wasm-unsafe-eval' https://www.gstatic.com), with connect-src 'self' https: wss: for RPC endpoints and the WalletConnect relay.

05Content-Security-Policy and Flutter upgrades

Both firebase.json files send a policy with no 'unsafe-inline' in script-src or style-src. The page's own styles are in web/splash.css (wallet) and web/styles.css (admin), and its scripts are files too. The Flutter engine adds two <style> blocks of its own at start-up; style-src allows exactly those two by hash:

style-src 'self' 'sha256-2hxujCU6WzSO6ycZznxFKlHE++BDsDuMYXT/O0zl8rM='
  'sha256-cgINIZw4iKgU7zccO2vPeZdQJIMlKEchLZG6hpkP+8k=' https://fonts.googleapis.com

The hashes match the engine of Flutter 3.41. After you upgrade Flutter, open the web build with the browser console visible. If it reports a refused inline style, it prints the hash it expected ('sha256-…'): put that hash in place of the old one in style-src, in both apps. Do not add 'unsafe-inline' instead.

The console also lists inline scripts the policy refused: FlutterFire tries to inject them, and web/firebase_csp_loader.js loads the same Firebase SDKs from https://www.gstatic.com/firebasejs/ instead, so those messages are expected.

The wallet's script-src also names https://www.google.com/recaptcha/, which App Check's reCAPTCHA v3 provider needs on the web (App Check).

06Backend updates

cd crypto_wallet
firebase deploy --only firestore:rules,firestore:indexes   # rules or indexes changed
firebase deploy --only functions                           # functions or functions/.env changed
firebase deploy --only functions:recordFeeEvent            # one function

07Android: APK and App Bundle

  1. Create an upload keystore onceBack up the file and the passwords; without them you cannot update your app.
    keytool -genkey -v -keystore ~/upload-keystore.jks \
      -keyalg RSA -keysize 2048 -validity 10000 -alias upload
  2. Create android/key.propertiesNext to android/build.gradle.kts. Never commit it. On a CI server you can use ANDROID_KEYSTORE_PATH, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD instead.
    storePassword=<your store password>
    keyPassword=<your key password>
    keyAlias=upload
    storeFile=/path/to/upload-keystore.jks
  3. Build
    cd crypto_wallet
    # App Bundle for Google Play
    flutter build appbundle --release --dart-define-from-file=.env
    # APK to install on a test phone
    flutter build apk --release --dart-define-from-file=.env
  4. UploadCreate a release in Play Console and upload build/app/outputs/bundle/release/app-release.aab, as described in Google's guide Prepare and roll out a release. Use Play App Signing.
No key, no upload

Without signing values the release build falls back to the debug key so that local release runs work. Google Play rejects debug-signed bundles.

08iOS: App Store and TestFlight

  1. Install pods
    cd crypto_wallet/ios
    pod install --repo-update
  2. SigningOpen ios/Runner.xcworkspace in Xcode → Runner → Signing & Capabilities. Select your Team, check the bundle ID, keep Push Notifications, In-App Purchase and Keychain Sharing.
  3. Push keyCreate an APNs authentication key in your Apple Developer account and upload it to your Firebase project, as described in Firebase's guide Configure APNs with FCM.
  4. Build the archive
    cd crypto_wallet
    flutter build ipa --release --dart-define-from-file=.env
  5. Upload to TestFlightXcode → Window → Organizer → Distribute App → App Store Connect, or drag the .ipa from build/ios/ipa/ into Apple's Transporter app. After processing it appears under TestFlight; add testers there.
  6. Submit for reviewRewrite the permission texts in ios/Runner/Info.plist (camera, Face ID, local network) with your app's name — reviewers read them.

09macOS: Mac App Store or your own download

The same source builds both. The only difference is the crypto PRO payment, which store policy does not allow inside an App Store app:

BuildCommandCrypto PRO payment
Mac App Storeflutter build macos --release --dart-define-from-file=.envHidden
Your own download (for example a notarized DMG)flutter build macos --release --dart-define-from-file=.env --dart-define=DIRECT_DISTRIBUTION=trueShown when enabled in Admin → System Config

DIRECT_DISTRIBUTION is false unless you pass it. Never pass it for a build you upload to App Store Connect.

10Store review notes

  • Apple 3.1.5: crypto wallets must come from a developer enrolled as an organisation.
  • Apple 3.1.1: sell PRO with In-App Purchase inside the iOS app; do not link to outside payment on iOS.
  • Google Play: some countries require wallet providers to be registered or licensed. Check the policy pages for the countries you publish in.
  • Fill App Privacy (Apple) and Data safety (Google) truthfully; Firebase Analytics, Crashlytics and Messaging collect data.
  • Tell reviewers the wallet is non-custodial, how to create a test wallet, and give them PRO (Admin → Users → Grant PRO).
Not legal advice

These are practical pointers. You are responsible for the licences and disclosures your business needs.

11After deploying: is it live?

  • The admin URL shows the Sign In screen, with no "Preview" or "Client demo" badge.
  • /config.json on your admin returns JSON with your values.
  • A change saved and published in the admin appears in the app after a restart.
  • A test push from Admin → Notifications arrives on a test phone.
  • The function logs show no repeated errors (viewing logs, or firebase functions:log).