Ricochet Dart Client

Dart client library for the Ricochet P2P store-and-forward messaging network.

This is a protocol-level client library — it provides a high-level API for sending/receiving messages, managing mailboxes, storing documents, and tracking presence over the Ricochet S&F protocol. It is designed to work with any Ricochet-compatible server implementation.

This library does not manage networking. It expects a fully configured dart_libp2p Host (and optionally PubSub) to be injected by the application. The application is responsible for creating the libp2p host, configuring transports, discovery, and connection management. This library only operates on top of that stack by opening libp2p streams to S&F servers using the Ricochet protocol IDs.

Installation

dependencies:
  ricochet: ^0.2.1

Usage

import 'package:ricochet/ricochet.dart';

Prerequisites

The application must provide:

  • A dart_libp2p Host — already started, with transports configured and the S&F server addresses in its peerstore.
  • A PubSub instance (optional) — only needed for presence tracking. The same GossipSub instance used by the rest of the application.

This library never creates or manages these — it only uses them.

Create and start the client

// The application creates and owns the libp2p host and pubsub
final host = await buildLibp2pHost(...);  // your setup
final pubsub = PubSub(host);             // your setup

// Configure with your preferred S&F servers (MX-style priority)
final config = SFClientConfig.withServers([
  SFServerPreference(serverId: serverPeerId, priority: 10),
]);

// Inject the host and pubsub into the client
final client = SFClient(
  host: host,        // required — application-managed libp2p Host
  config: config,
  pubsub: pubsub,   // optional — enables presence tracking
);

await client.start();

Send a message

Messages are automatically routed via S&F servers for offline delivery.

final result = await client.sendMessage(
  recipient: recipientPeerId,
  payload: utf8.encode('Hello!'),
  priority: MessagePriority.normal,
  folderPath: 'inbox',       // target folder (default: inbox)
  persistent: false,          // remove after reading
);

if (result.success) {
  print('Stored at server: ${result.storedAtServer}');
}

Retrieve messages

// Own messages
final messages = await client.retrieveMessages();

// From another peer's public mailbox
final publicMessages = await client.retrieveMessages(
  targetPeerId: otherPeerId,
  folderPath: 'announcements',
);

Encrypt the payload

Off by default. Build the client with a PayloadEncryptor from the identity's Ed25519 seed and pass encrypt: true; the payload is sealed with NaCl box (X25519 from the two identity keys, XSalsa20-Poly1305) in the same format the Go client uses, so either client can read what the other sent. The ciphertext is bound to the recipient, folder and message id, and retrieveMessages refuses one the server moved or relabelled. Retrieval opens sealed messages automatically; without an encryptor an encrypted message fails retrieval with a PayloadDecryptException that names this layer, so an application running its own end-to-end layer above can tell the two apart.

final client = SFClient(
  host: host,
  config: config,
  encryptor: PayloadEncryptor.fromEd25519Seed(identitySeed), // 32 bytes
);

await client.sendMessage(
  recipient: recipientPeerId,
  payload: utf8.encode('Hello!'),
  encrypt: true,
);

This layer has no forward secrecy (one static key pair per identity; a ratchet belongs above it) and does not detect a replay of the same ciphertext into the same folder.

IMAP-style flag operations

// Mark as read
await client.markDelivered(messageIds: ['msg-1', 'msg-2']);

// Update flags
await client.updateFlags(messageId: 'msg-1', addFlags: MessageFlags.flagged);

// Soft-delete then expunge
await client.updateFlags(messageId: 'msg-1', addFlags: MessageFlags.deleted);
await client.expunge();

// Hard-delete
await client.deleteMessages(messageIds: ['msg-1']);

Mailbox management

final mailboxes = client.mailboxes;

// Create a shared mailbox
await mailboxes.createMailbox(
  folderPath: 'team-updates',
  type: MailboxType.shared,
);

// Grant access
await mailboxes.grantAccess(
  folderPath: 'team-updates',
  type: MailboxType.shared,
  targetPeerId: colleaguePeerId,
  accessMode: AccessMode.readWrite,
);

// List ACLs
final acl = await mailboxes.listACL(
  folderPath: 'team-updates',
  type: MailboxType.shared,
);

Document store

Per-user key-value document storage with ETag versioning.

// Store a document
final putResult = await client.putDocument(
  path: 'profile',
  content: utf8.encode(jsonEncode({'name': 'Alice'})),
  contentType: 'application/json',
);

// Retrieve a document
final doc = await client.getDocument(path: 'profile');

// Conditional GET (returns 304 if unchanged)
final doc2 = await client.getDocument(
  path: 'profile',
  ifNoneMatch: putResult.etag,
);

// JSON Merge Patch
await client.patchDocument(
  path: 'profile',
  patch: {'bio': 'Updated bio'},
);

// List all documents
final listing = await client.listDocuments();

Peer directory

Opt-in server directory for peer discovery.

// Join the directory
await client.joinDirectory(displayName: 'Alice', bio: 'Hello!');

// Browse the directory
final results = await client.browseDirectory(limit: 20);

// Leave the directory
await client.leaveDirectory();

Presence tracking

Requires PubSub (GossipSub) to be passed when constructing SFClient.

final tracker = client.presenceTracker;

// Track a contact's online/offline status
tracker.trackContact(contactPeerId, serverPeerId);

// Listen for changes
tracker.contactPresenceChanges.listen((change) {
  print('${change.peerId} is now ${change.state.name}');
});

// Check status
if (tracker.isOnline(contactPeerId)) {
  print('Contact is online');
}

Ricochet URIs

Stable, owner-centric resource addressing.

// Format: ricochet://<ownerPeerId>/<resourceType>/<path>[?servers=...]
final uri = RicochetUri(
  ownerPeerId: peerId,
  resourceType: RicochetResourceType.doc,
  path: 'profile',
);

