Docs/Core Concepts

Triples & Predicates

Subject–predicate–object claims with deterministic IDs, connected by a curated registry of verbs — 25 enshrined, 108 proposed.

Atoms are things; triples are claims about them. A triple is a subject–predicate–object statement where every position is an atom:

code
Alive       — createdBy   → SoDown
Snow Crash  — authoredBy  → Neal Stephenson
next.js     — use         → React
Song        — listedIn    → Playlist

Each triple gets its own deterministic ID, derived from its (subject, predicate, object). Two apps making the same claim converge on the same triple — no duplicates, no merge step.

Predicates are a shared grammar#

@0xintuition/predicates ships a curated registry of verbs — 25 enshrined, 108 proposed — the vocabulary the protocol recognizes: createdBy, sameAs, hasTag, listedIn, endorse, authoredBy, and the rest. Each predicate is defined once — one file, one canonical record carrying its key, spec, machine-readable semantics, and precomputed atom ID — and used everywhere. Because every app references the same predicate IDs, joins line up and queries return consistent shapes across the whole graph. The full registry is browsable at /predicates.html.

A schema.org property becomes a predicate only when Intuition promotes it and decides its object model. That curation is the point: shared vocabulary is what keeps independent apps converging instead of fragmenting. See the predicates package for the two-tier promotion model.

Building triples#

Each predicate has its own tree-shakeable import — only the verbs you use ship in your bundle:

create-triples.ts
import { multiVaultCreateTriples } from "@0xintuition/protocol";
import { useId }                   from "@0xintuition/predicates/use";
import { createdById }             from "@0xintuition/predicates/createdBy";
import { calculateAtomId }         from "@0xintuition/ids";
import { parseEther }              from "viem";

const reactId  = calculateAtomId("https://react.dev");
const vercelId = calculateAtomId("https://vercel.com");

const per = parseEther("0.1");
await multiVaultCreateTriples(cfg, {
  args: [
    [id,      id          ],  // subjects
    [useId,   createdById ],  // predicates — registry atom IDs
    [reactId, vercelId    ],  // objects
    [per,     per         ],  // value per triple
  ],
  value: per * 2n,
});

Or resolve a predicate by name with the ergonomic helper:

build-triple.ts
import { buildTripleByName } from "@0xintuition/primitives/triple";

const { value: t } = buildTripleByName(aliveId, "createdBy", soDownId);
// → { subjectId, predicateId, objectId } — deterministic IDs throughout

Prefer the aggregate import? import { PREDICATE_IDS } from "@0xintuition/predicates" still works.

Atom data vs triple metadata#

The same schema.org property can matter twice, and the decisions are separate. The atom-data column (classifications fields) is what gets embedded in the atom at creation. The triple-metadata column (predicates) is which verbs are recommended for claims about the atom later:

Book atom data Book triple metadata
decided by classifications.fields registry predicates, recommended via each classification's metadataPredicates
when at creation any time after
example name, author, isbn, sameAs authoredBy, hasCategory, hasDescription, sameAs

Claims are multiplayer — and contestable#

Once an atom is onchain, anyone in any app derives the same ID and attaches their own triples. The shared predicate registry keeps everyone's vocabulary aligned, so independent claims pile onto one record instead of scattering.

Counter-triples carry disagreement. A claim isn't just assertable — it's contestable. Opinions become portable, stake-weighted claims with deterministic IDs (calculateCounterTripleId derives the counter side), and vaults make contested claims legible. See Offchain to Onchain for how TRUST prices both sides.