> 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/03-host-contract.md).

# Host contract

The **host contract** is the Dart interface that defines every native capability a mini-app can request. It is the single source of truth for what `based.*` exposes; the JS bridge is generated from it.

This document is normative. Adding, removing, or changing a method here is a platform-level change that requires a versioning decision.

## Design rules

These rules apply to every method on `BasedMiniAppHost`:

1. **Permission-gated.** Every method declares which manifest permission it requires via a `@RequiresPermission` annotation. The bridge enforces this; implementations may double-check.
2. **No raw secrets.** Methods never expose private keys, mnemonics, full session tokens, or signer objects. They expose *operations* on those secrets.
3. **JSON-serializable arguments and returns.** Anything crossing the bridge must round-trip through JSON. Custom types must declare `fromJson` / `toJson`.
4. **Async by default.** Every method returns `Future<T>` on the Dart side, `Promise<T>` on the JS side. Even if the implementation is synchronous, the bridge enforces async semantics for forward compatibility.
5. **User-visible side effects require user confirmation.** Any method that signs, pays, posts, or persists user-visible state shows a host-controlled confirmation sheet before completing.
6. **Errors are typed.** Methods throw `MiniAppHostException` subclasses, which serialize across the bridge with a stable `code` string. JS sees `error.code === 'permission_denied'`, etc.

## Top-level shape

```dart
abstract class BasedMiniAppHost {
  BasedHostInfo  get host;       // host metadata: tenant, sdkVersion, locale, preferredChain
  BasedAuth      get auth;       // identity, accounts
  BasedSigning   get signing;    // signing operations (no key access)
  BasedTx        get tx;         // sign + broadcast in one call (host-routed)
  BasedHttp      get http;       // network, scope-checked
  BasedStorage   get storage;    // sandboxed key-value
  BasedNav       get nav;        // open route, close mini-app, deep-link
  BasedUI        get ui;         // host-rendered toast, sheet, confirm, haptics
  BasedAnalytics get analytics;  // telemetry
  BasedTheme     get theme;      // tenant theme tokens (read-only for mini-apps)
  BasedClipboard get clipboard;  // gated copy/paste
  BasedGyro      get gyro;       // inertial sensors (pull-based)
  BasedNotifications get notifications; // host-rendered banner + lock-screen entry
  BasedLiveActivity  get liveActivity;  // iOS Live Activities + Android Live Updates
}
```

Each sub-interface is described below. Method signatures use Dart conventions; JS equivalents are `camelCase` and Promise-based.

## `BasedHostInfo` — host metadata

Read-only.

```dart
abstract class BasedHostInfo {
  String get sdkVersion;        // e.g. "1.0.0"
  String get hostVersion;       // e.g. "2.5.3"
  String get tenantId;          // always "based" — platform is Based-only
  String get platform;          // "ios" | "android"
  Locale get locale;            // device locale, IETF tag
  Chain  get preferredChain;    // user's preferred chain at host level (read-only)
}
```

Permissions: none. Always available.

## `BasedAuth` — identity

```dart
abstract class BasedAuth {
  /// Returns a stable, mini-app-scoped pseudonymous user id.
  /// Different mini-apps see different ids for the same user (privacy).
  @RequiresPermission('auth.userId')
  Future<String> getUserId();

  /// Public addresses the user has authorized for this mini-app.
  /// Mini-app must declare which chains it wants in manifest.permissions.auth.
  @RequiresPermission('auth.addresses')
  Future<List<WalletAddress>> getAddresses({Set<Chain> chains});

  /// One-shot SIWE-style proof-of-ownership for a specific chain.
  /// Used by partner backends to verify identity without trusting the client.
  @RequiresPermission('auth.proof')
  Future<SignedProof> proveOwnership({
    required Chain chain,
    required String nonce,
    required String domain,
  });
}
```

Notes:

* `getUserId()` returns a **per-mini-app pseudonymous id**, not the global user id. This prevents cross-mini-app correlation. Implementation: `HMAC(globalUserId, miniAppId)`.
* Public addresses only — never private keys.
* `proveOwnership()` is the SIWE flow but mediated by the host. Mini-app gets a signed payload it can ship to its backend.

## `BasedSigning` — signing operations

This is the most security-sensitive surface. Every method shows a host-rendered confirmation sheet before completing.

```dart
abstract class BasedSigning {
  @RequiresPermission('signing.evm')
  Future<SignedTx> signEvm(
    EvmTxRequest req, {
    required String reason,
  });

  @RequiresPermission('signing.evm')
  Future<String> signEvmTypedData(
    EvmTypedData typedData, {
    required String reason,
  });

  @RequiresPermission('signing.hl')
  Future<SignedHlOrder> signHlOrder(
    HlOrderRequest req, {
    required String reason,
  });

  @RequiresPermission('signing.solana')
  Future<SignedSolanaTx> signSolana(
    SolanaTxRequest req, {
    required String reason,
  });

  @RequiresPermission('signing.message')
  Future<String> signMessage({
    required Chain chain,
    required String message,
    required String reason,
  });
}
```

