Docs/Packages

ids

The identity layer — calculateAtomId and friends derive stable, content-addressed IDs that are the same offchain and onchain.

@0xintuition/ids answers the last question in the data model — how does everything get deterministic IDs? It computes stable, content-derived identifiers for atoms, predicates, and triples — the same value offchain and onchain. Everything above it in the stack resolves through it. One deterministic ID, everywhere.

The API is a pure function#

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

const value = "https://github.com/vercel/next.js";
const id    = calculateAtomId(value);
// → 0x3a52c8…f577   (branded AtomId — real output, keccak256-derived)

// Deterministic: same value → same id, every time.
// Sync, pure, no transaction, no network call.

No wallet, no gas, no network. The package also exports calculateTripleId — a triple's ID derives from its (subject, predicate, object), so two callers creating the same triple converge on the same ID with no duplicates and no merge step — and calculateCounterTripleId for the opposing claim. OAuth atoms anchor Google, GitHub, Apple, and X accounts through the same content-addressed scheme.

The derivation, exactly#

derivation.ts
// The protocol's atom-ID derivation:
//   atomId = keccak256(ATOM_SALT ‖ keccak256(utf8(atomData)))
//
// ATOM_SALT = keccak256(toHex('ATOM_SALT'))
//   0xc50959b2b0264fed58f3489f13cdf8345df0911245cc2b741070787ee7aceaa2
//
// On the Solidity side this is
//   keccak256(encodePacked(['bytes32','bytes'], [ATOM_SALT, keccak256(data)]))
// — encodePacked of bytes32 + bytes is plain concatenation.

calculateAtomId("hello");
// → 0xa0e157e5fa1b17d3b54ec73622ce3317296920a06502661617613d59f58e947e
//   published test vector — the contract derives the same value

The double hash with a protocol salt namespaces atom IDs away from raw keccak256 output, and the packed encoding matches the MultiVault contract byte for byte. Same content, same ID — in your app, in anyone else's, and onchain. The conceptual walkthrough is Deterministic IDs.

Why identity is its own package#

Splitting identity into the bottom layer buys three things:

  • Offchain-first is safe. Before anyone classifies or enriches a record, its ID is already determined — the same salted keccak256 the MultiVault contract uses. Offchain databases and the chain resolve to the same record without coordination, and nothing migrates or gets reindexed at publish time.
  • The graph converges. Independent apps hashing the same canonical bytes land on the same atom, the same triple, the same market. Determinism is the dedup strategy.
  • Versions can't break identity. APIs may move during the alpha; identities will not — IDs are derived from bytes, not versions.

The rule for downstream code#

Downstream packages reference stable keys and specs, and resolve to IDs through this package's accessors — they don't copy hashes into source:

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

const id = calculateAtomId("https://github.com/vercel/next.js");
// → 0x3a52c8…f577 — real output, keccak256-derived,
//   the same offchain and on.

// Downstream packages reference stable keys/specs and resolve
// to ids through accessors — they don't copy hashes into source.

This is how predicates ship precomputed IDs and how classifications stay hash-free: identity is always derived, never transcribed.

The bytes are the identity — so canonicalize the bytes. Strip tracking params (?si=… is not identity), prefer canonical sameAs URLs at creation, and read the graph before you create. See Atoms for what goes into atom data and Offchain to Onchain for where the ID meets the contract.