# BitChat Message Freshness Policies and TTL Capping: A Complete Guide

> Understand BitChat message freshness policies and TTL capping. Learn how hop limits and age-based expiry control message propagation and storage lifetimes in BitChat.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-02

---

**BitChat controls message propagation and storage lifetimes through hop-based TTL limits and age-based expiry windows defined in [`TransportConfig.swift`](https://github.com/permissionlesstech/bitchat/blob/main/TransportConfig.swift), capping mesh packet travel at 7 hops in sparse networks and 5 in dense topologies while expiring stored public messages after 6 hours.**

BitChat, developed by the `permissionlesstech/bitchat` repository, implements a strict freshness model to prevent mesh network flooding and bound storage growth. The **BitChat message freshness policies** combine **time-to-live (TTL) counters** for packet routing with **age-based eviction** for persisted data, all centralized in the `TransportConfig` type for auditability. These mechanisms ensure that messages either reach their destination within a limited hop budget or are discarded to maintain network efficiency.

## Core Freshness Policies Defined in TransportConfig

All freshness limits reside in [`bitchat/Services/TransportConfig.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/TransportConfig.swift), providing a single source of truth for mesh behavior and storage management.

### Mesh Packet TTL Defaults

The default hop limit for regular mesh packets is **7 hops**, defined as `TransportConfig.messageTTLDefault` at lines 8–9. When a packet originates, its TTL header is set to this value; each intermediate hop decrements the counter until it reaches zero and the packet is dropped.

### Fragment Relay TTL Caps

Media fragmentation uses topology-aware TTL capping to prevent broadcast storms:

- **Sparse graphs**: `bleFragmentRelayTtlCap` is set to **7** (line 29), matching the regular packet default to ensure fragments travel as far as text messages.
- **Dense graphs**: `bleFragmentRelayTtlCapDense` is capped at **5** (line 30), reducing propagation depth to prevent overwhelming highly connected networks.

### Storage and Sync Age Limits

Local storage enforces strict age limits to bound database size:

- **Public message sync**: `syncPublicMessageMaxAgeSeconds` limits broadcasts to **6 hours** (line 74).
- **General sync messages**: `syncMaxMessageAgeSeconds` caps walk-synced messages at **15 minutes** (line 69).
- **Private media receipts**: `privateMediaReceivedLedgerTTLSeconds` retains receipts for **7 days** (lines 21–24) before eviction.
- **Pre-key bundles**: `syncPrekeyBundleMaxAgeSeconds` validates store-and-forward encryption keys for **24 hours** (line 104).
- **Location drops**: `locationDropExpirySeconds` expires geo-notes after **24 hours** (line 26).
- **Deduplication cache**: `messageDedupMaxAgeSeconds` suppresses duplicate IDs for **5 minutes** (line 32).

## How TTL Capping Works in RelayController

The [`RelayController.swift`](https://github.com/permissionlesstech/bitchat/blob/main/RelayController.swift) file enforces TTL constraints when forwarding packets. At line 26, the controller calculates the effective TTL for the next hop:

```swift
let ttlCap = min(ttl, TransportConfig.messageTTLDefault)

```

**Sparse topology handling** applies `bleFragmentRelayTtlCap` (7), allowing fragments to traverse the full mesh depth. **Dense topology handling** applies `bleFragmentRelayTtlCapDense` (5), cutting propagation short to prevent fragment storms while maintaining local delivery.

The hop count for any received packet is recoverable via `originTTL – receivedTTL`, implemented in [`localPackages/BitFoundation/Sources/BitFoundation/MeshPingPayload.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/MeshPingPayload.swift).

## Storage Freshness and Message Eviction

When the background sync task processes public timelines, it discards any entry older than `syncPublicMessageMaxAgeSeconds` (6 hours). This prevents stale broadcasts from persisting indefinitely. Private-media receipts follow a longer **7-day** retention window via `privateMediaReceivedLedgerTTLSeconds`, after which they are purged to reclaim storage while maintaining enough history for reliable delivery confirmation.

## Practical Implementation Examples

### Creating a Mesh Packet with TTL

To originate a message with the correct freshness metadata:

```swift
import BitFoundation

let payload = try MeshPingPayload(
    nonce: Data([0x01, 0x02]), 
    originTTL: TransportConfig.messageTTLDefault
)
let packet = BitchatPacket(
    type: MessageType.ping.rawValue,
    ttl: TransportConfig.messageTTLDefault,
    payload: payload.toData()
)

```

### Validating Message Age Before Display

To check if a stored message remains fresh according to the public sync policy:

```swift
func shouldDisplay(_ messageTimestamp: UInt64) -> Bool {
    let age = Date().timeIntervalSince1970 - Double(messageTimestamp) / 1000.0
    return age < TransportConfig.syncPublicMessageMaxAgeSeconds
}

```

## Summary

- **Hop limits**: Default mesh TTL is 7 hops; dense networks reduce fragment relay to 5 hops.
- **Storage caps**: Public messages expire after 6 hours; private media receipts last 7 days.
- **Configuration**: All policies are centralized in [`TransportConfig.swift`](https://github.com/permissionlesstech/bitchat/blob/main/TransportConfig.swift) for easy auditing.
- **Enforcement**: [`RelayController.swift`](https://github.com/permissionlesstech/bitchat/blob/main/RelayController.swift) caps TTL at line 26; [`MeshPingPayload.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MeshPingPayload.swift) tracks hop counts.

## Frequently Asked Questions

### What is the default TTL for BitChat mesh packets?

The default TTL is **7 hops**, defined as `TransportConfig.messageTTLDefault` in [`bitchat/Services/TransportConfig.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/TransportConfig.swift) at lines 8–9. This value represents the maximum number of intermediary devices a packet can traverse before being automatically discarded by the mesh layer.

### How does BitChat prevent fragment flooding in dense networks?

BitChat uses topology-aware capping via `TransportConfig.bleFragmentRelayTtlCapDense`, which sets a hard limit of **5 hops** for fragment-relay packets in dense graphs (line 30 of [`TransportConfig.swift`](https://github.com/permissionlesstech/bitchat/blob/main/TransportConfig.swift)). The [`RelayController.swift`](https://github.com/permissionlesstech/bitchat/blob/main/RelayController.swift) enforces this cap at line 26 by taking the minimum of the current TTL and the configured cap, preventing media storms while preserving local delivery.

### How long does BitChat retain private media receipts?

Private media receipts are retained for **7 days** (604,800 seconds) as specified by `TransportConfig.privateMediaReceivedLedgerTTLSeconds` at lines 21–24 of [`TransportConfig.swift`](https://github.com/permissionlesstech/bitchat/blob/main/TransportConfig.swift). After this period, the background eviction task purges these entries to prevent unbounded storage growth while maintaining sufficient history for delivery confirmation.

### Where is the message freshness policy configured in BitChat source code?

All freshness policies are centralized in [`bitchat/Services/TransportConfig.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/TransportConfig.swift), which defines constants for TTL defaults, fragment caps, sync age limits, and storage eviction windows. This single-file configuration allows operators to audit and tune the entire freshness model without modifying routing logic in [`RelayController.swift`](https://github.com/permissionlesstech/bitchat/blob/main/RelayController.swift) or storage logic elsewhere.