The worked path in
Build on NULLIUS
Everything reduces to cells, turns and receipts — so building an app is defining a cell's predicate, issuing capabilities, and signing turns. Here is the whole loop in real code you can paste into node.
1 · Make a cell with a predicate
A budget that cannot be exceeded is just a cell whose program says so.
import { genesis, makeCell, faucet } from "./kernel/executor.js";
import { genKeypair, randomHex } from "./kernel/crypto.js";
const owner = await genKeypair();
const secret = await randomHex(32); // the cell's cap-root secret
let state = genesis();
state.cells["cell:treasury"] = makeCell("cell:treasury", {
owner: owner.pub, rootSecret: secret,
predicate: { kind: "budget", asset: "GRAIN", limit: 500 }, // lifetime cap
});
state = (await faucet(state, { recipient: "cell:treasury", amount: 1000 })).state;
2 · Issue a capability, then attenuate it
Narrow, never amplify. Hand an agent a capped token and it physically cannot exceed it.
import { issue, attenuate } from "./kernel/caps.js";
const full = await issue(secret, {
cell: "cell:treasury", rights: ["transfer", "write"], holder: owner.pub,
});
// give an agent transfer-only, max 50 per turn
const agentCap = await attenuate(full, {
rights: ["transfer"],
caveat: { type: "max_amount", limit: 50 },
});
3 · Sign a turn and apply it
import { signTurn, applyTurn } from "./kernel/executor.js";
const turn = await signTurn({
actorPub: owner.pub, actorPriv: owner.priv, nonce: await randomHex(),
actions: [{ verb: "transfer", asset: "GRAIN", amount: 40,
from: "cell:treasury", to: "cell:agent", cap: agentCap }],
});
const r = await applyTurn(state, turn);
if (r.ok) { state = r.state; console.log("receipt", r.receipt.q); }
else { console.log("refused:", r.reason); } // the rejection is the product
That is the entire builder surface: define a predicate, issue and attenuate capabilities, sign turns. The playground is these three steps wired to buttons. The organs (credit lines, encrypted groups, custody handoffs) are each a few lines on this same executor — no new kernel entry.