> For the complete documentation index, see [llms.txt](https://basedapp.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://basedapp.gitbook.io/docs/integrations/mini-apps-platform/02-architecture.md).

# Architecture

## Layer model

The mini-app system is split into six layers, top to bottom. Each layer depends only on layers below it.

| Layer                  | Package                       | Purpose                                                          |
| ---------------------- | ----------------------------- | ---------------------------------------------------------------- |
| **L6 — Mini-App Code** | partner-owned                 | The JS bundle the partner ships. Calls `based.*`.                |
| **L5 — JS Runtime**    | `based_js_runtime`            | Wraps QuickJS, owns isolates, lifecycle, the `based.*` global.   |
| **L4 — Bridge**        | `based_js_runtime` (internal) | Marshals calls between Dart and JS. Codegen'd from L3.           |
| **L3 — Host Contract** | `based_mini_app_sdk`          | `BasedMiniAppHost` Dart interface + permission enforcer.         |
| **L2 — Host Services** | `based_core`, host app        | Auth, signing, http, storage, nav. The host's existing services. |
| **L1 — UI Library**    | `based_ui`                    | Shared Flutter widgets used to render `UiNode` trees.            |

Layer L4 is mostly invisible to humans — it's generated. Layer L3 is the doc the platform team maintains most carefully; it is the contract.

## Package layout

The host app and SDK packages live in a melos workspace under `flutter/`:

```
flutter/
├── melos.yaml
├── based_app/                 # the host app binary (today's flutter/based_app/)
├── packages/
│   ├── based_ui/              # widgets + theme  (extracted from presentation/widgets/common)
│   ├── based_core/            # shared services (auth, signing, http, storage)
│   ├── based_mini_app_sdk/    # host contract, manifest parser, permission enforcer
│   ├── based_js_runtime/      # QuickJS wrapper, bridge, lifecycle
│   └── based_ui_nodes/        # UiNode tree shape + renderer (uses based_ui)
└── tools/
    └── bridge_codegen/        # build_runner-based generator for L4
```

Partner mini-app code lives outside this repo entirely.

## Data flow — cold boot of a mini-app

Numbered steps; followed by sequence diagram.

1. User taps a mini-app entry (deep link, home screen tile, search result).
2. Host's `MiniAppLauncher` resolves the mini-app id → checks local cache → if missing, fetches bundle + manifest from CDN.
3. Manifest is validated (schema, signature, `minHostVersion`, permissions).
4. A new `MiniAppRuntime` instance is created with a fresh QuickJS isolate.
5. The bridge is installed: `based.*` becomes available to the JS context.
6. `main.js` is evaluated. The default export is a function `App(ctx)`.
7. `App(ctx)` returns a `UiNode` (synchronously or via a Promise).
8. The host's `UiNodeRenderer` mounts the node into the mini-app container `Widget`.
9. As the user interacts, events propagate JS → Dart through the bridge; state mutations trigger re-renders.

```mermaid
sequenceDiagram
    participant U as User
    participant H as Host
    participant C as Cache/CDN
    participant R as Runtime
    participant B as JS Bundle
    U->>H: tap
    H->>C: lookup
    C-->>H: miss
    H->>C: fetch
    C-->>H: bundle + signature
    Note over H: verify, parse manifest
    H->>R: boot
    Note over R: install bridge
    R->>B: eval main.js
    B-->>R: UiNode
    R-->>H: mount widget
    H-->>U: render
```

## Data flow — a `based.signing.signEvm()` call

This is the canonical "sensitive native call" flow. Every native capability with user-visible effect follows the same shape.

```mermaid
sequenceDiagram
    participant M as Mini-app (JS)
    participant Br as Bridge
    participant H as Host (Dart)
    participant S as ActiveSignerService
    participant U as User
    M->>Br: signEvm(req, { reason })
    Br->>H: permission check<br/>(manifest.permissions.signing.evm?)
    H->>U: host-rendered confirmation sheet<br/>(name, icon, reason, decoded tx)
    U-->>H: approve
    H->>S: sign(req)
    S-->>H: signed payload
    H-->>Br: resolve
    Br-->>M: signed payload (Promise resolves)
    Note over M: broadcast via based.http.post(...)<br/>(also bridge + permission checked)
```

Critical property: **at no point does the mini-app see a private key, a mnemonic, or a raw signer object.** Even if the JS context is compromised, it can only request signatures, not produce them.

## Threading and concurrency

| Concern            | Decision                                                                                                       |
| ------------------ | -------------------------------------------------------------------------------------------------------------- |
| JS engine isolate  | Runs on a **dedicated background isolate** (Dart isolate, not just a thread). One per active mini-app.         |
| Bridge transport   | Dart `SendPort` / `ReceivePort` between host UI isolate and mini-app isolate. Messages are JSON-serializable.  |
| UI rendering       | Host UI isolate. The renderer subscribes to `UiNode` updates from the bridge.                                  |
| Multiple mini-apps | At most one **foreground** mini-app at a time. Background mini-apps are paused (isolate frozen, see L5).       |
| Async API surface  | Every `based.*` method returns a Promise on the JS side, regardless of whether the Dart side is sync or async. |

This avoids the JS engine ever blocking the host's UI thread, and the isolate-per-mini-app model gives us a clean kill switch.

## Failure model

What happens when something goes wrong, by category:

| Failure                                     | What the host does                                                                                              |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Manifest fails validation                   | Refuse to launch. Show host-rendered error sheet. Log.                                                          |
| Bundle signature invalid                    | Refuse to launch. Treat as security incident.                                                                   |
| `minHostVersion` exceeds host version       | Show "Update Based to use this mini-app" prompt.                                                                |
| JS exception during boot                    | Show host-rendered error UI with mini-app id, version, brief message. Telemetry.                                |
| JS exception during interaction             | Caught at the bridge boundary. Mini-app sees a rejected Promise. Host renders a non-blocking toast if uncaught. |
| Memory limit exceeded                       | Isolate is killed. User sees host-rendered "this mini-app crashed" screen with a Retry button.                  |
| Permission violation (e.g. unlisted domain) | Bridge rejects the call with a typed error. Mini-app sees the rejection. Telemetry.                             |
| Bundle hot-update available                 | Downloaded in background. Applied at next cold boot, never mid-session.                                         |

## Versioning

Three versions matter:

* **Host version** — the Based app binary version (semver).
* **SDK version** — `based_mini_app_sdk` version. Compiled into the host. The host advertises it via `based.host.sdkVersion`.
* **Bundle version** — set by the partner in `based.json`.

A mini-app declares `minHostVersion` and `targetSdkVersion` in its manifest. The host refuses to run mini-apps whose `minHostVersion` exceeds the host's version. The bridge tolerates `targetSdkVersion < currentSdkVersion` by maintaining shims for deprecated APIs (with a deprecation telemetry event).

API removal is a multi-quarter process documented in `03-host-contract.md`.

## Cross-cutting decisions made here

These are referenced by later docs:

* **A1.** Each mini-app gets its own Dart isolate. (Not just a JS context — a real isolate.)
* **A2.** No webview. No DOM. The renderer is a Dart widget tree driven by `UiNode`.
* **A3.** All native capability is mediated through `BasedMiniAppHost`. There is no out-of-band path to native functionality.
* **A4.** The `based.*` global is **codegen'd** from `BasedMiniAppHost`. The bridge is not hand-written.
* **A5.** **Based-tenant only.** The mini-app platform is gated at runtime to `tenantId == "based"`. Other tenants (Hyena, future) do not host mini-apps. Mini-apps still see the Based theme variants (dark/light) via `based.theme`; only allowlisted theme tokens are overridable.
* **A6.** **Wallet rotation = hard restart.** When the user changes wallet, any running mini-app is silently killed and the user is returned to the host. There is no pause/resume across wallet identity changes; mini-app state is discarded. (Q2)

Read [`03-host-contract.md`](/docs/integrations/mini-apps-platform/03-host-contract.md) next.
