Publishing Events
Events are the atomic unit of communication in Decentrl — signed, encrypted, and dual-delivered.
Formal spec: DCTRL-0005 — Event Encryption and Storage defines event envelopes, dual delivery, encrypted tags, and the forward secrecy trade-off.
All communication and state changes in Decentrl are represented as cryptographically signed, encrypted events. Applications function as event processors: they publish events, query event streams, and reduce them into local application state.
Anatomy of an Event
An event has a type and a data payload:
{
type: "chat.message",
data: {
id: "550e8400-e29b-41d4-a716-446655440000",
chatId: "abc",
content: "Hello Bob",
sender: "did:decentrl:YWxpY2U:z6MkpT...",
timestamp: 1699123456
}
}When published, the event is wrapped in a signed envelope, encrypted, and dual-delivered — one copy to the recipient's mediator, one copy to the sender's own mediator.
Event Flow: Alice Sends a Message
1. Alice publishes a chat.message event:
- Encrypt with contract root secret → send to Bob's mediator (TWO_WAY_PRIVATE)
- Encrypt with storage key → save on Alice's mediator with encrypted tags
2. Alice's local state updates immediately (optimistic update):
- messages["abc"] = [...messages["abc"], newMessage]
3. Bob's client syncs (poll or WebSocket push):
- Decrypt with root secret → verify signature → re-encrypt with storage key → store
- Apply to local state: messages["abc"] = [...messages["abc"], newMessage]Ephemeral Events
Some events don't need to be stored. Presence indicators, typing signals, and online status are transient — they matter right now but not later.
Events tagged with no tags (tags: []) are sent via TWO_WAY_PRIVATE for real-time delivery but are not stored on the sender's mediator. They exist only in transit and in the recipient's pending event queue — once acknowledged, they vanish.
chat.presence → tags: [] // ephemeral — not stored, just delivered
chat.message → tags: ["chat.abc"] // persistent — stored and queryableDefining Events with the SDK
The SDK lets you define your event schema declaratively with Zod:
import { defineDecentrlApp } from '@decentrl/sdk'
import { z } from 'zod'
const app = defineDecentrlApp({
events: {
'chat.message': {
schema: z.object({
id: z.string().uuid(),
chatId: z.string(),
content: z.string(),
}),
tags: ['chat.${chatId}'],
},
'chat.reaction': {
schema: z.object({
messageId: z.string().uuid(),
emoji: z.string(),
}),
tags: ['chat.${messageId}'],
},
'chat.presence': {
schema: z.object({ timestamp: z.number() }),
tags: [], // empty tags = ephemeral
},
},
state: { /* ... reducers ... */ },
})Publishing is a single method call — the SDK handles signing, encryption, and dual-delivery:
const client = app.createClient({ mediatorDid: 'did:web:mediator.decentrl.io' })
await client.publish('chat.message', {
id: crypto.randomUUID(),
chatId: 'abc',
content: 'Hello Bob',
})