# 14 — Distribution and Native for Consumer Nostr Apps

*How a consumer Nostr app actually reaches a phone: Zapstore's permissionless Android store, a Capacitor wrap for native features without touching Swift or Kotlin, what Apple's Lockdown Mode silently breaks, and the standards gap — no Nostr NIP for push — that a PWA install papers over but doesn't solve.*

**Verified: 2026-07-10**

Confidence: High for Zapstore's publishing/verification mechanics (its own FAQ, publish guide, and OpenSats grant page, fetched directly) and for the absence of a ratified Nostr push NIP (the full NIP index scanned directly, plus the one serious prior attempt read in full). High for NIP-55's same-device signing model (spec fetched directly) and for the `capacitor`/`lockdown-mode` skill content (both `SKILL.md` files fetched directly). Medium for Ditto's current iOS distribution — its own install guide and a live App Store search result disagree, and this pass didn't reconcile them. Medium for the two Apple 2026 policy facts (each confirmed against a primary Apple page, but that's two targeted checks, not a guideline audit). Low for "no Nostr-specific App Store friction" as a general claim — Apple's Bitcoin/crypto-payment provisions (Guideline 3.1.5) weren't examined this pass.

---

## 1. The channel map

A consumer app built on MKStack ends up choosing among four channels: ship as a PWA (zero review, zero native code, but a background-push hole under one condition below), or wrap it in Capacitor for Google Play, Zapstore, and/or the App Store. They aren't mutually exclusive — one Capacitor-wrapped codebase produces both Android artifacts, and most teams ship the PWA on day one regardless of native plans.

```mermaid
flowchart TD
    A["New consumer Nostr app"] --> B{"Ship on iOS?"}
    B -->|"Not yet / MVP"| C["PWA: Safari Share -> Add to Home Screen\nWeb Push works normally\n(breaks entirely under Lockdown Mode, S5)"]
    B -->|"Yes, native"| D["Capacitor wrap -> App Store\niOS 26 SDK / Xcode 26 required since Apr 28 2026\n+ AI-consent screen if ai-chat skill used"]
    A --> E{"Ship on Android?"}
    E -->|"Fast iteration, signed releases, no review queue"| F["Zapstore: zsp publish\nfree, NIP-C1 cert-linked"]
    E -->|"Default-installed reach"| G["Google Play: standard review"]
    F -.->|"same APK, ship both"| G
    C --> H{"Need real background push?"}
    H -->|"Yes"| D
    H -->|"No - in-app / foreground is fine"| C
```

## 2. Zapstore — Android's permissionless catalog

Zapstore is **not a Soapbox product.** It's an independently OpenSats-funded project (10th Wave of Nostr Grants, announced 2025-03-27 [1]), founded by Franzap and incubated by And Other Stuff, with OpenSats and HRF named as donors [2]. Its own tagline states the design goal plainly: "You shouldn't need permission to use apps" [2]. Today it's Android-only — "Available for Android and more platforms soon," with iOS on the roadmap for Q3 2026 [2] — running 3,000+ listed apps and roughly 4,000 active users [2]. Ditto ships through it alongside Google Play and the web, as one Android option among several [2][5] — but it isn't Soapbox's channel; Soapbox is a customer of it, same as any other Android Nostr app (Agora is also Zapstore-only on Android, no iOS listing found — ch. 03).

Publishing is free, permissionless, and keypair-keyed [1][4]:

| Step | Mechanism |
|---|---|
| Install | `zsp` CLI (`go install github.com/zapstore/zsp@latest`) [4] |
| Configure | `zsp publish --wizard` walks source selection, metadata, signing, and writes `zapstore.yaml` to the repo — committed, not gitignored [4] |
| Sign | `SIGN_WITH` env var: `nsec1...`, a hex key, a `bunker://` NIP-46 URL, or `browser` (NIP-07) — bunker URLs as CI secrets are the recommended non-interactive path [4] |
| Link identity to build | First publish requires the actual Android signing keystore (`.jks`/`.p12`/`.pem`) to cryptographically link the APK's signing certificate to the Nostr pubkey — this is **NIP-C1**, certificate linking [4] |
| Get whitelisted | Wizard-written `zapstore.yaml` commits the pubkey to the repo; the relay fetches and verifies the match on first publish — or skip repo verification entirely if the npub already carries social reputation, via the Vertex system [4] |

No listing fee, no revenue share, no review queue — updates ship "as often as you like" [3]. Verification at install time stacks two independent checks, not one: a **Nostr-layer** check (signature on the catalog event, publisher identity) and an **Android-layer** check (the APK's actual signing certificate compared against the NIP-C1 declaration, enforced by Android's own OS-level cert-pinning at install time) [3]. The Nostr key signs *metadata* — explicitly not a substitute for the APK's own Android signing certificate [3].

**Ditto's own distribution is where this chapter corrects the manual's working assumption.** Soapbox's install guide confirms Android ships as a real native app via Google Play ("the standard way") and Zapstore, with the Android build carrying "full push notifications, secure credential storage, native file downloads" [5]. iOS, per that same guide, is **web-app-only today** — Safari's Share sheet → "Add to Home Screen" → a full-screen installed PWA with working push — with a native app explicitly framed as "in development," not shipped [5]. A live App Store search independently surfaced a listing, "Ditto: Fun Social Media" under Soapbox Technology LLC [6] — but the fetched page returned stale version metadata predating this year's relaunch, so it's unclear whether that's a dormant earlier build, a soft-launched beta, or a caching artifact. Treat iOS-native Ditto distribution as **unconfirmed**; verify live before promising a client an App Store listing exists.

## 3. Zapstore's neighbor: the App Store, mid-2026

No Nostr-specific App Store friction turned up in this pass — no rejection pattern, no guideline naming Nostr, zaps, or decentralized identity as a special case. What did turn up are two *generic* 2026 rules that apply to any MKStack-derived native build:

| Rule | Since | What it means for a Capacitor-wrapped MKStack app |
|---|---|---|
| **iOS 26 SDK / Xcode 26 mandatory** | 2026-04-28, already in effect [7] | Every new submission and every update to an existing app must be *built* with Xcode 26 against an iOS/iPadOS/tvOS/visionOS/watchOS 26 SDK — deployment target (oldest device supported) is unaffected, only the build toolchain. Apps built against the 26 SDK also inherit Apple's Liquid Glass UI treatment on native chrome by default [7]. |
| **Guideline 5.1.2(i): third-party AI data sharing** | Added 2025-11, still current | Verbatim: "You must clearly disclose where personal data will be shared with third parties, including with third-party AI, and obtain explicit permission before doing so" [8]. Any MKStack app wiring up the `ai-chat` skill (ch. 12, forthcoming) and sending user messages to an external LLM needs an explicit opt-in screen before that first request — a privacy-policy mention alone doesn't satisfy this. |

Confidence: Medium — both rules confirmed against Apple's own pages, but this is two targeted lookups, not a guideline audit; Bitcoin/crypto-specific provisions weren't checked.

## 4. Capacitor — native features without Swift or Kotlin

The `capacitor` MKStack skill wraps a web app into iOS and Android binaries, exposing native capability through helper functions the app calls unconditionally — on web they no-op or fall back, on native they hit the real API [9]:

| Web behavior | Capacitor-native behavior |
|---|---|
| Basic vibration | Haptics — taptic engine (iOS) / Android haptics [9] |
| Browser download | Write directly to the app's Documents directory [9] |
| `<a href>` to an external URL | Native share sheet [9] |
| `localStorage` | iOS Keychain / Android KeyStore, with automatic migration of any plaintext values already stored [9] |
| Client-side route change only | OS deep-link (app-open) events routed through React Router [9] |
| CSS-only notch handling | Safe-area Tailwind utilities for the actual notch/gesture-bar geometry [9] |

The design rule is explicit in the skill itself: helpers are "SSR-safe and web-safe — import and call unconditionally from shared components" [9] — there's no `if (native)` branching scattered through the app. Setup is mechanical: install the Capacitor runtime, plugins, and CLI; copy eight utility files plus one config file into the project; register the safe-area Tailwind plugin; call `bootstrapNative()` before React mounts; render `<DeepLinkHandler />` inside the router; then `npx cap add ios` / `npx cap add android` and build [9]. It's opt-in — "not included in the project by default" [9]. Agora and Armada's Android builds already run on Capacitor (ch. 02, ch. 03); this pass didn't independently confirm Ditto's own native build uses the same skill rather than a separate codebase.

## 5. Lockdown Mode — the sandbox gets smaller

Apple's Lockdown Mode (opt-in, iOS/iPadOS 16+, macOS Ventura+, watchOS 10+) restricts the exact WKWebView shell a Capacitor app — and any PWA — runs inside [10]. The restrictions aren't cosmetic:

| Blocked entirely | Still works |
|---|---|
| IndexedDB, Service Workers, Cache API, WebAssembly, Web Locks, WebRTC (critical/high) [10] | `localStorage`/`sessionStorage`, `fetch`/XHR, WebCrypto (`crypto.subtle`), Credential Management, cookies, inline PDF [10] |
| WebGL, `FileReader`, OPFS, `SharedArrayBuffer` (medium) [10] | `File` constructor, `URL.createObjectURL()` [10] |
| Notifications API, WebCodecs, Gamepad, Web Share, Speech Synthesis (low) [10] | — |

The skill's own recommendations: `localStorage` as the primary store, no hard IndexedDB dependency (in-memory fallback instead), pure-JS crypto (`@noble/secp256k1`) rather than WASM builds, WebGL treated as progressive enhancement only, and native Capacitor equivalents (`@capacitor/share`, `@capacitor/local-notifications`, `@capacitor/filesystem`) standing in for the blocked web APIs [10]. There is no official detection API for Lockdown Mode — the only way to know is to probe the disabled surfaces directly, and testing on a real Lockdown-enabled device beats trusting feature-detection alone [10].

This is exactly where MKStack's `NIndexedDB` — the persistent browser cache on the same `NStore` interface, covered in ch. 11 (Nostrify, forthcoming) — earns its keep: it's built directly on the API this table strips out, and it's designed to degrade gracefully when IndexedDB isn't there. The localStorage-first discipline recommended above *is* that degradation path, not a separate concern.

## 6. PWA install and push — a real gap, not a documentation gap

Installing a Nostr PWA is unremarkable: Safari's Share → "Add to Home Screen" produces a full-screen app with working push, confirmed directly on Ditto's own install guide [5]. The mechanism underneath is entirely generic — the Push API, the Notification API, and a Service Worker to receive events in the background. **No Nostr-specific push standard exists, and none of MKStack's 19 skills covers it** — confirmed by scanning the full NIP index directly: nothing numbered, lettered, or hex-coded addresses push or notification delivery [11].

One serious attempt exists, and it's instructive in exactly the way ch. 08's language-tag story is instructive:

| Proposal | Author | Status | What it would have added |
|---|---|---|---|
| **"Push Notification — Event Watcher API"** (PR #1528) | vitorpamplona (Amethyst) | **Closed, unmerged**, 2025-07-31 | A NIP-96-shaped registration spec for push-relay servers: POST interface, multi-account registration in one event, wrapped encryption to hide the destination pubkey, multi-server/relay/token support [12] |

It wasn't a paper proposal — it had "been running on Amethyst for the last year or so" before the PR was even opened [12]. It still didn't merge; the author's own closing note: "after seeing how well Pokey has been doing, I am not sure if this idea is worth pursuing anymore" [12]. Read plainly: **this is an ecosystem gap, and it's contribution-shaped** — the same class of opening as the es-i18n upstream play in ch. 08. A working reference implementation already ran in production; what's missing is someone carrying it (or a redesign) back through ratification, or an MKStack skill that at least standardizes the non-Nostr fallback so app teams stop re-solving it per app.

The Lockdown Mode interaction from §5 matters here directly: Web Push requires an active Service Worker to receive events in the background, and Service Workers sit in Lockdown Mode's blocked-entirely tier [10]. So the fallback isn't "PWA push degrades" — under Lockdown Mode it's **absent**, full stop, regardless of the Notification API's own lower severity rating there. The only remaining path for a Lockdown Mode user who needs real background push is a Capacitor-native build wired to `@capacitor/push-notifications` (APNs/FCM) — not `@capacitor/local-notifications`, which only schedules device-local notifications and doesn't solve remote delivery at all. Don't conflate the two when scoping a build.

## 7. Signer distribution for consumer onboarding

Ch. 06 already covers NIP-07 (browser extension) and NIP-46 (relay-based remote signing/bunker) as two of the three standards Soapbox names for signing without ever handling a raw nsec. **NIP-55 is the third, and it's new to this manual.** It solves a narrower problem than NIP-46: 2-way communication between an Android signer app and a Nostr client *on the same device*, via Android Intents (a visible approval popup, one per request) or a Content Resolver (silent, background, only for previously "remember my choice" grants) [13]. The spec is explicit about why you'd pick the other standard instead: "Consider using NIP-46 for web applications. With the approach here the web client can't call the signer in the background, so the user sees a popup for every request" [13] — NIP-55 is same-device-only and relay-free; NIP-46 is cross-device and relay-based. The spec text names no reference implementation, but the ecosystem's is unambiguous: Amber, whose author is widely credited with having written NIP-55 itself [14].

diVine (divine.video) — Vine's 6-second-loop format rebuilt on Nostr [15], independent of Soapbox, which has only written *about* it, not built it [17] — takes a third onboarding path worth contrasting: **Keycast**, a managed key-custody service offering OAuth2 login with NIP-46 remote signing underneath, keys encrypted server-side rather than held in a device app [16]. That's a materially different trust model from Amber's on-device custody — closer to "sign in with Google" UX at the cost of a custodian in the loop — and a legitimate pattern for a consumer app whose users won't install a second app just to hold a key.

| Standard | Custody | Device model | Best fit |
|---|---|---|---|
| NIP-07 | Browser extension storage | Single machine | Daily web use — ch. 06 |
| NIP-46 | Signer app/daemon/service | Cross-device, relay-based | Multi-device, team accounts, OAuth-style services like Keycast |
| NIP-55 | Signer app (e.g. Amber) | Same Android device only | Mobile-first, no relay dependency, visible per-request consent |

## 8. Nostr-native docs, dogfooded

Stacks' own onboarding practices what it sells: its Getting Started flow has a new user run `stacks add naddr1qvzqqqrhl5pzqprpljlvcnpnw3pejvkkhrc3y6wvmd7vjuad0fg2ud3dky66gaxaqqy8qeedwfjkcctehjfz9x` to pull in a starter template, then `stacks list` and `stacks mkstack` [18] — the first thing the platform teaches a new user is the exact kind-30717 template-publishing mechanism it runs on. The intended companion piece — NostrDeploy.com as the index/gateway for the nsite+Blossom deploy lane — is DNS-dead as of this manual's 2026-07-10 delta pass; full tripwire detail lives in [ch. 10](10-mkstack.md) and [ch. 04](04-shakespeare.md) and isn't re-litigated here.

## Open Questions

- Whether Ditto ships a native iOS app today or the App Store listing found is stale/dormant — the primary install guide and a live App Store search disagree; re-check before any client commitment.
- Whether Ditto's own native Android build actually uses the `capacitor` MKStack skill or a separate codebase — the feature list matches closely but wasn't independently confirmed.
- Whether PR #1528's design (or a successor) gets picked up by anyone in the MKStack/Soapbox orbit specifically — no roadmap signal found either way.
- Apple's Bitcoin/Lightning/zap-specific App Store provisions (Guideline 3.1.5 family) — out of scope this pass, worth a dedicated check before an app with native zaps ships to the App Store.

## Sources

1. [OpenSats — Zapstore](https://opensats.org/projects/zapstore) — 10th Wave of Nostr Grants (announced 2025-03-27), mission statement, ecosystem scope (relays, indexer, signing tools, `zapstore-cli`). Accessed 2026-07-10.
2. [zapstore.dev](https://zapstore.dev/) — homepage: tagline, founder (Franzap), incubator (And Other Stuff), donors (OpenSats, HRF), Android-only today / iOS Q3 2026 roadmap, 3,000+ apps / ~4,000 active users. Accessed 2026-07-10.
3. [zapstore.dev/docs/faq](https://zapstore.dev/docs/faq) — publishing model (free, permissionless, no fees/review queue), dual Nostr+Android verification model. Accessed 2026-07-10.
4. [zapstore.dev/docs/publish](https://zapstore.dev/docs/publish) and [zapstore.dev/docs/quickstart](https://zapstore.dev/docs/quickstart) — `zsp` CLI, `zapstore.yaml`, `SIGN_WITH` signing methods, NIP-C1 certificate linking, whitelisting/Vertex-reputation bypass. Quickstart returned mostly navigational content; mechanism detail sourced from the publish guide. Accessed 2026-07-10.
5. [soapbox.pub/blog/how-to-install-ditto-mobile](https://soapbox.pub/blog/how-to-install-ditto-mobile) — Android via Google Play + Zapstore with native-app feature list; iOS as PWA-only today with native "in development"; web at ditto.pub. Accessed 2026-07-10.
6. [Ditto: Fun Social Media — App Store](https://apps.apple.com/gb/app/ditto-fun-social-media/id6761851821) — live listing under Soapbox Technology LLC, but returned stale version metadata (2.23.4, dated 2024) inconsistent with the 2026 relaunch timeline; conflict flagged, not resolved. Accessed 2026-07-10.
7. [Apple Developer — Upcoming Requirements](https://developer.apple.com/news/upcoming-requirements/) — Xcode 26 / iOS 26 (+ iPadOS/tvOS/visionOS/watchOS 26) SDK requirement for all App Store Connect uploads, in effect since 2026-04-28; applies to build toolchain, not deployment target. Accessed 2026-07-10.
8. [TechCrunch — Apple's new App Review Guidelines clamp down on apps sharing personal data with 'third-party AI'](https://techcrunch.com/2025/11/13/apples-new-app-review-guidelines-clamp-down-on-apps-sharing-personal-data-with-third-party-ai/) — Guideline 5.1.2(i) verbatim text, third-party-AI-specific disclosure/consent requirement. Accessed 2026-07-10.
9. [gitlab.com/soapbox-pub/mkstack — `.agents/skills/capacitor/SKILL.md` (raw)](https://gitlab.com/soapbox-pub/mkstack/-/raw/main/.agents/skills/capacitor/SKILL.md) — full skill content: haptics, native downloads, share sheet, Keychain/KeyStore storage, deep linking, safe areas, setup steps, SSR/web-safe design rule. Accessed 2026-07-10.
10. [gitlab.com/soapbox-pub/mkstack — `.agents/skills/lockdown-mode/SKILL.md` (raw)](https://gitlab.com/soapbox-pub/mkstack/-/raw/main/.agents/skills/lockdown-mode/SKILL.md) — full skill content: platform availability, blocked/available API tables, storage/crypto/media recommendations, native-alternative mapping, no official detection API. Accessed 2026-07-10.
11. [github.com/nostr-protocol/nips — README (NIP index)](https://github.com/nostr-protocol/nips/blob/master/README.md) — full index scanned directly; no NIP addresses push notifications or web/mobile push delivery. Accessed 2026-07-10.
12. [github.com/nostr-protocol/nips — PR #1528, "Push Notification - Event Watcher API"](https://github.com/nostr-protocol/nips/pull/1528) — author vitorpamplona, closed unmerged 2025-07-31, in production on Amethyst for roughly a year before submission, verbatim closing quote. Accessed 2026-07-10.
13. [github.com/nostr-protocol/nips — 55.md](https://github.com/nostr-protocol/nips/blob/master/55.md) — full NIP-55 spec text: Intent/Content-Resolver mechanism, same-device scope, verbatim comparison to NIP-46. Accessed 2026-07-10.
14. [github.com/greenart7c3/Amber](https://github.com/greenart7c3/Amber) — Android NIP-55 signer app, corroborating community sources (reviews, client lists) crediting its author with authoring NIP-55. Accessed 2026-07-10 (also cited in ch. 06 as source 13).
15. [divine.video](https://divine.video/) and [about.divine.video/faqs](https://about.divine.video/faqs/) — diVine product description (Nostr-native short-form video), independence from Soapbox. Accessed 2026-07-10.
16. [github.com/divinevideo/keycast](https://github.com/divinevideo/keycast) — Keycast managed key-custody service: OAuth2 login, server-encrypted keys, NIP-46 bunker / HTTP RPC signing. Accessed 2026-07-10.
17. [soapbox.pub/blog/what-is-nostr-divine](https://soapbox.pub/blog/what-is-nostr-divine/) — Soapbox explainer post about diVine, confirming an editorial rather than ownership relationship. Accessed 2026-07-10.
18. [getstacks.dev](https://getstacks.dev/) — Getting Started flow (`stacks add naddr1...` → `stacks list` → `stacks mkstack`); homepage is a client-rendered SPA (confirmed via direct fetch of an empty `#root` shell), so the exact command was recovered via a search-index cache rather than a direct page render. Accessed 2026-07-10.
