3. Author & deploy the enclave
An enclave is minted by submitting a signed manifest to the node. The manifest is the enclave's constitution — its RBAC: the roles, the event types, and exactly who may do what. The node enforces it on every commit, forever. In this step you'll write that manifest by hand (no bundled template) and deploy it.
Author the manifest
Your app writes two events — public posts and private notes — and a dataview
fans the public ones out. That's one role (OWNER), one service trait (dataview), and
three rules. Write it:
// the constitution for your enclave
export const personal = {
name: 'Personal',
description: 'single-owner: public posts + private notes',
manifest: {
states: ['OWNER'], // one role — you
traits: ['dataview(1)'], // a service role that may fan posts out
readers: [{ type: 'OWNER', reads: '*' }], // the owner reads everything
grants: [ // the owner may (un)appoint a dataview
{ event: 'Grant', operator: ['OWNER'], scope: ['OUTSIDER'], trait: ['dataview'] },
{ event: 'Revoke', operator: ['OWNER'], scope: ['OUTSIDER'], trait: ['dataview'] },
],
customs: [
{ event: 'public', operator: 'OWNER', ops: ['C', 'U', 'D'] }, // your public posts
{ event: 'public', operator: 'dataview', ops: ['P'] }, // dataview may Push them → the feed
{ event: 'private', operator: 'OWNER', ops: ['C', 'U', 'D'] }, // owner-only notes
],
init: [{ identity: '<owner_pub>', state: 'OWNER', traits: [] }], // bootstrap: you, as OWNER
},
}Read it as a permission table: only the OWNER (your key) may write public and private; the
dataview role gets Push access to your public events — and only those, never your private
notes. <owner_pub> is a placeholder the deploy step fills with your key.
Deploy it — and wire the dataview in
Minting is two calls: flatten the authored manifest into the wire RBAC, then createEnclave.
The id is derived deterministically from the manifest + your key, so it's idempotent — rerun and
you get the same enclave. The script also bakes your dataview's push URL into the manifest, so the
node starts pushing the moment the enclave exists — no separate registration.
// author once, mint on the node
import { flattenEnclaveManifest } from '@enc-protocol/protocol-runtime'
import { NetworkAdapter } from '@enc-protocol/client/network-adapter.js'
import { ownerFromMnemonic } from './owner.mjs'
import { personal } from './personal.manifest.mjs'
import { readFileSync, writeFileSync } from 'node:fs'
const NODE_URL = process.env.NODE_URL || 'http://localhost:8787'
const DATAVIEW_URL = process.env.DATAVIEW_URL || 'http://localhost:8789'
const owner = ownerFromMnemonic(readFileSync('owner.seed', 'utf8')) // your seed from step 1
// 1. authored manifest → wire RBAC manifest for THIS owner
const wire = flattenEnclaveManifest(personal).enclaveManifest(owner.publicKeyHex)
// 2. name the dataview in the manifest: { identity, endpoint } in initial_state.
// The node registers the push route at mint — the endpoint must be the full /push URL.
const { pubHex: dataviewPub } = await (await fetch(DATAVIEW_URL + '/identity')).json()
wire.RBAC.initial_state.dataview = [{ identity: dataviewPub, endpoint: DATAVIEW_URL + '/push' }]
// 3. sign + submit → the node mints it and returns its sequencer pubkey
const { enclave_id, sequencer } = await new NetworkAdapter(NODE_URL, '', owner).createEnclave(wire)
// the frontend reads this — node URL, sequencer pubkey, enclave id
writeFileSync('enc.config.json', JSON.stringify(
{ nodeUrl: NODE_URL, seqPub: sequencer, enclaves: { Personal: enclave_id } }, null, 2))
console.log('Personal enclave →', enclave_id, '\nwrote enc.config.json')Run it with your node and dataview up:
NODE_URL=http://localhost:8787 node deploy-enclave.mjs
# Personal enclave → fff25e6a6645c198cb87e854927b252eb9582a7adcc43e3c2daaa7910b785b71
# wrote enc.config.jsonThe node admits the mint because the manifest commit's from is your owner pubkey — the very
identity your step-1 hook allows. The
flattened wire manifest it stores is a flat schema plus your baked-in dataview:
{ "enc_v": 1, "nonce": "…", "RBAC": {
"schema": [
{ "event": "public", "role": "OWNER", "ops": ["C","U","D"] },
{ "event": "public", "role": "dataview", "ops": ["P"] },
{ "event": "private", "role": "OWNER", "ops": ["C","U","D"] }
],
"states": ["OWNER"], "traits": ["dataview(1)"],
"initial_state": {
"OWNER": ["<your pubkey>"],
"dataview": [{ "identity": "<dataview pubkey>", "endpoint": "http://localhost:8789/push" }]
}
} }enc.config.json now carries everything the browser app needs: the node URL, the sequencer
pubkey (seqPub, used to encrypt reads), and your enclave id.
You have a live enclave on your node, owned by your key, already feeding your dataview — where your posts and private notes will live.
Next: Build the frontend app →