Confirmation sheet contract:

* Sheet is rendered by the host using `based_ui` widgets — never by mini-app code.
* Sheet shows: mini-app `name` and `icon` (from manifest), the `reason` string, decoded transaction, gas / network fee, the destination address, and a clear "Approve" / "Reject" pair.
* The throttle on agent approval (10-second, see `TradingAgentService.canApproveAgent` in CLAUDE.md) applies to mini-app signing too.

Decoding: `EvmTxRequest` includes ABI metadata when known (we maintain a small registry of common contracts: ERC20, ERC721, common DEX routers). Unknown calls fall back to "raw call to `<address>`" with hex data — and the sheet warns the user explicitly.

## `BasedTx` — sign + broadcast in one call

Convenience surface that combines signing and broadcasting. Same security guarantees as `BasedSigning` (host-rendered confirmation sheet, no key exposure), with the host owning the broadcast path.

```dart
abstract class BasedTx {
  /// Sign and broadcast an EVM transaction.
  /// Host picks the default RPC for the chain.
  /// `relay` is optional and must be in `permissions.network` if provided.
  @RequiresPermission('signing.evm')
  Future<TxResult> sendEvm(
    EvmTxRequest req, {
    required String reason,
    String? relay,
  });

  /// Sign and broadcast a Solana transaction.
  @RequiresPermission('signing.solana')
  Future<TxResult> sendSolana(
    SolanaTxRequest req, {
    required String reason,
    String? relay,
  });
}

class TxResult {
  final String hash;
  final String? explorerUrl;
}
```

Broadcast routing rules:

* The host maintains a default RPC per chain. Partners do not need to specify one.
* If `relay` is provided, the URL's host must be in `manifest.permissions.network`. Otherwise the call rejects with `code: "permission_denied"`.
* `BasedTx` does **not** wait for inclusion — it returns the tx hash on broadcast acceptance. Partners poll for confirmation via their own backend or via `based.http.*`.
* HL orders use `BasedSigning.signHlOrder` + partner relay (HL has its own placement semantics); `BasedTx` is EVM/Solana only in v1.

## `BasedHttp` — network access

```dart
abstract class BasedHttp {
  @RequiresPermission('network')
  Future<HttpResponse> get(String url, {Map<String, String>? headers});

  @RequiresPermission('network')
  Future<HttpResponse> post(
    String url, {
    Object? body,
    Map<String, String>? headers,
  });

  // Restricted set of methods. No PUT/DELETE/PATCH initially.
}
```

Enforcement:

* The URL is parsed; the host extracts the host (domain).
* The domain must match an entry in `manifest.permissions.network` (exact match or `*.example.com` wildcard).
* Cookies are **not** sent. Each mini-app's HTTP client is cookie-jar-isolated.
* Headers are filtered: `Authorization`, `Cookie`, `X-Based-*` are stripped before send unless explicitly granted (`network.auth-headers` permission).
* Response body is capped (default 10MB; configurable).

## `BasedStorage` — sandboxed key-value

```dart
abstract class BasedStorage {
  @RequiresPermission('storage')
  Future<String?> get(String key);

  @RequiresPermission('storage')
  Future<void> set(String key, String value);

  @RequiresPermission('storage')
  Future<void> remove(String key);

  @RequiresPermission('storage')
  Future<List<String>> keys();

  @RequiresPermission('storage')
  Future<void> clear();
}
```

Implementation: a single MMKV instance per mini-app id, namespaced. Quota declared in manifest (`permissions.storage: "5MB"`). The host enforces the quota and rejects writes that would exceed it.

No structured types — values are strings. Mini-apps serialize their own JSON.

## `BasedNav` — navigation

```dart
abstract class BasedNav {
  /// Push a host-managed deep route. The route must be in the mini-app's
  /// declared `permissions.deepLinks` list, OR be one of the
  /// safe public routes (e.g. "/marketplace", "/discover").
  @RequiresPermission('nav.deepLink')
  Future<void> openDeepLink(String url);

  /// Close the mini-app and return to the host.
  Future<void> close({Map<String, dynamic>? result});

  /// Open another mini-app by id, optionally with params.
  /// Requires `nav.openMiniApp` permission *and* the target mini-app's id
  /// in `permissions.openMiniApps`.
  @RequiresPermission('nav.openMiniApp')
  Future<Map<String, dynamic>?> openMiniApp(
    String miniAppId, {
    Map<String, dynamic>? params,
  });
}
```

