Deployment
web, Android, iOS.
Put the web terminal online, and build signed releases for Google Play and the App Store. The web build is the primary target and was verified for these docs.
01Before any release
- Rebrand done (checklist), including your own legal pages.
- Your config is published (
config.jsonor Firebase) with the builder fee you want, andhyperliquid_perps_enabledset the way you intend for this platform. - Version raised in
pubspec.yaml:version: 1.0.0+1means version 1.0.0, build 1. Every store upload needs a higher build number. - Checks pass:
flutter gen-l10n
flutter analyze
flutter test
02Web
flutter build web --release # add your --dart-define values here
The result is a static site in build/web/. Upload the whole folder to any static host (for example Vercel, Netlify, Cloudflare Pages, Firebase Hosting or your own server). The app uses Flutter's default # URLs, so no rewrite rules are needed.
- Serve it over HTTPS on your own domain. The web vault needs a secure context (https, or localhost while developing) to derive its key.
- Send the security headers. Firebase Hosting reads them from
firebase.json; Netlify and Cloudflare Pages frombuild/web/_headers; Vercel frombuild/web/vercel.json. On nginx or Apache, copy the lines fromweb/_headersinto your server config. The same Content-Security-Policy is also inweb/index.htmlas a meta tag (details). - If
config.jsonis on another domain, allow your terminal's origin with a CORS header on that host. - Verified for these docs:
flutter build web --releasesucceeds with Flutter 3.41.4.
03Firebase Hosting and Firestore rules
firebase.json in the project root serves build/web with the headers above and points Firestore at firestore.rules. With the Firebase CLI logged in and your project selected (firebase use --add):
flutter build web --release
firebase deploy --only hosting
firebase deploy --only firestore:rules # only if you use Firestore
Deploy the rules before you publish app_config/runtime_admin_config: they make that document read-only for every client and limit user documents to their owner (what they allow).
04Never ship a demo build
--dart-define=AXIS_DEMO=true builds the sample desk used for public demos: fixture balances, positions and a synthetic builder address, with orders quoted but not sent. In a normal build the demo code is compiled out. To prove your release bundle is clean, run:
bash tool/verify_demo_absent.sh
It builds the web app twice (with and without the demo switch) and prints PASS only when every demo marker is present in the demo build and absent from the normal one. Note that it leaves the normal build in build/web/. Verified for these docs: the script prints PASS on the 1.0.0 source.
05Android (Google Play)
- Create an upload keyOnce. Keep the file and passwords safe and out of Git (
*.jksandkey.propertiesare in.gitignore).keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload - Create
android/key.properties
The same four names also work as environment variables (useful on a CI server). The older namesANDROID_KEYSTORE_PATH=/Users/<you>/upload-keystore.jks ANDROID_KEYSTORE_PASSWORD=<store-password> ANDROID_KEY_ALIAS=upload ANDROID_KEY_PASSWORD=<key-password>storeFile,storePassword,keyAlias,keyPasswordare accepted too. - Build the bundle
Output:flutter build appbundle --releasebuild/app/outputs/bundle/release/app-release.aab. Upload it in Play Console.
Without all four signing values the release build falls back to the debug key (android/app/build.gradle.kts). That build runs, but Google Play will not accept it. Verified for these docs: flutter build apk --release succeeds on the unconfigured project (no Firebase, debug-key fallback). A signed appbundle was not built in this pass.
Minimum Android version: API 24 (Android 7.0). Only arm64-v8a and armeabi-v7a are packaged for plugin libraries.
06iOS (App Store)
- Install pods
cd ios && pod install && cd .. - Set signingOpen
ios/Runner.xcworkspacein Xcode → Runner → Signing & Capabilities: choose your team and your bundle ID. - Build
Upload the result fromflutter build ipa --releasebuild/ios/ipa/with Xcode Organizer or Apple's Transporter app.
Minimum iOS version: 15.5. The app icon and launch screen are compiled from ios/Runner/Assets.xcassets and ios/Runner/Base.lproj by Xcode's normal resource phase, so dart run flutter_launcher_icons changes the iOS icon (rebranding). Verified for these docs: flutter build ios --release --no-codesign succeeds with Xcode 26.4.1 and Flutter 3.41.4, and the built app contains an Assets.car compiled from the catalog (AppIcon, LaunchImage) and the compiled Main and LaunchScreen storyboards.
07Store review notes
- The mobile builds contain the whole terminal. Markets, trade, order ticket, positions, deposits and withdrawals are all in the iOS and Android apps, exactly as on the web. Submit the build with the same published configuration your users will get: if your users can trade, the store reviews it with trading on. Choose the countries you publish in from App Store Connect and the Play Console, not with a switch.
hyperliquid_perps_enableddefaults tofalseonly so a fresh build does not trade before you have configured it; switched off, it refuses new positions while closes and withdrawals stay available. Declare the app for what it is: leveraged trading apps have their own rules in both stores, and meeting them in your market is your responsibility. - Apple: apps that hold crypto keys generally need an organisation developer account (App Store Review Guideline 3.1.5; what to do).
- Both stores: have your privacy policy, terms and risk disclosure online, and fill in the stores' data-safety / privacy forms truthfully (Firebase analytics and crash reports send data if you enable Firebase).
- Leveraged trading is restricted or prohibited in some countries. Limit availability in the store consoles where needed.
08After release
- Open the Earnings tab on a production build and press RECHECK (checklist).
- For an important fix, publish
min_app_versiononce the new version is live in the stores, so older installs are asked to update. - Maintenance: publish
maintenance_mode: truewith a message; publishfalseto end it.