The custom operation turns VIZ into a data layer for your own app. You write arbitrary JSON under an app id; the chain timestamps and orders it; you index it off the op stream. No smart contracts, no schema migrations — just append-only, signed, publicly verifiable events. It's the “VIZ as a platform” primitive.
The chain doesn't interpret your payload — your app defines the meaning. A game, a social feed, a marketplace, a poll: all of it is custom ops plus an indexer.
Step 1 — Write an event
npm install @viz-cx/core @viz-cx/apiid namespaces your protocol so you can filter later; json is a string payload. Custom ops use the regular authority, so sign with the regular key and list the signer in requiredRegularAuths.
import { createClient } from '@viz-cx/core'
// custom ops are authorized by the REGULAR authority, so sign with the
// regular key (the 'activeKey' option is simply the signing key).
const client = createClient({
account: 'alice',
activeKey: process.env.VIZ_REGULAR_KEY!,
endpoint: 'https://node.viz.cx',
})
// Emit an app event. 'id' namespaces your protocol; 'json' is a string
// payload. List the accounts whose regular authority must sign.
const result = await client.custom({
id: 'chess', // your app id (keep it short & unique)
json: JSON.stringify({ t: 'move', game: 42, from: 'e2', to: 'e4' }),
requiredRegularAuths: ['alice'],
})
console.log('Wrote move:', result.id)Step 2 — Index it live
Subscribe to custom ops and keep the ones matching your id. This is your real-time indexer — fold each event into whatever state your app holds.
import { createApiClient } from '@viz-cx/api'
const api = createApiClient()
// Index your protocol live: subscribe to custom ops and keep the ones
// tagged with your app id.
const stream = api.streamOps({ op_type: 'custom' })
for await (const { body, timestamp } of stream) {
if (body.id !== 'chess') continue // ignore other apps' ops
const payload = JSON.parse(body.json)
console.log(timestamp, payload) // { t: 'move', game: 42, ... }
}Step 3 — Backfill from history
To rebuild state on startup, walk an account's history and replay its past custom ops. Use get_ops_in_block across a block range to reconstruct the whole protocol.
import { createClient } from '@viz-cx/core'
const client = createClient({ endpoint: 'https://node.viz.cx' })
// Backfill an account's past custom ops from its history (newest first).
const history = await client.api.getAccountHistory('alice', -1, 100)
for (const [, item] of history) {
const [opType, body] = item.op
if (opType === 'custom' && body.id === 'chess') {
console.log(JSON.parse(body.json))
}
}Keep payloads small and version them (e.g. a v field) — custom ops are permanent, so a forward-compatible schema saves you later. For push-based indexing without a socket, wire a custom filter into a webhook.