print(uri.toString());
// ricochet://12D3KooW.../doc/profile

Architecture

lib/
  ricochet.dart              # Barrel export file
  client/
    sf_client.dart           # High-level client API (main entry point)
    sf_client_config.dart    # Client configuration
    mailbox_manager.dart     # Mailbox CRUD and ACL management
    message_sender.dart      # Message routing with retries & failover
    server_selector.dart     # MX-style server selection & health checking
    presence_tracker.dart    # GossipSub presence subscription
  core/
    sf_message.dart          # Core message type + request/response types
    mailbox_address.dart     # Mailbox addressing (owner/folder/type)
    mailbox_types.dart       # MailboxType, AccessMode enums
    message_types.dart       # MessagePriority enum
    message_flags.dart       # IMAP-style flags (seen, flagged, deleted, draft)
  protocol/
    maa/                     # Mail Access Agent (read path)
      access_handler.dart    # Client-side retrieve, flags, expunge, delete
      access_frame.dart      # Wire encoding/decoding
    msa/                     # Mail Submission Agent (write path)
      submission_handler.dart  # Client-side message submission
      submission_frame.dart    # Wire encoding/decoding
    mma/                     # Mailbox Management Agent (admin)
      admin_protocol.dart    # Request/response/error types
      admin_frame.dart       # Wire encoding/decoding
    sda/                     # Store Document Access
      document_handler.dart  # Client-side GET/PUT/PATCH/HEAD/DELETE/LIST
      document_frame.dart    # Wire encoding/decoding
      document_crdt.dart     # CRDT merge support
    mailbox_notify_protocol.dart  # Push notification protocol
    sf_frame.dart            # Legacy frame utilities
    stream_utils.dart        # Stream read helpers
  presence/
    presence_cache.dart      # TTL-based presence cache
    presence_event.dart      # Presence event/heartbeat types
  registry/
    peer_preferences.dart    # MX-style server preference records
  uri/
    ricochet_uri.dart        # Owner-centric URI scheme
    ricochet_uri_resolver.dart  # URI resolution via DHT/direct

Protocol IDs

Protocol ID Purpose
MSA /sf-network/submit/1.0.0 Message submission
MAA /sf-network/access/1.0.0 Message retrieval & flags
MMA /sf-network/admin/1.0.0 Mailbox management
SDA /ricochet/store/doc/1.0.0 Document storage
Notify /ricochet/mailbox-notify/1.0.0 Push notifications

Server compatibility

This client works with any server implementing the above protocols:

  • ricochet — Reference S&F server implementation
  • go-ricochet — Go S&F server implementation

Libraries

client/mailbox_manager
Client API for remote mailbox management
client/message_sender
client/presence_tracker
Client-side presence tracking via GossipSub subscription
client/server_selector
client/sf_client
client/sf_client_config
core/mailbox_address
Mailbox addressing for folder-based store-and-forward
core/mailbox_types
Mailbox type definitions for store-and-forward system
core/message_flags
IMAP-style message flags
core/message_types
core/sf_message
crypto/payload_encryption
Payload encryption compatible with the Go client's bound NaCl box (go-ricochet pkg/client/encryption.go).
presence/heartbeat_assembler
Reassembly of paged presence heartbeats.
presence/presence_cache
presence/presence_event
Presence event models for GossipSub-based presence signaling
protocol/maa/access_frame
MAA Frame Encoding/Decoding
protocol/maa/access_handler
Mail Access Agent (MAA) Protocol Handler - Client Side
protocol/mailbox_notify_protocol
Libp2p protocol for mailbox notifications
protocol/mma/admin_frame
Frame encoding/decoding for mailbox management protocol (MMA)
protocol/mma/admin_protocol
Mailbox Management Agent (MMA) Protocol definitions
protocol/msa/submission_frame
MSA Frame Encoding/Decoding
protocol/msa/submission_handler
Mail Submission Agent (MSA) Protocol Handler - Client Side
protocol/overmedia/discovery_handler
OverMedia Discovery Protocol Handler - Client Side
protocol/overmedia/overmedia_frame
OverMedia Frame Encoding/Decoding
protocol/overmedia/recording_handler
OverMedia Recording Protocol Handler - Client Side
protocol/overmedia/room_handler
OverMedia Room Protocol Handler - Client Side
protocol/overmedia/session_handler
OverMedia Session Protocol Handler - Client Side
protocol/overmedia/token_handler
OverMedia Token Protocol Handler - Client Side
protocol/sca/collection_frame
SCA Frame Encoding/Decoding
protocol/sca/collection_handler
Store Collection Access (SCA) Protocol Handler - Client Side
protocol/sda/document_crdt
Document CRDT (Conflict-free Replicated Data Type)
protocol/sda/document_frame
SDA Frame Encoding/Decoding
protocol/sda/document_handler
Store Document Access (SDA) Protocol Handler - Client Side
protocol/sf_frame
protocol/sfa/feed_frame
SFA Frame Encoding/Decoding
protocol/sfa/feed_handler
Store Feed Access (SFA) Protocol Handler - Client Side
protocol/stream_utils
registry/peer_preferences
ricochet
Ricochet — Dart client library for P2P store-and-forward messaging.
test_harness/collection_tests
Collection Store (SCA) integration tests.
test_harness/document_tests
Document Store (SDA) integration tests.
test_harness/feed_tests
Feed Store (SFA) integration tests.
test_harness/mailbox_tests
Mailbox (MSA/MAA/MMA) integration tests.
test_harness/test_runner
Shared test infrastructure for store integration tests.
uri/ricochet_uri
uri/ricochet_uri_resolver