Your own keys and URLs
RPC endpoints, market-data keys, the privacy policy and the account-deletion URL — the four things that must be yours before you submit.
Four things in a published build must be yours. Three of them work in a development build with our placeholders, which is exactly why they get missed.
RPC endpoints
Your deployment talks to chains through RPC providers. Vendor or demo endpoints are shared quota: your customers' traffic competes with everyone else's, and the provider rate-limits the endpoint rather than the app, so the failure looks like your platform being slow.
Register your own with whichever providers you use, restrict them to your own origins, and put them in your server configuration — not in the app. The client never talks to a chain directly.
Give each chain more than one endpoint while you are there. From Core 6.7.2 a chain accepts a comma-separated list, moves to the fastest, drops one that keeps failing and picks it up again when it recovers — which matters more for an app than for a website, because a rate-limited endpoint reaches your customer as a balance that will not load.
An Origin not allowed error from a provider is nearly always the dashboard
allow-list rather than a bad key.
Market-data keys
Same argument, plus one of Apple's. The packaged app_config.json ships with
empty placeholders for the client-side keys, and
package_for_codecanyon.sh overwrites the working file with the example on the
way into the archive, so a vendor key cannot reach a buyer by accident.
Put your own in, and prefer proxying quota-bearing data through your backend so the key is not in a binary at all. Apple 5.2.2 treats shipping a third party's credentials in your app as your problem, not theirs.
A Firebase project, if you want push notifications
Push is optional and off until you configure it. The app builds, runs and publishes with no Firebase project at all — it simply never asks for notification permission and never receives anything.
It has to work that way. The app ships as source, so a Firebase configuration
committed to it would be ours, registering your customers' devices against our
project. And the Google Services Gradle plugin fails the build outright when its
configuration file is missing, so requiring it would mean your first
flutter build failing on a file you had never heard of.
To turn it on:
-
Create a Firebase project and add an Android app using this module's application id.
-
Download
google-services.jsonintoandroid/app/. The build detects it and switches push on; you will seegoogle-services.json found - Firebase push ENABLEDin the Gradle output. Without it you get the same line saying DISABLED, and the build succeeds. -
For iOS, add
GoogleService-Info.plistto the Runner target and upload an APNs key in the Firebase console. iOS delivers through APNs whatever Firebase is doing, so an app with FCM configured and no APNs key sends nothing and reports no error. -
Configure the server half —
FCM_PROJECT_ID,FCM_CLIENT_EMAILandFCM_PRIVATE_KEYfrom a service account in the same project. See Notifications. Neither half reports the other's absence.
android/app/google-services.json and ios/Runner/GoogleService-Info.plist
identify your project, and package_for_codecanyon.sh copies android/ and
ios/ wholesale. Both are gitignored for the same reason
android/key.properties is. A buyer creates their own; there is nothing to
hand them.
A privacy policy URL
Required by both stores, on a URL you control, describing your service — what you collect, why, how long you keep it, and who you share it with. A policy that names the vendor rather than the operator is a rejection.
An account-deletion URL
Both stores require an in-app path to delete an account and a public URL that explains it. The platform ships both: deletion anonymises the record inside the same transaction as the soft delete — email tombstoned, password, phone, avatar, username, wallet address, profile and settings cleared — and the email address is released, so a deleted customer can register again.
The public page is at /account-deletion on your frontend. Give that URL to
Apple and Google, and check it describes what your deployment actually does:
the prose and the code list the same fields and are kept in step by hand.
Enable Push Notifications in Xcode, or iOS push is dead
firebase_messaging ships and the app asks for notification permission at
startup. On iOS, that is not enough: APNs needs the aps-environment
entitlement, and the project has none until you add it.
Xcode target Runner → Signing & Capabilities → + Capability → Push
Notifications. That creates Runner.entitlements and points the build at it.
Nothing tells you if you skip it. The permission prompt still appears, the
customer still agrees, the build still archives and still passes review — and
no notification is ever delivered. The preflight refuses it once a
GoogleService-Info.plist is present, which is the point at which you have
said you want push.
Four steps nothing else will tell you about
Each of these fails quietly. Three of them produce a build that compiles, installs and runs, and is still ours in a way a reviewer can see.
Replacing the app icon does nothing on its own
assets/icons/app_icon.png is an input. What ships is what
flutter_launcher_icons writes from it — the Android mipmaps under
android/app/src/main/res/ and the iOS appiconset — and those are committed
files that do not regenerate themselves.
So after you replace the source:
dart run flutter_launcher_iconsSkip it and your app carries our green mark on every home screen, while the source PNG sitting beside it is yours. The preflight checks both now, but it was possible to pass the old one and ship our icon.
Clear the iOS development team
ios/Runner.xcodeproj/project.pbxproj carries a DEVELOPMENT_TEAM value from
our own Apple account. Open the project in Xcode, select the Runner target →
Signing & Capabilities, and set the team to yours.
This one is fail-closed — Xcode cannot archive with a team you are not a member of — so it costs you an error rather than a rejection. It is here because the error does not say what it wants.
Changing the bundle identifier is a separate step from the name
The installer rewrites the display name. The identifier is what the stores key your app on and it is never guessed for you:
- iOS — Xcode, Runner target → Signing & Capabilities → Bundle Identifier.
- Android —
applicationIdinandroid/app/build.gradle.kts, and thepackagein the manifest and Kotlin source directory must match it.
Use a domain you control, reversed. The preflight refuses anything still under
com.bicrypto — including the misspelt com.bicryptto, which is the one
actually in the iOS project.
Bump the version between releases
version: in pubspec.yaml is name+build — 1.0.0+1. Both stores refuse an
upload whose build number they have already seen, and the message arrives after
a full release build has finished. Raise the number after + for every upload,
and the part before it whenever the release is worth a version to a customer.
Before you submit
dart run tool/release_preflight.dart --explain refuses a build still pointing
at a demo backend, still carrying our icon, still under our bundle identifier,
or with no signing key configured.