`close()` always works without permission — the user must always be able to leave a mini-app.

## `BasedUI` — host-rendered UI affordances

These are the "system-style" UI elements that the host renders on behalf of the mini-app, separate from the mini-app's own UI tree.

```dart
abstract class BasedUI {
  Future<void> toast(String message, {ToastVariant variant});

  Future<bool> confirm({
    required String title,
    String? body,
    String confirmLabel = 'Confirm',
    String cancelLabel = 'Cancel',
    bool destructive = false,
  });

  Future<String?> prompt({
    required String title,
    String? body,
    String? placeholder,
  });

  Future<void> haptic(HapticPattern pattern);
}
```

Permissions: none for these. They're considered low-risk and improve UX even without explicit grant.

## `BasedAnalytics` — telemetry

```dart
abstract class BasedAnalytics {
  /// Log an event. Mini-app id and version are auto-attached.
  /// Property values are coerced to strings; PII is rejected by name match.
  Future<void> track(String event, {Map<String, Object?>? properties});
}
```

No permission required. The host decides whether and where to forward events. Mini-apps cannot read analytics, only write.

## `BasedTheme` — theme tokens (read)

```dart
abstract class BasedTheme {
  /// All tenant theme colors as a flat map of token → hex string.
  /// Includes accent, background, surface, text, success, error, warning, etc.
  Map<String, String> get colors;

  /// Design system tokens: spacing, fontSize, borderRadius.
  Map<String, double> get tokens;

  /// True if dark mode is active.
  bool get isDark;

  /// Current locale (mirrors host.locale).
  Locale get locale;
}
```

Mini-apps **cannot** modify the active theme. They may declare static theme overrides in `manifest.theme.overrides`, applied only within the mini-app's own UI tree, and only for the allowlisted token set (see `04-manifest.md`).

## `BasedClipboard` — gated clipboard

```dart
abstract class BasedClipboard {
  @RequiresPermission('clipboard.read')
  Future<String?> read();

  @RequiresPermission('clipboard.write')
  Future<void> write(String text);
}
```

Clipboard reads always show a host-rendered toast ("MiniApp X read clipboard") so the user is aware. Some platforms enforce this natively (iOS) but we do it ourselves for consistency.

## `BasedGyro` — inertial sensors (pull-based)

```dart
abstract class BasedGyro {
  @RequiresPermission('gyro.read')
  Future<GyroSample> read();
}

class GyroSample {
  final SensorVector? accelerometer; // m/s², gravity included
  final SensorVector? gyroscope;     // rad/s
  final int timestampMs;
}

class SensorVector { final double x, y, z; }
```

One-shot read of the device's inertial sensors. The first call kicks off the host's sensor subscription; subsequent calls return cached values updated at the platform's native rate. Mini-apps that want continuous tilt-driven UI poll on a JS `setInterval` (30 Hz is plenty); the host streaming layer is owned by the host, not the call.

Either vector is `null` on platforms that don't expose the sensor (rare on phones, common on desktop).

The native `<CardTilt />` UiNode reads sensors directly inside the renderer via `package:sensors_plus` and does **not** require this permission — it lives entirely in the host process. Only direct `based.gyro.read()` calls from JS are gated.

## `BasedNotifications` — local system notifications

```dart
abstract class BasedNotifications {
  @RequiresPermission('notifications.local')
  Future<void> show({
    required String title,
    required String body,
    String? deepLink,
    Map<String, Object?>? data,
  });
}
```

Shows a host-rendered system notification (banner + lock-screen entry) in response to events the mini-app generates synchronously. Three guarantees the host enforces (mini-app code cannot bypass any of them):

1. **Brand prefix.** Title is auto-prepended with `[<displayName>]` from the manifest. `show({title: "Pull complete"})` renders as `[Pull] Pull complete`.
2. **Icon.** Pulled from the manifest registry — partner cannot supply a custom icon. Prevents a partner from spoofing other mini-apps or first-party host UI on the lock screen.
3. **Deep link.** Gated against `permissions.deepLinks`. URLs outside the allowlist are dropped silently rather than passed to the OS.

Use this for events that fire while the user is in another tab or has the app backgrounded. For events that need to be delivered when the app is fully closed, partners use the gateway push API (see [`17-partner-api.md`](/docs/integrations/mini-apps-platform/17-partner-api.md)) — that path has the same brand prefix and icon enforcement plus rate limiting.

## `BasedLiveActivity` — iOS Live Activities + Android Live Updates

