Memory & the 0G spine

0G integration

Thin, typed wrappers over 0G Compute (attested inference), 0G Storage (root-hash commitments), and 0G Chain (the registry). Each exposes .raw() — one place to absorb SDK churn.

#0G Compute

Inference runs against a TEE-capable (“TeeML”) provider through the 0G Compute broker, which can cryptographically attest the response came from the enclave. The broker is throw-on-error and auto-selects testnet contracts from the signer’s chain id.

packages/zerog/src/compute.tsts
async infer(messages): Promise<InferenceResult> {
  const broker = await this.getBroker();
  await this.ensureFunded(broker);

  const provider = this.config.computeProvider;
  const { endpoint, model } = await broker.inference.getServiceMetadata(provider);

  const content = serialize(messages);                               // joined role+content; the broker bills on it
  const headers = await broker.inference.getRequestHeaders(provider, content);  // SINGLE-USE per call

  const openai = new OpenAI({ baseURL: endpoint, apiKey: '' });
  // .withResponse() exposes raw headers: the TEE signature is keyed by the
  // provider's ZG-Res-Key header, NOT completion.id (which 404s).
  const { data: completion, response } = await openai.chat.completions
    .create({ model, messages }, { headers: { ...headers } })
    .withResponse();

  const answer = completion.choices[0]?.message?.content;
  if (!answer) throw new Error('0G inference returned an empty completion');

  const chatId = response.headers.get('ZG-Res-Key') ?? completion.id;   // the verification handle
  return { answer, chatId, provider, model };
}
War story
getting signature error — the day attestation would not pass

Storage went green, but compute kept failing the attestation step. processResponsefetches the TEE signature from /v1/proxy/signature/{chatID} — and we were passing completion.id, which 404s. The chatID must be the provider’s ZG-Res-Key response header, only reachable via .withResponse(). One line turned the red cross into “processResponse returned TRUE — the call is sworn.”

Verification is told honestly: processResponse returns boolean | null, and anything other than an explicit true is treated as unverified. We never fabricate a verified state.

#0G Storage

0G Storage turns a sealed memory blob into a public, content-addressed commitment: a merkle root hash. The SDK is tuple-returning — it never throws — the exact inverse of the compute broker, so every call checks the error tuple.

packages/zerog/src/storage.tsts
async upload(bytes): Promise<UploadResult> {
  const data = new MemData(bytes);

  const [tree, treeErr] = await data.merkleTree();   // [result, err] TUPLE — never assume a throw
  if (treeErr !== null) throw treeErr;
  const expectedRoot = tree?.rootHash() ?? null;

  const [res, upErr] = await this.indexer.upload(data, this.evmRpc, signer);
  if (upErr !== null) throw upErr;

  // upload() is polymorphic: one small object returns { rootHash, txHash }; narrow defensively.
  const rootHash = 'rootHash' in res ? res.rootHash : res.rootHashes[0];
  if (expectedRoot !== null && rootHash !== expectedRoot)
    throw new Error('0G upload root mismatch');
  return { rootHash, txHash: res.txHash ?? null };
}

Splitting computeRoot from upload lets a receipt surface a real root hash immediately while the upload runs in the background — and the upload’s root is asserted to match. Reads are Node-only and run only on the reconcile path; the 3–5 minute propagation delay is precisely why a user turn never reads from storage.

#0G Chain

Storage proves a memory exists; the chain proves the agent committed to it, permanently, in an order anyone can replay. AgentRegistry is a tiny Solidity contract: an append-only log of each owner’s root hashes, with events the proof feed indexes. It holds no funds and has no admin path to mutate or delete.

contracts/src/AgentRegistry.solsolidity
function commitMemory(bytes32 rootHash) external returns (uint256 index) {
  if (!registered[msg.sender]) revert NotRegistered();
  if (rootHash == bytes32(0))  revert ZeroRootHash();
  index = _memories[msg.sender].length;
  _memories[msg.sender].push(Commitment({ rootHash: rootHash, timestamp: uint64(block.timestamp) }));
  emit MemoryCommitted(msg.sender, rootHash, index);
}

Phase 3 is wired behind one env var. When a registry address is configured, a successful upload is followed by an on-chain commit whose tx hash flows into the memory receipt — another clickable proof. Eight Foundry tests cover access control, the reverts, append-only immutability, and a fuzz pass.

#SDK reality

The single hardest-won lesson in this project is which packages to install. The 0G SDKs migrated org from @0glabs/* to @0gfoundation/*; the old packages are abandoned and point at the wrong contracts on Galileo (the broker defaults to mainnet addresses). Use these:

NeedPackage
Compute@0gfoundation/0g-compute-ts-sdk@^0.8.4
Storage@0gfoundation/0g-ts-sdk@^1.2.8
War story
Trust the packages, not the docs

Before touching a 0G primitive, run npm pack @0gfoundation/<pkg> and read the .d.ts files. Never trust docs, blogs, or training data for versions or signatures — they are routinely stale for this stack. Galileo’s chain id is 16602 (ChainList still shows the old 16601).