> ## 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.

# Transports and routing

> Configure transport paths, routing, relay roles, retries and platform bridge callbacks.

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

The native binding normally owns bridge callbacks such as `wifiDirectMessageReceived` and `internetConfirmSent`. Application integrations should use the binding transport configuration; call these low-level hooks only when implementing the corresponding bridge.

## Methods on this page

* [`getActiveTransports`](#getactivetransports)
* [`enableTransport`](#enabletransport)
* [`disableTransport`](#disabletransport)
* [`isBluetoothEnabled`](#isbluetoothenabled)
* [`requestEnableBluetooth`](#requestenablebluetooth)
* [`getTopology`](#gettopology)
* [`setBatteryLevel`](#setbatterylevel)
* [`setBatteryState`](#setbatterystate)
* [`getBatteryLevel`](#getbatterylevel)
* [`getIsCharging`](#getischarging)
* [`setRelayPriority`](#setrelaypriority)
* [`getRelayPriority`](#getrelaypriority)
* [`updateRelayConfig`](#updaterelayconfig)
* [`getRelayConfig`](#getrelayconfig)
* [`isRelay`](#isrelay)
* [`getBLePeerCount`](#getblepeercount)
* [`getBleDiagnostics`](#getblediagnostics)
* [`getTransportMetrics`](#gettransportmetrics)
* [`forceTransport`](#forcetransport)
* [`releaseTransportLock`](#releasetransportlock)
* [`updateDorsConfig`](#updatedorsconfig)
* [`getDorsConfig`](#getdorsconfig)
* [`updateAckConfig`](#updateackconfig)
* [`updateRetryConfig`](#updateretryconfig)
* [`updateDedupConfig`](#updatededupconfig)
* [`getDedupStats`](#getdedupstats)
* [`getMeshRelayStats`](#getmeshrelaystats)
* [`getMeshRelayTunables`](#getmeshrelaytunables)
* [`getPendingAckCount`](#getpendingackcount)
* [`getRetryQueueSize`](#getretryqueuesize)
* [`shouldEscalateToWifi`](#shouldescalatetowifi)
* [`wifiDirectStatusChanged`](#wifidirectstatuschanged)
* [`wifiDirectMessageReceived`](#wifidirectmessagereceived)
* [`wifiDirectGetNextMessage`](#wifidirectgetnextmessage)
* [`wifiDirectPeerConnected`](#wifidirectpeerconnected)
* [`wifiDirectPeerDisconnected`](#wifidirectpeerdisconnected)
* [`internetStatusChanged`](#internetstatuschanged)
* [`internetMessageReceived`](#internetmessagereceived)
* [`internetGetNextMessage`](#internetgetnextmessage)
* [`internetConfirmSent`](#internetconfirmsent)
* [`internetSendFailed`](#internetsendfailed)
* [`sendRawServerCommand`](#sendrawservercommand)
* [`isInternetReady`](#isinternetready)
* [`isInternetSuperseded`](#isinternetsuperseded)
* [`forceInternetReconnect`](#forceinternetreconnect)

## getActiveTransports

Gets the list of active transports

**returns:** Array of active transport type names

```typescript theme={null}
getActiveTransports(): Promise<TransportType[]>
```

## enableTransport

Enables a transport with optional configuration

Re-enabling `'internet'` is also the recovery from a relay supersede: it
clears the transport's latch, so any `internet_session_superseded` this
instance is still holding for a late listener is dropped here. Held, it
would tell an app with a freshly reconnected relay socket that it is
connected elsewhere, with nothing to correct it. Mirrors the same discard
on the native side.

**throws:** Error if transport fails to enable

```typescript theme={null}
enableTransport(type: TransportType, config?: InternetTransportConfig | WifiDirectTransportConfig | NostrTransportConfig | ReticulumTransportConfig): Promise<void>
```

## disableTransport

Disables a transport

**throws:** Error if transport fails to disable

```typescript theme={null}
disableTransport(type: TransportType): Promise<void>
```

## isBluetoothEnabled

Checks if Bluetooth is enabled on the device

**returns:** True if Bluetooth is enabled, false otherwise

```typescript theme={null}
isBluetoothEnabled(): Promise<boolean>
```

## requestEnableBluetooth

Requests the user to enable Bluetooth

On Android, this shows a system dialog to enable Bluetooth.
On iOS, this returns false as iOS doesn't allow programmatic Bluetooth enabling.

**returns:** True if Bluetooth was enabled, false otherwise

```typescript theme={null}
requestEnableBluetooth(): Promise<boolean>
```

## getTopology

Gets the current network topology

**returns:** Network topology snapshot including nodes, links, and stats

**throws:** Error if topology retrieval fails

```typescript theme={null}
getTopology(): Promise<NetworkTopology>
```

## setBatteryLevel

Reports the device's battery level to the protocol engine.

This is the feed for every battery-dependent policy in the SDK: DORS
energy scoring, relay promotion/demotion (`relay_promoted` /
`relay_demoted`), the message-forwarding battery floor, and the telemetry
device-capability snapshot. No transport can observe the host's battery,
so until this is called each of those policies runs in its unknown-level
branch. Call it on start and on each platform battery notification.

Prefer `setBatteryState` where charging state is available: a
charging device is deliberately excused the soft relay battery floor, so
reporting the level alone strips relay duty from plugged-in devices that
should keep it.

```typescript theme={null}
setBatteryLevel(level: number): Promise<void>
```

## setBatteryState

Reports the device's battery level and charging state to the protocol
engine. See `setBatteryLevel` for what depends on it.

```typescript theme={null}
setBatteryState(level: number, isCharging: boolean): Promise<void>
```

## getBatteryLevel

Gets the last reported battery level

**returns:** Battery level (0-100) or null if the host has not reported one

```typescript theme={null}
getBatteryLevel(): Promise<number | null>
```

## getIsCharging

Gets the last reported charging state (false if none reported).

```typescript theme={null}
getIsCharging(): Promise<boolean>
```

## setRelayPriority

Sets the relay priority.

A shorthand for updating only `relayPriority`; see
`updateRelayConfig` for the rest. The legacy `'low' | 'medium' |
'high'` spelling is still accepted and maps to `never` / `auto` /
`always`.

**throws:** Error if setting fails

```typescript theme={null}
setRelayPriority(priority: RelayPriority | "low" | "medium" | "high"): Promise<void>
```

## getRelayPriority

Gets the current relay priority

```typescript theme={null}
getRelayPriority(): Promise<RelayPriority>
```

## updateRelayConfig

Updates the relay configuration at runtime.

Governs whether this device carries other people's traffic and under what
conditions it takes the relay role. Applies to the next role evaluation
and the next forwarding decision: no restart needed. Omitted fields keep
their current values.

The battery-dependent parts need a battery feed to do anything: see
`setBatteryState`.

```typescript theme={null}
updateRelayConfig(config: RelayConfig): Promise<void>
```

## getRelayConfig

Gets the current relay configuration.

```typescript theme={null}
getRelayConfig(): Promise<Required<RelayConfig>>
```

## isRelay

Checks if this device is currently acting as a relay

**returns:** True if device is a relay

```typescript theme={null}
isRelay(): Promise<boolean>
```

## getBLePeerCount

Gets the current number of discovered BLE peers

**returns:** Number of BLE peers currently tracked

```typescript theme={null}
getBLePeerCount(): Promise<number>
```

## getBleDiagnostics

Gets the BLE diagnostic counters.

These are the rollout alarm for the self-certifying-address migration.
Each counter records a frame that took a *degraded* path rather than
failing outright, so none of them show up as a delivery error: a fleet
can be quietly falling back on every send while its success metrics look
healthy. Read them together and watch the trend, not the absolute value:
small counts are normal, sustained growth after a release means peers
disagree about identity or MTU.

* `fragmentFallbacks`: a frame for a directly-connected peer had to be
  broadcast to every peer instead of addressed to one. Rising means
  recipients are not being recognised as the connected peer they are.
* `recipientNotAmongPeers`: a send named a peer that is connected over
  BLE but not under that id. This is the sharpest identity-mismatch
  signal: it is what a peer announcing one id while framing another
  looks like from the sender's side.
* `undersizedMtuReports`: a peer reported an MTU too small to carry a
  fragment header, so a conservative default was used.

All three read zero when BLE is not enabled or not yet started.

**returns:** The three counters as of now

```typescript theme={null}
getBleDiagnostics(): Promise<BleDiagnostics>
```

## getTransportMetrics

Gets detailed metrics for a specific transport

**returns:** Transport metrics or null if not available

```typescript theme={null}
getTransportMetrics(transportType: TransportType): Promise<TransportMetrics | null>
```

## forceTransport

Forces the protocol to use a specific transport (overrides DORS)

**throws:** Error if forcing fails

```typescript theme={null}
forceTransport(transportType: TransportType): Promise<void>
```

## releaseTransportLock

Releases the transport lock and lets DORS make decisions again

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

## updateDorsConfig

Updates DORS configuration at runtime.

Omitted fields keep their current values: the same partial-update
contract as `updateRelayConfig`. Every field must therefore be
expressible here: a field this signature omits cannot be set, and (before
the bridges merged from the live config) was silently reset by any update
that changed something else.

**throws:** Error if update fails

```typescript theme={null}
updateDorsConfig(config: { preferOnline?: boolean; switchHysteresis?: number; switchCooldownSecs?: number; bleToWifiRetryThreshold?: number; minSuccessRateBeforeEscalation?: number; minBleSamplesBeforeSuccessRateEscalation?: number; rssiSwitchThreshold?: number; congestionQueueThreshold?: number; stabilityWindowSecs?: number; poorSignalDurationSecs?: number; ttlEscalationThreshold?: number; congestionDurationSecs?: number; ttlEscalationHoldSecs?: number; historyWindowSize?: number; queueRecoveryRatio?: number; lowBatteryThreshold?: number; relayMinBatteryLevel?: number; relayOptimalConnectionCount?: number; }): Promise<void>
```

## getDorsConfig

Gets the current DORS configuration

**returns:** DORS configuration

```typescript theme={null}
getDorsConfig(): Promise<{
    preferOnline: boolean;
    switchHysteresis: number;
    switchCooldownSecs: number;
    bleToWifiRetryThreshold: number;
    minSuccessRateBeforeEscalation: number;
    minBleSamplesBeforeSuccessRateEscalation: number;
    rssiSwitchThreshold: number;
    congestionQueueThreshold: number;
    stabilityWindowSecs: number;
    poorSignalDurationSecs: number;
    ttlEscalationThreshold: number;
    congestionDurationSecs: number;
    ttlEscalationHoldSecs: number;
    historyWindowSize: number;
    queueRecoveryRatio: number;
    lowBatteryThreshold: number;
    relayMinBatteryLevel: number;
    relayOptimalConnectionCount: number;
  }>
```

## updateAckConfig

Updates ACK configuration at runtime

**throws:** Error if update fails

```typescript theme={null}
updateAckConfig(config: AckConfig): Promise<void>
```

## updateRetryConfig

Updates retry configuration at runtime

**throws:** Error if update fails

```typescript theme={null}
updateRetryConfig(config: RetryConfig): Promise<void>
```

## updateDedupConfig

Updates deduplication configuration at runtime

**throws:** Error if update fails

```typescript theme={null}
updateDedupConfig(config: DedupConfig): Promise<void>
```

## getDedupStats

Gets deduplicator statistics for monitoring

**returns:** Deduplication statistics

```typescript theme={null}
getDedupStats(): Promise<DedupStats>
```

## getMeshRelayStats

Reports how much traffic this device is carrying for other people.

Counters are cumulative for the lifetime of this instance and never reset,
so a rate is a difference between two reads. The exception is
`awaitingTransmission`, a gauge that goes down as well as up. `forwarded`
is the contribution figure to show a user; `transmissions` is the one the
per-second budget bounds, since it counts each link separately and
includes this device's own sends.

Two readings worth knowing: `rateDeferred` rising means forwarding is
hitting its ceiling and those frames are delayed rather than dropped, and
`coveredByANeighbor` is the mesh working as intended: a neighbor was
heard carrying the frame first, so this device stood down.

For whether back-pressure is actually costing anything, read
`refusedQueueFull` and `abandonedOverdue`. Those are the two that count
frames genuinely lost, and a device shedding traffic can otherwise show
nothing but healthy-looking deferrals.

Note that a device with a working relay connection forwards nothing: the
mesh is only offered frames no other carrier can deliver. Zero counters on
an online device are the honest answer, not a fault.

**returns:** Cumulative mesh forwarding counters

```typescript theme={null}
getMeshRelayStats(): Promise<MeshRelayStats>
```

## getMeshRelayTunables

Reports the mesh forwarding tunables actually in force.

Read from the governor in the Rust core, so this is what forwarding
decisions really use rather than an echo of what was passed to
`create()`: including every default this app never set.

Every field is present. Nothing here needs a `??` fallback, and writing
one would be inventing a second copy of a default that can drift.

**returns:** The mesh forwarding tunables in force

```typescript theme={null}
getMeshRelayTunables(): Promise<MeshRelayTunables>
```

## getPendingAckCount

Gets the number of pending ACKs waiting for confirmation

**returns:** Number of pending ACKs

```typescript theme={null}
getPendingAckCount(): Promise<number>
```

## getRetryQueueSize

Gets the current retry queue size

**returns:** Number of messages in retry queue

```typescript theme={null}
getRetryQueueSize(): Promise<number>
```

## shouldEscalateToWifi

Checks if DORS recommends escalating to WiFi.
Use this to query whether the protocol should switch from BLE to WiFi Direct.

**returns:** True if escalation to WiFi is recommended

```typescript theme={null}
shouldEscalateToWifi(): Promise<boolean>
```

## wifiDirectStatusChanged

Notifies the protocol that the platform's stream layer is up or down.

Up means streams can be established, not that a peer is reachable. The
slot counts as an available carrier only once a stream has proved a peer,
and it queues a message only toward an address a stream has proved, so an
up layer with no announced peer is not a carrier at all. Down clears every
announced link.

```typescript theme={null}
wifiDirectStatusChanged(isConnected: boolean): Promise<void>
```

## wifiDirectMessageReceived

Hands the protocol one frame body that arrived on the stream whose
preamble proved `senderId`.

`senderId` is the address `verifyIdentityAssertion` derived from that
stream's first frame, and nothing else: the core attributes the body to
it and matches `Message.sender` against it, so a body handed up under a
socket address, a device name, or an address merely read from a discovery
record is refused as unattributed. `data` is the body after the length
prefix, exactly one message.

```typescript theme={null}
wifiDirectMessageReceived(senderId: string, data: number[]): Promise<void>
```

## wifiDirectGetNextMessage

Gets the next outgoing body and the proved address of the stream to
write it on. The caller frames it (u32 big-endian length, then the body)
and writes it to the stream whose preamble proved `recipientId`; nothing
is ever returned for an address no stream proved.

**returns:** The body and its stream's address, or null if the queue is empty

```typescript theme={null}
wifiDirectGetNextMessage(): Promise<{
    recipientId: string;
    data: number[];
  } | null>
```

## wifiDirectPeerConnected

Announces a stream whose preamble verified, under the address it proved.

Call it once per announced stream with the derived address. An unproven
`peerId` here is entered into the core's capacity-bounded `known_peers`,
evicting genuine neighbours, and starts an automatic key exchange toward
a peer that cannot answer it, which is why nothing but the verifier's
result may be passed. A receiver holds one announced stream per address:
a second stream proving an address already announced is refused or
supersedes the first, and either way the core sees one announcement and,
later, one loss.

```typescript theme={null}
wifiDirectPeerConnected(peerId: string): Promise<void>
```

## wifiDirectPeerDisconnected

Reports that the announced stream for `peerId` ended.

Only for a stream that was announced: a stream that never proved a peer
has nothing to report, and reporting it would tell the core a peer it may
still hold over another stream is gone.

```typescript theme={null}
wifiDirectPeerDisconnected(peerId: string): Promise<void>
```

## internetStatusChanged

Notifies the protocol of internet connection state change.

```typescript theme={null}
internetStatusChanged(isConnected: boolean): Promise<void>
```

## internetMessageReceived

Handles an incoming internet message.

```typescript theme={null}
internetMessageReceived(senderId: string, data: number[]): Promise<void>
```

## internetGetNextMessage

Gets the next outgoing internet message.

After sending over the wire, you **must** call either
`internetConfirmSent(messageId)` or `internetSendFailed(messageId)`.

**returns:** Message to send (with messageId) or null if queue is empty

```typescript theme={null}
internetGetNextMessage(): Promise<{
    messageId: string;
    recipientId: string;
    data: number[];
  } | null>
```

## internetConfirmSent

Confirms that a message was successfully sent over the wire (e.g., WebSocket).

Call this after the WebSocket `send()` completes successfully.
This feeds real delivery data into transport metrics for DORS routing.

```typescript theme={null}
internetConfirmSent(messageId: string): Promise<void>
```

## internetSendFailed

Reports that a message failed to send over the wire.

Call this when the WebSocket `send()` fails or the connection drops.

```typescript theme={null}
internetSendFailed(messageId: string): Promise<void>
```

## sendRawServerCommand

Sends a raw, caller-built relay command verbatim over the SDK's internet
socket: the generic server-command channel for relay features that are
app concerns rather than SDK APIs (the invite-link lifecycle:
`CreateGroupInviteLink`, `JoinGroupViaInvite`, `AckGroupInviteJoin`, …).

Responses the SDK doesn't consume arrive as `internet_server_message`
events carrying the verbatim frame. `GroupInfo` and `UserGroups` are also
emitted on that channel in addition to their stable typed events, so
application-owned extension fields remain lossless. Correlate
request/response with your own `request_id` where the relay supports one.

Gate calls on `isInternetReady()` / the `internet_status_changed` event
rather than probing with a command and retrying on false. For
group-scoped commands (`CreateGroupInviteLink`, …) additionally await
`ensureGroupRegistered(groupId)` first: the relay must know the group.

Do not send frame types the SDK itself manages (`SendMessage`,
`CreateGroup` / member deltas, `LeaveGroup`, `CheckPresence`, …): the
SDK cannot correlate their answers with its own in-flight state, and a
raw `CreateGroup`/`LeaveGroup` desyncs the bridge's registration
tracking. Use the typed SDK APIs for those.

**returns:** true once the socket accepted the command (write-confirmed on
iOS, enqueue-confirmed on Android: the closest OkHttp offers);
false when not connected+authenticated, the JSON is invalid, or
the SDK's client-side rate limiter deferred it (the SDK's
mirror sits slightly under the relay's 30-burst/10-per-second
budget, at 28 burst / 9 per second: safe to retry after a
short delay)

**throws:** when the internet transport was never initialized (enable it via
`transports.internet` before calling)

```typescript theme={null}
sendRawServerCommand(json: string): Promise<boolean>
```

## isInternetReady

Whether the SDK's internet socket is connected AND relay-authenticated: the same gate `sendRawServerCommand` checks before writing. The
positive replacement for app-side `relayStatus === 'authenticated'`
tracking: gate raw sends on this (or on the `internet_status_changed`
event) instead of probing with a command and retrying on false.

Point-in-time; transitions arrive as `internet_status_changed` events.
A ready socket can still defer an individual send (client-side rate
limiter): `sendRawServerCommand` returning false while ready means
retry after a short delay.

Cannot tell you *why* it is false. An ordinary disconnect (which
reconnects itself) and a relay displacement (which never will) both read
false here: use `isInternetSuperseded` to tell them apart.

**returns:** true when connected and authenticated; false otherwise,
including when the internet transport was never initialized
(never throws)

```typescript theme={null}
isInternetReady(): Promise<boolean>
```

## isInternetSuperseded

Whether the relay displaced this session, another device registered the
same identity and took over the relay slot, and the SDK latched the
internet transport stopped.

This is the question `isInternetReady` structurally cannot answer.
A `false` from it means "not usable right now" and nothing more: an
ordinary disconnect reconnects itself within seconds, while a displaced
session **will never reconnect on its own**, because a blind reconnect
would just re-displace the other device in a loop. The two are
indistinguishable from readiness alone.

True here means the only recovery is a deliberate re-enable:

```ts theme={null}
if (await sdk.isInternetSuperseded()) {
  // Surface "connected elsewhere" and let the user decide.
  await sdk.enableTransport('internet', { serverAddress });  // clears the latch
}
```

Complements the `internet_session_superseded` event rather than replacing
it. The event tells an app that is listening at that instant; this answers
an app that asks: including one that subscribed later, or whose process
was killed and restarted, which no in-memory event hold survives. A
foreground reconcile against this is the most robust shape.

**returns:** true while the session is superseded; false otherwise, including
when the internet transport was never initialized (never throws)

```typescript theme={null}
isInternetSuperseded(): Promise<boolean>
```

## forceInternetReconnect

Forces an immediate teardown + reconnect + re-authenticate of the SDK's
internet socket, bypassing the exponential reconnect backoff.

`isInternetReady()` is a point-in-time cached flag, not a liveness probe:
an OS suspend can kill the TCP connection before a clean WebSocket close,
so after a background→foreground transition the socket may be a zombie
(dead but still reported ready) or alive-but-deregistered by the relay.
Neither is detectable by a ping, and both are healed by the same action: a full reconnect that re-runs the relay authenticate/register handshake.

You normally do NOT need to call this on foreground: as of the automatic
foreground-heal, both native bridges (iOS `applicationWillEnterForeground`,
Android `onHostResume`) already force a reconnect themselves after a
background stay long enough to have killed the socket (\~4s), gated on
monotonic background duration: and iOS additionally tears down a zombie on
the first stalled write via the write-stall watchdog. Calling this method
in addition, on every foreground, would double-reconnect and drop a
genuinely-healthy socket, forcing a wasted group re-registration round-trip.

Keep it for the cases the automatic heal does not cover: a deliberate
user-initiated "reconnect now", or a stale socket you detect while already
foregrounded (e.g. a long idle period with no background transition). If you
do drive foreground recovery yourself, debounce and gate on background
duration rather than calling on every foreground.

Recovery lands in \~1s rather than waiting \~20-30s for zombie ping detection.

Emits a transient `internet_status_changed` down→up. No-op unless the
internet transport is running (respects the enable/disable lifecycle).

**returns:** true once the request reached a live internet transport: this
means "accepted", not "reconnected": it is also true when the
transport is initialized but not currently running/starting, in
which case the call is a deliberate no-op. false only when the
internet transport was never initialized. Never throws.

```typescript theme={null}
forceInternetReconnect(): Promise<boolean>
```

## Parameter and result types

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

### TransportType

```typescript theme={null}
export type TransportType = 'ble' | 'internet' | 'wifiDirect' | 'reticulum' | 'nostr';
```

### NetworkTopology

```typescript theme={null}
export interface NetworkTopology {
  timestamp: number;
  local_user_id: string;
  nodes: NetworkNode[];
  links: NetworkLink[];
  stats: NetworkStats;
}
```

### NetworkNode

```typescript theme={null}
export interface NetworkNode {
  user_id: string;
  role: NodeRole;
  connection_count: number;
  battery_level?: number;
  last_seen: number;
  transports: TransportType[];
}
```

### NodeRole

```typescript theme={null}
export enum NodeRole {
  Normal = 'normal',
  Relay = 'relay',
}
```

### NetworkLink

```typescript theme={null}
export interface NetworkLink {
  from: string;
  to: string;
  quality: number;
  transport: TransportType;
  rssi?: number;
}
```

### NetworkStats

```typescript theme={null}
export interface NetworkStats {
  total_nodes: number;
  relay_nodes: number;
  total_connections: number;
  avg_link_quality: number;
  network_diameter?: number;
}
```

### BleDiagnostics

```typescript theme={null}
export interface BleDiagnostics {
  fragmentFallbacks: number;
  recipientNotAmongPeers: number;
  undersizedMtuReports: number;
}
```

### TransportMetrics

```typescript theme={null}
export interface TransportMetrics {
  packetsSent: number;
  packetsReceived: number;
  bytesSent: number;
  bytesReceived: number;
  errorRate: number;
  avgLatencyMs: number;
  rssi?: number;
  bandwidthBps?: number;
  congestion?: number;
  queueDepth?: number;
  batteryLevel?: number;
  isCharging?: boolean;
  relayConnectionCount?: number;
  isActiveRelay?: boolean;
  deliveryRatio?: number;
  dropRate?: number;
  averageHopCount?: number;
  energyCost?: number;
}
```

### DedupStats

```typescript theme={null}
export interface DedupStats {
  totalTracked: number;
  recentTracked: number;
  capacityUsedPercent: number;
  mode: string;
}
```

### MeshRelayStats

```typescript theme={null}
export interface MeshRelayStats {
  forwarded: number;
  transmissions: number;
  queued: number;
  awaitingTransmission: number;
  duplicatesSuppressed: number;
  coveredByANeighbor: number;
  peerRateLimited: number;
  refusedQueueFull: number;
  rateDeferred: number;
  abandonedOverdue: number;
  hopLimitReached: number;
  reachClamped: number;
  droppedForCapacity: number;
}
```

### MeshRelayTunables

```typescript theme={null}
export interface MeshRelayTunables {
  maxTtl: number;
  denseMaxTtl: number;
  denseDegree: number;
  fanout: number;
  jitterMinMs: number;
  jitterMaxMs: number;
  ratePerSec: number;
  burst: number;
  peerRatePerSec: number;
  peerBurst: number;
  queueCapacity: number;
  biasMinScale: number;
  biasMaxHandicapMs: number;
  activityWindowMs: number;
  activityMinForwards: number;
  activityIdleWindows: number;
}
```

## Related references

* [`neighbor_discovered`](/docs/mesh-sdk/events#neighbor_discovered)
* [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)
