FAQ
General
Which class should I use, L2PSMessagingPeer or MessagingPeer?
Use L2PSMessagingPeer unless you have a specific reason not to. It is the production messaging client (default port 3006) and is the only one that provides:
- Conversation history with pagination (
history()) - Offline delivery (server queues messages for offline recipients)
- Server-side delivery acknowledgement (
message_sent/message_queuedframes) - L2PS pipeline persistence (mempool → batch → L1 proof → L1 finality)
- L2PS network isolation via
l2psUid peer_joined/peer_leftpresence events
MessagingPeer only when you need the legacy signaling-server transport (default port 3005). It is a transient relay — no history, no persistence, no offline queue, no ack.
What is the Instant Messaging Protocol?
A pair of WebSocket protocols, both with end-to-end encryption applied client-side. The L2PS protocol additionally persists messages through the Demos L2PS rollup pipeline so they can be retrieved later and delivered to peers that were offline at send time.What platforms does it support?
AnywhereWebSocket and crypto.getRandomValues are available — modern browsers, Node, mobile and desktop runtimes that ship those primitives.
Encryption
How does the encryption work?
Both classes use end-to-end public-key encryption: each peer has a public/private key pair, and the sender encrypts with the recipient’s public key. Only the recipient’s private key can decrypt.What cryptographic algorithms are used?
ml-kem-aes for message encryption (ML-KEM-768 key encapsulation + AES-GCM), and ml-dsa for signing keys that bind the encryption identity to the registration identity.
Does the protocol support perfect forward secrecy?
Yes. Each message uses a fresh ML-KEM encapsulation, so compromising one decapsulated AES key does not expose past or future messages.How are peers authenticated?
ForL2PSMessagingPeer, registration carries an ed25519 proof — the client signs register:{publicKey}:{timestamp} with the configured signFn and the server verifies it before accepting subsequent frames. The same signFn is used to authorize history requests.
For MessagingPeer, the client signs its ML-KEM public key with an ML-DSA signing key (via unifiedCrypto) during registerAndWait(), binding the encryption key to the signing identity.
Delivery and reliability (L2PSMessagingPeer)
How is delivery acknowledged?
L2PSMessagingPeer.send() resolves with one of two server frames: message_sent ({ messageHash, l2psStatus }) when the message was delivered to an online recipient, or message_queued ({ messageHash, status: "queued" }) when the recipient was offline and the message was stored.
Are messages queued if the recipient is offline?
Yes — but only withL2PSMessagingPeer. The server stores the encrypted payload and delivers it the next time the recipient connects. Inbound message frames carry an offline?: boolean flag indicating delivery from offline storage. MessagingPeer (legacy) has no offline queue.
Can I retrieve past messages?
Yes — but only withL2PSMessagingPeer. Call peer.history(peerKey, { before, limit }) and you’ll get back { messages: StoredMessage[], hasMore }. Each StoredMessage includes its status from the L2PS pipeline (delivered, queued, sent, failed, l2ps_pending, l2ps_batched, l2ps_confirmed) and the l2psTxHash once it has been included in a batch.
How does the protocol handle network issues?
Both clients use exponential-backoff reconnection (base 1000 ms, capped at 30 000 ms, up to 10 attempts).L2PSMessagingPeer re-runs register automatically after reconnection.
Implementation
How do I set up the messaging server?
It is launched automatically as part of a Demos node. The L2PS messaging server defaults to port3006; the legacy signaling server defaults to port 3005.
How do I create an L2PSMessagingPeer instance?
How do I create a MessagingPeer instance (legacy)?
The only canonical import path isimport { instantMessaging } from "@kynesyslabs/demosdk". There is no@kynesyslabs/demosdk/instant_messagingsubpath.
How do I send a message?
WithL2PSMessagingPeer, encrypt the plaintext yourself, serialize to SerializedEncryptedMessage ({ ciphertext, nonce, ephemeralKey? } with base64 / hex fields), then call peer.send(to, encrypted, messageHash).
With MessagingPeer, call peer.sendMessage(targetId, plaintext) — encryption is performed automatically against the recipient’s public key.
How do I handle incoming messages?
How do I handle errors?
L2PSMessagingPeer server-side error codes are: INVALID_MESSAGE, REGISTRATION_REQUIRED, INVALID_PROOF, PEER_NOT_FOUND, L2PS_NOT_FOUND, L2PS_SUBMIT_FAILED, RATE_LIMITED, INTERNAL_ERROR.
How do I handle peer presence?
Troubleshooting
What should I do if I can’t connect?
- Verify the server is running and the URL is reachable (port
3006for L2PS,3005for legacy). - Check network and firewall rules.
- For
L2PSMessagingPeer, make sure yoursignFnactually returns a hex ed25519 signature of the input string — bad proofs surface asINVALID_PROOF. - For
MessagingPeer, make sure yourunifiedCryptoinstance has identities generated (see the Quickstart).
What should I do if messages are not being delivered?
- With
L2PSMessagingPeer, look at the resolved value ofsend()—message_queuedmeans the recipient is offline, not that delivery failed. The recipient will receive it on next connect withoffline: true. - With
MessagingPeer, the recipient must be connected at the moment of send; there is no offline queue. - Confirm the recipient public key / client ID is correct, and check
onErrorfor server-reported issues.