Files
buzz/mobile
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell 0438c0ef63 feat(mobile/ios): NIP-PL installation lifecycle state machine + Keychain store
Implements the B2/B4 client-side lifecycle rules from the iOS cold review
as a pure decision machine plus a Keychain persistence seam, all in
BuzzPushKit and fully unit-tested without App Attest/network:

- BuzzPushInstallationState: durable installation record with a two-phase
  pendingRotation intent and local APNs-token fingerprinting (SHA-256 of
  the lowercase-hex token), so change detection never retains the token.
- BuzzPushLifecycle.onDeviceToken: rotate ONLY on a real token fingerprint
  change (iOS re-delivers the token every launch; unconditional rotate
  bumps the server epoch and kills every outstanding relay delegation);
  proactive re-enroll inside a 7-day renewal window before expires_at;
  surviving intents resume instead of desyncing.
- BuzzPushLifecycle.applyRotate: persist-then-confirm epoch commit;
  transient retains the intent; invalid_attestation (zombie key after
  backup-restore) clears and re-enrolls; ambiguous not_authorized after a
  crash window adopts the pending epoch and escalates once before giving
  up — deterministic reconvergence without reading server state.
- KeychainPushStateStore: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
  (NSE must read while locked; ThisDeviceOnly excludes the record from
  backup-restore, killing the zombie-installation class at the storage
  layer) with optional access group for the extension; versioned JSON
  envelope that fails closed to 'corrupt' instead of crashing.

swift test: 32/32 green (10 transcript vectors + 22 lifecycle/store).

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-15 18:35:57 -04:00
..
…

Buzz Mobile

Flutter mobile client for Buzz.

Setup

cd mobile
flutter pub get

Run

# From repo root (recommended — starts Docker, relay, and simulator):
just mobile-dev

# Direct (requires services and relay already running):
cd mobile && flutter run

Checks

dart format --output=none --set-exit-if-changed .
flutter analyze
flutter test

Or from the repo root: just mobile-check and just mobile-test.

Android release signing

Android release builds fail unless all upload-key inputs are supplied through the environment:

  • BUZZ_ANDROID_UPLOAD_KEYSTORE_PATH: path to a CI-vended keystore file
  • BUZZ_ANDROID_UPLOAD_KEYSTORE_PASSWORD
  • BUZZ_ANDROID_UPLOAD_KEY_ALIAS
  • BUZZ_ANDROID_UPLOAD_KEY_PASSWORD

The keystore path must be absolute, and the keystore must remain outside the repository. Development and debug builds do not require these variables.

Architecture

lib/
├── main.dart              # Entry point, Riverpod bootstrap
├── app.dart               # MaterialApp with theme
├── shared/
│   └── theme/             # Catppuccin light/dark, spacing tokens, extensions
└── features/
    └── home/              # Placeholder home surface
  • State management: Riverpod + Hooks (HookConsumerWidget)
  • Theme: Catppuccin Latte (light) / Macchiato (dark) — matches desktop
  • Spacing: Grid tokens for consistent spacing
  • Linting: flutter_lints + riverpod_lint via custom_lint
  • Feature isolation: No cross-feature imports except shared/