Types & Internals
Naming conventions
The SDK uses two casing conventions, and the split is deliberate:
The only camelCase fields on events are
type and the optional seenAt. The clearest example
of the pair is MediaMetadata (camelCase, input) versus MediaMetadataEvent (snake_case,
output).
ForwardInfo and ReplyContext are the exception: they are snake_case on both sides, as
they are passed verbatim into params and re-emitted on events.
Core Enums
Message Types
ForwardInfo
ReplyContext
Renders a reply preview without a local copy of the original message.Media Types
MediaMetadata (input, camelCase)
MediaMetadataEvent (output, snake_case)
Same information as carried on received events. All fields are optional.FileProgress
Network Types
Routing Types
MLS Types
GroupRichReadiness is point-in-time and advisory: capability knowledge changes with key
package exchanges and restarts, and the send path re-evaluates the gate itself. Use it to warn
before sending (graying out an attachment button) rather than reacting to
group_rich_extras_dropped after the fact.
Diagnostics & Telemetry Types
{ category: 'extension' } at older client builds, so handle
that case rather than assuming exhaustiveness.
Several
MetricsFrame counters are u64 in Rust but cross the bridge as JavaScript numbers
(f64, 53-bit mantissa). Values above 2^53 silently lose precision, so treat any single value
above roughly 9 PB as approximate.Constants
DORS (Dynamic Offline Relay Switch)
DORS automatically selects the optimal transport across BLE, Wi-Fi Direct, Internet, Reticulum, and Nostr based on real-time conditions.Scoring Factors
Transport Weights
BLE: optimized for energy efficiency and mesh scenarios Signal 30%, Energy 30%, Congestion 15%, Proximity 15% Wi-Fi Direct: optimized for high throughput Bandwidth 35%, Proximity 20%, Congestion 20%, Reliability 15% Internet: optimized for server connectivity Bandwidth 35%, Reliability 30%, Congestion 15%, Energy 10%Switching Safeguards
Mesh Networking
Cluster Architecture
Devices organize into clusters of nearby connected peers. Connections between clusters are handled by bridge connections.MEMBER: intra-cluster connectionBRIDGE: inter-cluster connection
How It Works
- Discovery: devices broadcast BLE advertisements with mesh metadata (degree, free slots, battery, uptime)
- Cluster detection: each device computes a cluster signature from connected peer hashes
- Connection decisions: the MeshController prioritizes bridging different clusters
- Rebalancing: lower-quality peers are periodically swapped for better candidates
- Delivery: messages are handed onward to a bounded set of neighbors until they arrive or run out of hops
Connection Budget
- Default: 4 connections per device
- Minimum: 1 connection maintained
- Connections are scored and rebalanced roughly every 15 seconds
- Bridge candidates get priority when clusters need unifying
Peer Scoring
Candidates from a different cluster get a score bonus to encourage network unification.
Message TTL
Default 8 hops. Messages are dropped when TTL reaches 0, which prevents infinite circulation. Every device also caps how much it forwards, per second overall and per neighbor, so a crowded room stays usable rather than filling with repeated copies.Reliability Layer
Acknowledgments
Messages require an ACK for delivery confirmation, andmessage_delivered fires on receipt.
Retry Queue
Failed messages are retried with exponential backoff. Each attempt emitsmessage_retrying
with the scheduled time.
Message lifecycle states
Only two events are terminal. Everything else is a status update.Deduplication
- Bloom filter mode: space-efficient, ~1% false positive rate
- HashMap mode: exact tracking, configurable capacity
Troubleshooting
Messages not delivering
- Verify both devices have the protocol started
- Check they are within BLE range (~10–30 m)
- Confirm
recipientis a peer’soff1…address, not a username or profile string - Ensure TTL is sufficient for the network size
- Watch
message_retryingandmessage_undeliverablerather than treating either as failure - Check
getEstablishmentState(peerId)if encryption is required
No peers discovered
- Verify Bluetooth is enabled:
await protocol.isBluetoothEnabled() - Check runtime permissions were granted before
start() - Ensure background modes are declared (iOS)
- Verify devices are within range
Secure session not establishing
- Ensure encryption is enabled (it is by default)
- Check
getEstablishmentState(peerId)for the current state - Verify
hasPendingKeyPackage(peerId)returns true - Watch for
secure_session_failed, but note it no longer implies the session is gone - Watch for
security_warningwithSENDER_ADDRESS_MISMATCH, which indicates impersonation rather than a re-keyed peer
Sends fail with an encryption error
requireEncryption defaults to true, so a send that cannot be encrypted fails rather than
falling back to plaintext. Confirm MLS initialized (isMlsInitialized()) and that a session
exists with the recipient. Opt out explicitly only if you intend plaintext operation.
Degraded BLE behavior
SamplegetBleDiagnostics() over time. Rising recipientNotAmongPeers or
fragmentFallbacks counts indicate frames taking a degraded path. They still send, so they
never surface as delivery failures.
Frequent disconnections
- Check signal strength via
neighbor_discoveredRSSI - Increase
stabilityWindowSecsin DORS config - Check for BLE interference
High battery drain
- Verify DORS is selecting BLE over Wi-Fi Direct
- Check for excessive retry activity
- Use
setBatteryLevel()to inform mesh decisions - Consider
relayPriority: 'never'on constrained devices
Transport not switching
- Verify the transport is enabled in config
- Check the hysteresis threshold is not too high
- Ensure the cooldown period has elapsed
- Use
forceTransport()to test manually