Docs/Core Concepts

Deterministic IDs

How atom IDs are derived — salted keccak256 over the exact bytes — and why the same content is the same atom everywhere.

Every identity in Intuition is a pure function of content. No counters, no registries, no allocation step — the ID is the hash. That single decision is what lets the graph be built offchain for free and still line up perfectly with the chain later.

The derivation#

code
atomId = keccak256(ATOM_SALT ‖ keccak256(utf8(atomData)))

The atom data is UTF-8 encoded and hashed; the result is prefixed with a protocol-wide salt (ATOM_SALT, itself keccak256 of the string 'ATOM_SALT') and hashed again. This mirrors the Solidity side exactly — keccak256(encodePacked(['bytes32','bytes'], [ATOM_SALT, keccak256(data)])) — so the ID your TypeScript derives is the ID the MultiVault contract registers.

derive.ts
import { calculateAtomId } from "@0xintuition/ids";

const id = calculateAtomId("https://github.com/vercel/next.js");
// → 0x3a52c8…f577

calculateAtomId("hello");
// → 0xa0e157e5fa1b17d3b54ec73622ce3317296920a06502661617613d59f58e947e
// published test vector — verify it yourself

Deterministic: same value, same ID, every time. Sync, pure, no transaction, no network call, no wallet, no gas. Triples get the same treatment — calculateTripleId derives from (subject, predicate, object), and calculateCounterTripleId derives the opposing claim's ID.

Why it matters#

Nothing migrates. Before anyone classifies or enriches a record, its ID is already determined. Every offchain contribution attaches to a row the onchain contract will later honor — when the atom is finally published, nothing gets reindexed and nothing gets rebuilt.

No coordination. Offchain databases and the chain resolve to the same record without talking to each other. Two apps that never met converge on the same atom because they hashed the same bytes.

Dedup for free. The same fact hashes to the same atom, every run — which is why deterministic IDs are the backbone of agent memory, directories, and any system that ingests the same world twice.

The bytes are the identity — canonicalize them#

Since the ID hashes the exact bytes, byte-level discipline is identity discipline:

canonicalize.ts
import { calculateAtomId } from "@0xintuition/ids";

// ?si=… tracking params are not identity — strip them
const value  = canonicalize(pastedUrl);
const atomId = calculateAtomId(value);

The same rule extends into atom data. A real example: Flume's "Never Be Like You" lives at two different Spotify URLs. Two apps that each hash their own variant URL into sameAs produce different bytes, different IDs, two vaults — the song's history and signal split across duplicates. Two apps that resolve to one canonical sameAs at creation produce matching bytes, matching IDs, and one vault where everyone's stakes, tags, and reviews compound.

Read the graph before you create. Reuse the atoms, predicates, and canonical IDs that already exist. Shared markets reward convergence — the more contributors land on one record, the more its signal compounds — so the incentives pull everyone toward common standards.

One rule for downstream code#

Packages and apps built on top of @0xintuition/ids reference stable keys and resolve to IDs through the package — they don't copy hashes into source. Enshrined predicates ship precomputed IDs the same way: derived once from canonical bytes, imported everywhere. See Atoms for what goes into the bytes, and Offchain to Onchain for where the ID meets the contract.