Client API
The client instance provides methods for publishing events, managing contracts, querying state, and syncing.
The client is the primary interface for interacting with the Decentrl network. It's created from an app definition and handles all cryptographic operations internally.
const client = app.createClient({
mediatorDid: 'did:web:mediator.decentrl.io',
})Identity
The client manages your cryptographic identity through client.identity:
// Create a new identity (generates keys, DID, registers with mediator)
await client.identity.create({ alias: 'alice', mediatorDid: 'did:web:mediator.decentrl.io' })
// Access current identity
const identity = client.identity.getIdentity()
// { did, alias, mediatorDid, mediatorEndpoint, keys, mediatorContract }
// Serialize for persistence
const serialized = client.identity.serialize()
// Load a previously serialized identity
client.identity.load(serialized)
// Listen for identity changes
const unsub = client.identity.onChange((identity) => { ... })Publishing Events
await client.publish('message', {
text: 'Hello Bob',
threadId: crypto.randomUUID(),
})Under the hood, publish:
- Validates the payload against the Zod schema
- Evaluates tag templates from the event definition
- Signs the event envelope with Ed25519
- Encrypts with the contract root secret and sends to recipient's mediator
- Re-encrypts with storage key and saves on own mediator with encrypted tags
- Updates local state through the matching reducer
Options:
await client.publish('message', data, {
recipient: bobDid, // send to specific recipient
ephemeral: true, // don't store locally (typing indicators, presence)
})Public Events
Publish signed, unencrypted events to a public channel:
// Publish a public event (validated against publicEvents schema)
const { publicEventId } = await client.publishPublic('blog.post', {
title: 'Hello World',
body: 'My first public post',
})
// Delete a public event
await client.deletePublicEvent(publicEventId, 'blog.post')Under the hood, publishPublic:
- Validates the payload against the public event's Zod schema
- Evaluates tag templates from the definition
- Signs
{ channel_id, event, tags, timestamp }with Ed25519 - Sends a
ONE_WAY_PUBLICcommand to the mediator - Updates local state through the matching
public:reducer
Querying Public Events
Read public events from any identity — no contract or identity required:
// Query a publisher's public events
const result = await client.queryPublicEvents({
publisherDid: 'did:decentrl:alice:...',
channelId: 'blog',
tags: ['featured'],
pagination: { page: 0, pageSize: 20 },
})
// result.data — array of PublicEventResult
// result.pagination — { page, pageSize, total }Each result includes the event content, tags, timestamp, and event signature for verification.
Subscribing to Channels
For continuous polling of an external identity's public channel:
const { unsubscribe } = client.subscribeToChannel({
did: 'did:decentrl:alice:...',
channelId: 'blog',
pollIntervalMs: 10000,
})
// Events are automatically validated, signature-verified,
// and dispatched to channel: reducers
// Stop polling
unsubscribe()Public Event Listeners
// Listen for public events (both own and channel)
const unsub = client.onPublicEvent((envelope) => {
console.log(envelope.type, envelope.data, envelope.meta.publisherDid)
})Contracts
Manage communication contracts through client.contracts:
// Send a contract request to another identity
await client.contracts.request(bobDid)
// Query pending inbound contract requests
const pending = await client.contracts.getPending()
// Accept a pending contract request
await client.contracts.accept(
pending[0].id,
pending[0].encryptedPayload,
pending[0].requestorEphemeralPublicKey,
)
// Refresh contracts from mediator
await client.contracts.refresh()
// Get all active contracts
const contracts = client.contracts.getActiveContracts()
// Find a specific contract by DID
const contract = client.contracts.getContractByDid(bobDid)
// Listen for contract changes
const unsub = client.contracts.onChange(() => { ... })State
Access reactive state derived from your reducers:
// Get current state snapshot
const state = client.getState()
// { messages: [...], reactions: [...], ... }
// Subscribe to state changes
const unsub = client.subscribe((state) => {
console.log('Messages:', state.messages.length)
})Querying Events
// Query stored events with filters
const result = await client.query({
tags: ['thread.abc'],
afterTimestamp: Date.now() / 1000 - 86400,
pagination: { page: 0, pageSize: 50 },
})
// result.data — array of EventEnvelope
// result.pagination — { page, pageSize, total }
// Query and process events through reducers
const { processed, total } = await client.queryAndProcess({
tags: ['thread.abc'],
})
// Listen for individual events
const unsub = client.onEvent((envelope) => {
console.log('Event:', envelope.type, envelope.data)
})Sync
Start automatic background synchronization:
client.startSync({
intervalMs: 3000, // poll interval
websocket: true, // enable WebSocket push
autoRenew: {
enabled: true,
threshold: 0.2, // renew when 20% of contract lifetime remains
},
onError: (err) => console.error('Sync error:', err),
})
// Manual sync
await client.sync()
// Stop sync
client.stopSync()
// Connection status
const status = client.getConnectionStatus()
// 'disconnected' | 'connecting' | 'authenticating' | 'connected'
const unsub = client.onConnectionStatusChange((status) => { ... })Lifecycle
// Reset client state (identity, contracts, state)
client.reset()