0gkitdocsGitHub

@foundryprotocol/0gkit-storage

Neutral 0G Storage client: upload, download, computeRoot, exists.

What it does

Wraps @0gfoundation/0g-storage-ts-sdk behind one class. Upload bytes (returns the storage root + the funding tx as a Receipt), download by root, compute a merkle root locally without uploading, and probe whether a root is retrievable. The SDK and ethers are lazily, dynamically imported so the package installs and type-checks without them.

When to use it

  • Persisting blobs (model weights, datasets, artifacts) to 0G Storage.
  • Content-addressing: computeRoot gives you the root without an upload or any keys — perfect for dedupe / "do I already have this?" checks.
  • Polling for finalization with exists.

Where to use it

Node only. upload/download/computeRoot/exists dynamically import the Node-only @0gfoundation/0g-storage-ts-sdk (and ethers for the signer). This is why the playground and React guide surface a clean ConfigError in the browser — uploads must run on a server, in a script, or via the CLI.

Install

npm install @foundryprotocol/0gkit-storage @foundryprotocol/0gkit-core viem
# optional peers — required for upload/download/computeRoot/exists:
npm install @0gfoundation/0g-storage-ts-sdk ethers

API reference

class Storage

interface StorageConfig {
  network?: "aristotle" | "galileo"; // default "aristotle"
  indexerUrl?: string; // overrides the preset indexer
  rpcUrl?: string; // default https://evmrpc.0g.ai
  signer?: Signer; // preferred — use fromEnv() / fromPrivateKey() / etc.
  privateKey?: string; // legacy — deprecated, use signer instead
  loadSdk?: () => Promise<StorageSdk>; // inject the SDK (testing)
}
interface UploadResult {
  root: string; // 0x-normalized merkle root
  tx: Receipt; // { txHash, latencyMs }
  raw: unknown; // the raw SDK result
}
new Storage(config: StorageConfig);

The default network is aristotle. The indexer is chosen from the network (https://indexer-storage.0g.network for aristotle, https://indexer-storage-testnet.0g.ai for galileo) unless indexerUrl is set.

Pass a Signer (from @foundryprotocol/0gkit-wallet) to upload so the key is managed independently of the Storage class. The legacy privateKey string continues to work but emits a deprecation warning — see Legacy (deprecated) below.

storage.upload(data)

upload(data: Uint8Array): Promise<UploadResult>;

Uploads the bytes; returns { root, tx, raw }. Requires privateKey in the config (it funds the tx). Throws ConfigError if privateKey is missing or the SDK / ethers cannot be loaded; throws NetworkError if the upload fails or returns an unrecognized shape.

storage.download(root)

download(root: string): Promise<Uint8Array>;

Downloads with proof. Throws NetworkError if the download fails, the blob is empty (not finalized yet — retry), or the response is truncated.

storage.computeRoot(data)

computeRoot(data: Uint8Array): Promise<string>;

Computes the merkle root locally — no upload, no keys, no network. Throws NetworkError if the computation fails (e.g. empty input).

storage.exists(root)

exists(root: string): Promise<boolean>;

true if the root's header is retrievable. Transport errors are treated as "not found" and return false — if you are polling for finalization, retry rather than treating false as definitive. Never throws for transport.

storage.raw()

raw(): Promise<unknown>; // the loaded @0gfoundation/0g-storage-ts-sdk module

The escape hatch — the underlying SDK module.

Examples

With signer — recommended

import { Storage } from "@foundryprotocol/0gkit-storage";
import { fromEnv } from "@foundryprotocol/0gkit-wallet";

const signer = await fromEnv(); // KMS_KEY_ID > KEY_FILE > PRIVATE_KEY
const storage = new Storage({ network: "galileo", signer });

const { root, tx } = await storage.upload(new TextEncoder().encode("hello 0G"));
console.log(`root ${root} — tx ${tx.txHash}`);

Minimal — content-address without uploading

import { Storage } from "@foundryprotocol/0gkit-storage";

// No privateKey needed: computeRoot is local-only.
const storage = new Storage({ network: "galileo" });
const root = await storage.computeRoot(new TextEncoder().encode("hello 0G"));
console.log(root); // 0x… — deterministic for the same bytes
console.log(await storage.exists(root)); // false until uploaded

Realistic — upload, verify, download with error handling

import { Storage } from "@foundryprotocol/0gkit-storage";
import { ZeroGError } from "@foundryprotocol/0gkit-core";
import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet";

const signer = await fromPrivateKey(process.env.ZEROG_PRIVATE_KEY!);
const storage = new Storage({ network: "galileo", signer });

const payload = new TextEncoder().encode(JSON.stringify({ hi: "0G" }));

try {
  const { root, tx } = await storage.upload(payload);
  console.log(`root ${root} — tx ${tx.txHash} (${tx.latencyMs} ms)`);

  // Poll for finalization (exists returns false on transport errors too):
  let ready = false;
  for (let i = 0; i < 10 && !ready; i++) {
    ready = await storage.exists(root);
    if (!ready) await new Promise((r) => setTimeout(r, 2000));
  }

  const bytes = await storage.download(root);
  console.log("round-trip ok:", new TextDecoder().decode(bytes));
} catch (err) {
  if (err instanceof ZeroGError) {
    console.error(`[${err.code}] ${err.message}\n→ ${err.hint}`);
  } else {
    throw err;
  }
}

Legacy (deprecated)

Passing privateKey directly is still supported but deprecated. It emits a DeprecationWarning and will be removed in the next major version.

// ⚠ deprecated — works today, removed in v1.0
const storage = new Storage({
  network: "galileo",
  privateKey: process.env.ZEROG_PRIVATE_KEY,
});

Migrate by wrapping with fromPrivateKey:

import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet";
const signer = await fromPrivateKey(process.env.ZEROG_PRIVATE_KEY!);
const storage = new Storage({ network: "galileo", signer });

Common errors

SymptomCauseFix
ConfigError: Storage.upload requires a signer or privateKeyupload with no credentials in config.Pass { signer } (from fromEnv()) to the Storage constructor.
ConfigError: @0gfoundation/0g-storage-ts-sdk could not be loadedOptional peer not installed.npm install @0gfoundation/0g-storage-ts-sdk ethers.
ConfigError: ethers could not be loadedethers peer missing.npm install ethers.
NetworkError: 0G Storage upload failed…Indexer/RPC unreachable or signer unfunded.Check the indexer + RPC; fund the signer (testnet faucet).
NetworkError: 0G Storage returned an empty blob…Root not finalized yet.Retry shortly — use exists as a poll.

Related

core · wallet (loaders: fromEnv, fromKMS, …) · CLI: 0g storage · MCP: og_storage_* (MCP guide) · React: useUpload / useDownload. Template: npx degit rajkaria/0g-ai-kit/templates/storage-app.

Exports

  • SEGMENT_SIZE_BYTES
  • estimateBytes
  • makeStorageEstimate
  • type StorageConfig
  • type StorageEstimate
  • type StorageEstimateBreakdown
  • type StorageSdk
  • type UploadResult