```dart
abstract class BasedLiveActivity {
  @RequiresPermission('liveActivity')
  Future<Map<String, Object?>> start({
    required String template,        // 'progress' | 'status' | 'countdown'
    required String title,
    String? subtitle,
    required Map<String, Object?> state,
    int? ttlSeconds,                 // default 1800; max 28800 (Apple cap)
  });

  @RequiresPermission('liveActivity')
  Future<void> update({
    required String activityId,
    required Map<String, Object?> state,
  });

  @RequiresPermission('liveActivity')
  Future<void> end({
    required String activityId,
    Map<String, Object?>? finalState,
  });
}
```

iOS Live Activities (lock-screen + Dynamic Island cards) and the Android equivalent (high-priority progress notifications, rendered via `Notification.ProgressStyle` on Android 16+, falls back to a standard ongoing notification with progress bar otherwise). Same JS API on both platforms; the host picks the rendering.

**Templates.** Three are compiled into the host's iOS Widget Extension and Android renderer. Partners pick a template and supply a state shape; they cannot ship custom SwiftUI/Compose.

| Template    | State shape                                                                                   | Use case                                                             |
| ----------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `progress`  | `{ stages: string[], currentStage: number, progress: number, statusText? }`                   | Multi-step flows: deposit confirming, mint progressing, pack opening |
| `status`    | `{ statusLabel: string, statusVariant: 'pending'\|'success'\|'warning'\|'error', footnote? }` | Single-state pill (Pending → Confirmed → Failed)                     |
| `countdown` | `{ deadline: string (ISO 8601), footnote? }`                                                  | Pack drops, auctions, sale windows                                   |

The fixed catalogue is what makes the threat model tractable — a partner can't visually impersonate the host or other mini-apps on the user's lock screen. The brand prefix from `displayName` and the manifest icon are still enforced.

**Background updates.** Foreground updates from inside the bundle flow through `update()`. Updates while the app is closed flow through the partner's backend hitting the gateway — see the push-to-LA / FCM data-message flow in [`17-partner-api.md`](/docs/integrations/mini-apps-platform/17-partner-api.md). The mini-app SDK does NOT see push tokens; the host registers them with the gateway on behalf of the user.

## Permission catalog

Full set of permission strings, in canonical order. The manifest declares which subset a mini-app needs.

| Permission             | Grants                                                                                                                                                                                   |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auth.userId`          | `getUserId()`                                                                                                                                                                            |
| `auth.addresses`       | `getAddresses(chains)`                                                                                                                                                                   |
| `auth.proof`           | `proveOwnership(...)`                                                                                                                                                                    |
| `signing.evm`          | `signEvm`, `signEvmTypedData`                                                                                                                                                            |
| `signing.hl`           | `signHlOrder`                                                                                                                                                                            |
| `signing.solana`       | `signSolana`                                                                                                                                                                             |
| `signing.message`      | `signMessage`                                                                                                                                                                            |
| `network`              | `http.get`, `http.post` (against allowlisted domains)                                                                                                                                    |
| `network.auth-headers` | sending `Authorization`-class headers                                                                                                                                                    |
| `storage`              | `storage.*`                                                                                                                                                                              |
| `nav.deepLink`         | `nav.openDeepLink` (against allowlisted routes)                                                                                                                                          |
| `nav.openMiniApp`      | `nav.openMiniApp` (against allowlisted target ids)                                                                                                                                       |
| `clipboard.read`       | `clipboard.read`                                                                                                                                                                         |
| `clipboard.write`      | `clipboard.write`                                                                                                                                                                        |
| `gyro.read`            | `gyro.read` (does NOT gate the `<CardTilt />` UiNode)                                                                                                                                    |
| `notifications.local`  | `notifications.show` (host-rendered banner; brand prefix + icon enforced)                                                                                                                |
| `liveActivity`         | `liveActivity.start`, `liveActivity.update`, `liveActivity.end` (templated lock-screen card; APNs push-to-LA + Android FCM data messages are mediated by the gateway, not visible to JS) |

See [`04-manifest.md`](/docs/integrations/mini-apps-platform/04-manifest.md) for how these are declared.

## Versioning policy

The contract is semver'd at the package level (`based_mini_app_sdk@1.0.0`).

* **Patch** (1.0.0 → 1.0.1): bug fixes, no API change.
* **Minor** (1.0.0 → 1.1.0): additive only — new methods, new optional parameters. Mini-apps targeting older minor versions keep working.
* **Major** (1.0.0 → 2.0.0): breaking changes. Requires:
  * 2-quarter deprecation notice in writing to known partners.
  * Both versions of the API live simultaneously in the bridge for 1 quarter.
  * Telemetry on usage of deprecated methods to know when removal is safe.

A mini-app's `targetSdkVersion` is the SDK major.minor it was tested against. The bridge runs in **compatibility mode** for older targets, applying shims for renamed/removed methods where possible.

Read [`04-manifest.md`](/docs/integrations/mini-apps-platform/04-manifest.md) next.
