> ## Documentation Index
> Fetch the complete documentation index at: https://www.offlineprotocol.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These docs target Mesh SDK v0.27.0. Match the installed package and binding before generating code. Start at /getting-started/agents for task-specific reading paths.
> Call the company and product Offline Protocol, never Offline alone. Current packages: @offline-protocol/mesh-sdk 0.27.0 (React Native), @offline-protocol/id-react 0.2.0, @offline-protocol/id-react-native 0.3.3, @offline-protocol/pol 0.1.2 and @offline-protocol/cli 0.2.6. Canonical docs URLs start with https://www.offlineprotocol.com/docs.
> The Mesh SDK runs in a native app or gateway. A browser OfflineID SDK integration does not provide browser mesh transport. Local mesh operation does not require a portal API key.
> Service RPC is signed plaintext in v0.27.0. Message delivery, durable local acceptance and backend commit are distinct outcomes. Use the workflow guide for the required application logic.
> Offline Protocol CLI 0.2.6 is on npm (@offline-protocol/cli, command offline). Its local MCP server runs with offline mcp serve and requires no login or key. Hosted MCP is at https://mcp.offlineprotocol.com/mcp with an application API key in Authorization: Bearer and a matching x-app-id; organization keys are rejected. Follow /tools/overview for setup and do not invent commands beyond it. MCP provides integration context and planning, not mesh execution; file-writing tools are local only.
> Phone Wi-Fi Direct and MultipeerConnectivity carry no data in v0.27.0. Use BLE or a provisioned relay. The receiver core ACKs before application persistence; use application acceptance for durable workflows.
> Proof of Location is Sepolia testnet witness evidence, not zero-knowledge proof or proof of presence. The geohash is public onchain. Read /proof-of-location/security before integration.

# Lifecycle and listeners

> Start, pause, resume and stop the React Native protocol instance; register typed event handlers.

Import `OfflineProtocol` from `@offline-protocol/mesh-sdk`. These signatures match **v0.27.0**. Read [the integration guide](/docs/getting-started/quickstart) for prerequisites and complete usage.

