Store-release pipeline (iOS + Android)

Full end-to-end, tag-driven store-release automation for btc-wallet, tracking issue #178. Mirrors RealUnitCH/app's pipeline 1:1, adapted from its Flutter stack to our React Native (BlueWallet-fork) stack.

Setting it up? The one-time provisioning — every secret and exactly where to get it — is in the companion runbook release-pipeline-setup.md.

Goal: a single Git tag builds, signs, and uploads both the binary and the store listing (texts, images, screenshots) to App Store Connect / TestFlight and the Google Play Store — no manual upload steps beyond the final human "Submit/Promote" click in the consoles. The existing reproducible + attested Android build (build-release-apk.sh, code-transparency, actions/attest) is preserved; store delivery is added on top of it.

Bundle/package id is unified on both platforms: swiss.dfx.bitcoin.


Phases & status

All implemented in one PR (#181). "Implemented" = code written + syntax/preflight validated locally; the store-upload lanes are untested end-to-end until the external prerequisites + secrets are provisioned (see below).

Phase Scope Status
1 Versioning generator + auto-tag + release skeleton (guard + version + concurrency) ✅ done, validated (v9.9.9 test tag)
2 iOS lane: match + gym + upload_to_testflight + staged deliver ✅ implemented (needs certs repo + ASC)
3 Android lane: supply → Play internal, on the attested AAB ✅ implemented (needs Play service account)
4 metadata (de-DE/en-US) + check-store-metadata.sh + store-metadata.yml ✅ scaffolded (real copy + legal URLs are FIXME-, gated by preflight)
5 Cleanup: remove placeholder custom_lane; remove legacy CircleCI / App Center ✅ done (both deleted; BlueWallet upstream had already removed them)

Superseded: the earlier push: develop → APK-artifact + TestFlight workflow (PR #180) was replaced by this tag-driven design. Its iOS fastlane scaffolding carries over.


Architecture

1. Versioning (single source of truth)

2. Trigger model (two lanes)

Day-to-day: how to ship a version

3. iOS lane (phase 2)

ios/fastlane/Fastfile beta lane:

  1. Signing via fastlane match (sync_code_signing, type appstore) — certs/profiles in a dedicated private repo (DFXswiss/btc-wallet-certificates); SSH deploy key + MATCH_PASSWORD.
  2. Marketing version + build number from the generator.
  3. gym → signed .ipa (Xcode workspace build of the BlueWallet prod scheme → .env.prd).
  4. upload_to_testflight (ASC API key), skip_waiting_for_build_processing: true.
  5. deliver (best-effort) stages listing texts + screenshots; Deliverfile: submit_for_review false, automatic_release false (a human clicks Submit). Best-effort because the first App Store version must be created once manually.

4. Android lane (phase 3)

android/fastlane/ (Fastfile + Appfile + credentials.json from a base64 secret) beta lane, on top of the existing signed/attested AAB:

  1. Build the signed AAB (reuse build-release-apk.sh + KEYSTORE_* / TRANSPARENCY_*).
  2. upload_to_play_store track internal (Internal Testing) — AAB + changelog, skip_upload_metadata/images/screenshots: true.
  3. A second upload_to_play_store pushing only metadata + images + screenshots (no changelog).

5. Metadata & screenshots (phase 4)


Secrets (CI)

Secret Platform Purpose
TAG_DEPLOY_KEY tagging SSH deploy key so auto-tag's push triggers release.yml
APP_STORE_CONNECT_KEY iOS App Store Connect API key (.p8 contents)
APP_STORE_CONNECT_KEY_ID iOS ASC API key ID
APP_STORE_CONNECT_ISSUER_ID iOS ASC API key issuer ID
MATCH_SSH_KEY iOS deploy key for the private match certs repo
MATCH_PASSWORD iOS match encryption passphrase
PLAY_STORE_JSON_BASE64 Android Play Console service-account JSON (base64)
KEYSTORE_* Android app signing (already present)
TRANSPARENCY_* Android code-transparency signing (already present)
SENTRY_AUTH_TOKEN both crash-symbol/sourcemap upload auth (see docs/release-pipeline-setup.md)
SENTRY_URL, SENTRY_ORG, SENTRY_PROJECT both not secrets, but required alongside the token — same doc

No secret values live in this repo — only names. Provision them in repo/org settings.

One-time console prerequisites (DFX)


Decisions (defaults from #178, professional/consistent)

  1. match certs repo: dedicated private repo DFXswiss/btc-wallet-certificates.
  2. Languages: trim to de-DE + en-US; drop unmaintained inherited locales.
  3. Android track: internal (Internal Testing) for the automated lane; production promotion manual.
  4. Screenshots: committed/versioned set in-repo for the first cut; automated capture deferred.

RN ≠ Flutter deltas (don't blindly copy RealUnit's Flutter commands)


Acceptance criteria (from #178)


Validation log