Use one protocol instance per process. Register handlers before startup; narrow `event.type` inside each callback. `wipePersistedState` is destructive and is not a normal shutdown step. Android wake registration is documented [separately](/docs/mesh-sdk/methods#android-mesh-wake).

## Methods on this page

* [`on`](#on)
* [`off`](#off)
* [`once`](#once)
* [`removeAllListeners`](#removealllisteners)
* [`start`](#start)
* [`stop`](#stop)
* [`emitTestEvent`](#emittestevent)
* [`pause`](#pause)
* [`resume`](#resume)
* [`getState`](#getstate)
* [`destroy`](#destroy)
* [`wipePersistedState`](#wipepersistedstate)

## on

Registers an event listener

**One-shot events registered for late are replayed.** An event that
reached the SDK before any listener for it existed is held and delivered
to the first listener that registers: but only for the *one-shot* tags
(`internet_session_superseded`, `mesh_stopped_by_user`), which nothing
else ever restates. Everything else is dropped, as it should be: a
periodic event replayed after the fact would report a state that has
since changed.

That window is not an edge case, it is the default. The SDK subscribes to
the native emitter inside its own constructor, so between
`new OfflineProtocol(...)` and your first `on(...)` there is a stretch in
which events arrive with nothing registered: and Android's native
redelivery of held one-shot events fires on exactly that constructor-time
subscribe, landing squarely inside it. Registering synchronously right
after construction keeps the window at zero and is still the right habit;
this hold is what makes an `await` in between survivable.

Replay is **asynchronous** (a microtask), so a handler never runs before
the `on(...)` call that registered it has returned, and every listener
registered in the same tick, including an `'all'` listener added after a
specific one, receives it. Delivery stays **at-least-once**: these events
are state, not edges, and handlers must be idempotent. See
`docs/react-native-integration.md` §6.1.

**returns:** This instance for chaining

```typescript theme={null}
on<T extends ProtocolEvent = ProtocolEvent>(eventType: EventType | "all", listener: EventListener<T>): this
```

## off

Removes an event listener

**returns:** This instance for chaining

```typescript theme={null}
off<T extends ProtocolEvent = ProtocolEvent>(eventType: EventType | "all", listener: EventListener<T>): this
```

## once

Registers a one-time event listener

**returns:** This instance for chaining

```typescript theme={null}
once<T extends ProtocolEvent = ProtocolEvent>(eventType: EventType | "all", listener: EventListener<T>): this
```

## removeAllListeners

Removes all listeners for a specific event type, or all listeners if no type specified

**returns:** This instance for chaining

```typescript theme={null}
removeAllListeners(eventType?: EventType | "all"): this
```

## start

Starts the protocol

**Automatic BLE Management:**
When called, this method automatically starts BLE operations if BLE is enabled:

* Starts scanning for nearby devices advertising the Offline Protocol service
* Starts advertising this device so others can discover it
* Begins polling for fragments to send
* Handles incoming fragments from peers

**Automatic MLS Initialization:**
If encryption is enabled (default: true), MLS is automatically initialized with
platform-specific secure storage (iOS Keychain / Android EncryptedSharedPreferences).
To disable auto-initialization, set `encryption.enabled: false` in the config.

**Permissions Required:**

* iOS: Bluetooth permissions (NSBluetoothAlwaysUsageDescription in Info.plist)
* Android: BLUETOOTH\_SCAN, BLUETOOTH\_ADVERTISE, BLUETOOTH\_CONNECT (Android 12+)
  or BLUETOOTH, BLUETOOTH\_ADMIN, ACCESS\_FINE\_LOCATION (Android 11 and below)

**throws:** Error if protocol is already started or fails to start

```typescript theme={null}
start(): Promise<void>
```

## stop

Stops the protocol

**Automatic BLE Management:**
When called, this method automatically stops all BLE operations:

* Stops scanning for devices
* Stops advertising
* Disconnects from all connected peers
* Cleans up BLE resources

**throws:** Error if protocol is not started or fails to stop

```typescript theme={null}
stop(): Promise<void>
```

## emitTestEvent

Emits a test event to verify the event system is working

This is a debugging method that emits a network\_metrics event with all zeros.
Use this to verify that events are being delivered from Rust through the
native bridge to JavaScript.

```typescript theme={null}
emitTestEvent(): Promise<void>
```

## pause

Pauses the protocol (for background mode)

**throws:** Error if protocol is not running or fails to pause

```typescript theme={null}
pause(): Promise<void>
```

## resume

Resumes the protocol from pause

**throws:** Error if protocol is not paused or fails to resume

```typescript theme={null}
resume(): Promise<void>
```

## getState

Gets the current protocol state

**returns:** Protocol state (Stopped, Running, or Paused)

**throws:** Error if retrieval fails

```typescript theme={null}
getState(): Promise<ProtocolState>
```

## destroy

Destroys the protocol instance and cleans up resources

```typescript theme={null}
destroy(): Promise<void>
```

## wipePersistedState

Erases every byte of persisted SDK state for one account: the namespaced
secure store (MLS identity, sessions, peer trust records, the Nostr signing secret,
the protocol-state record key), the account's protocol-state directory
(outbox, pending queues, block list, media descriptors), and, when this
account owns it, or nobody does, the pre-namespace store an upgraded
install inherited from.

Call this on logout and on username switch, **after** `destroy()`. The
protocol persists as it works, so wiping underneath a live instance races
those writes; the native side rejects the call if the account named here is
the one the current instance is running.

Without it, an account's undelivered messages are restored and re-driven on
the next launch for the lifetime of the outbox, and on iOS, where the
Keychain outlives the app container, its identity and delivery state
survive an uninstall and are adopted again after a reinstall.

The account is named explicitly because `destroy()` clears the config the
namespace would otherwise be derived from. Pass the same `appId` and
`profile` the protocol was created with; any other pair names a different
account and wipes nothing.

Irreversible, and it rotates the account's MLS and Nostr identities: peers
holding a session with it will see a desync on next contact and re-establish
from a fresh key package. Safe to call twice: a failed wipe should simply
be retried.

Applications that supply their own storage providers must erase their own
containers: this only knows about the built-in ones.

```typescript theme={null}
wipePersistedState(appId: string, profile: string): Promise<void>
```

## Parameter and result types

Import these types from the same SDK package. Event payloads and configuration types are linked below.

### EventListener

```typescript theme={null}
export type EventListener<T extends ProtocolEvent = ProtocolEvent> = (event: T) => void;
```

### ProtocolState

```typescript theme={null}
export enum ProtocolState {
  Stopped = "Stopped",
  Running = "Running",
  Paused = "Paused",
}
```

## Related references

* [`identity_ready`](/docs/mesh-sdk/events#identity_ready)
* [`mesh_stopped_by_user`](/docs/mesh-sdk/events#mesh_stopped_by_user)
* [Event payloads](/docs/mesh-sdk/events)
* [Configuration types](/docs/mesh-sdk/configuration-types)
* [All public types](/docs/mesh-sdk/public-types)
* [Error codes](/docs/mesh-sdk/errors)
* [All API tasks](/docs/mesh-sdk/reference)
