# 0Gkit — full documentation (LLM text dump) > The neutral, MIT-licensed TypeScript toolkit for the 0G network — Storage, Compute, DA, Attestation, Chain, a `0g` CLI, an MCP server, React hooks, and drop-in feature Kits. Every package is versioned independently on npm under the `@foundryprotocol/0gkit-*` scope, and every write returns a uniform `Receipt`; every failure is a typed `ZeroGError`. Auto-generated from https://docs.0gkit.com. 102 pages. Index: https://docs.0gkit.com/llms.txt ============================================================================ # 0gkit — the neutral 0G builder toolkit Route: / URL: https://docs.0gkit.com/ ============================================================================ # 0gkit **0gkit** is the neutral, MIT-licensed, defacto builder toolkit for the [0G network](https://0g.ai). It gives you small, composable TypeScript packages for every 0G surface — Storage, Compute (inference), Data Availability, TEE Attestation, and the native chain — plus a language-agnostic `0g` CLI, an MCP server for AI agents, and React hooks. It is **not a framework**. Every package is a thin, faithful wrapper that you can drop at any time: each one exposes an escape hatch back to the raw 0G SDK. The toolkit is a help, never a cage. ## The neutrality principle 0gkit is deliberately vendor-neutral. No `@foundryprotocol/0gkit-*` package imports `@foundryprotocol/*` (the Foundry product) or any non-`@0gkit` workspace package. This is enforced in CI by `pnpm boundary:check` (dependency-cruiser). Foundry-specific functionality ships as a **separate, opt-in plugin** that is absent by default — you turn it on explicitly (`ZEROG_FOUNDRY=1`) and it is loaded through a computed specifier so the neutrality boundary stays green by construction. What this means for you: 0gkit has zero hidden coupling. It is just 0G, viem, and (optionally) the official 0G SDKs. ## The package map | Package | What it gives you | | ------------------------------------ | ------------------------------------------------------------------------------------------ | | `@foundryprotocol/0gkit-core` | Network presets, a viem client factory, the `Receipt` envelope, the `ZeroGError` taxonomy. | | `@foundryprotocol/0gkit-chain` | Explorer URLs, native balance, `waitForReceipt`, the testnet faucet. | | `@foundryprotocol/0gkit-storage` | `Storage`: `upload` / `download` / `computeRoot` / `exists`. | | `@foundryprotocol/0gkit-compute` | `Compute`: provider discovery, broker inference, an OpenAI-compatible shim. | | `@foundryprotocol/0gkit-da` | `DA`: deterministic digest, encoder publish, local integrity verify. | | `@foundryprotocol/0gkit-attestation` | Parse / sign / recover / verify / report a TEE attestation envelope (pure crypto). | | `@foundryprotocol/0gkit-cli` | The `0g` command line: `init`, `doctor`, `chain`, `storage`, `infer`, `da`, `attest`. | | `@foundryprotocol/0gkit-mcp` | Every primitive as an MCP `og_*` tool for Claude / Cursor / Cline / any agent runtime. | | `@foundryprotocol/0gkit-react` | `useUpload` / `useDownload` / `useInference` / `useAttestation`. | The dependency direction is always one way: `chain`, `storage`, `compute`, `da`, and `attestation` build on `core`; the `cli`, `mcp`, and `react` packages build on the primitives. `core` depends on nothing but `viem`. ## When to reach for 0gkit vs the raw 0G SDKs **Reach for 0gkit when you want:** - One consistent surface across every 0G capability instead of five different SDK shapes. - Actionable errors. Every failure is a `ZeroGError` with a `.code` and a `.hint` that tells you the exact remedy (missing env var, run `0g doctor`, which attestation check failed). No silent failures, ever. - A uniform [`Receipt`](/concepts#the-receipt-envelope) for everything that touches the chain (tx hash, block, latency, explorer link). - A language-agnostic surface — the `0g` CLI with `--json` lets any stack (Python, Go, shell) drive 0G. - Agent-native access — the MCP server exposes every primitive to an LLM with no glue code. **Reach for the raw 0G SDK when:** - You need an SDK feature 0gkit does not wrap yet. Every package has an escape hatch — `Storage#raw()`, `Compute#raw()`, or just use the underlying viem client from `createClient` — so you are never blocked. ## Next steps - [Getting started](/getting-started) — install and a 60-second end-to-end example. - [Cookbook](/cookbook) — end-to-end tutorials: build a chat app, a TEE-attested AI agent, an NFT minter backed by 0G Storage. - [Concepts](/concepts) — `Receipt`, the `ZeroGError` taxonomy, networks, the client factory, the escape hatch. - [Packages](/packages) — one detailed page per package with full API reference and runnable examples. - [CLI reference](/cli) · [MCP guide](/mcp) · [React guide](/react) ============================================================================ # CLI reference Route: /cli URL: https://docs.0gkit.com/cli ============================================================================ # CLI reference The `0g` command line (`@foundryprotocol/0gkit-cli`) is the universal, language-agnostic surface for 0G. Every command mirrors a primitive package; `--json` makes it scriptable from any language. ```bash # Install globally (exposes the `0g` binary): npm install -g @foundryprotocol/0gkit-cli 0g doctor # Or run one-off via npx (note the full scope — `npx 0g` resolves to an # unrelated package on npm): npx @foundryprotocol/0gkit-cli doctor ``` ## Global flags These apply to every command and are inherited by subcommands. | Flag | Env | Default | Meaning | | ---------------------- | ------------------- | ---------- | -------------------------------------------------------------- | | `--network ` | `ZEROG_NETWORK` | `galileo` | `aristotle` \| `galileo` \| `local`. | | `--rpc ` | `ZEROG_RPC_URL` | preset RPC | Override the network RPC URL. | | `--private-key ` | `ZEROG_PRIVATE_KEY` | _(none)_ | Signer key (funds storage tx). | | `--json` | — | off | Machine-readable JSON output. | | `--foundry` | — | off | Force-show the optional Foundry plugin namespace. | | `--copy-issue-context` | — | off | On error, also print a redacted markdown report to **stderr**. | | `--version` | — | — | Prints the installed CLI version. | Precedence: **flag > env > preset default**. Additional env vars: `ZEROG_BROKER_KEY`, `ZEROG_PROVIDER` (for `infer`). ## Output & exit codes - Human mode: pretty lines; errors print `✗ ` then `→ `. - `--json` mode: success is `{ "ok": true, ... }`; failure is `{ "ok": false, "error": { "code", "message", "hint" } }`. - **Exit code `0`** on success. **Exit code `1`** when a command throws (any `ZeroGError`), when `0g doctor` has a failing required check, or when `0g attest verify` does not verify. ## Debugging: `--copy-issue-context` Any `0g` command accepts `--copy-issue-context`. When the command throws a `ZeroGError`, the normal error output still goes to stdout; in addition, a markdown report is written to **stderr**, suitable for pasting straight into a new GitHub issue. The report contains: - Error `code`, `message`, `hint`, and `helpUrl`. - The CLI invocation, with `--private-key` redacted and URL userinfo stripped from `--rpc`. - Node.js version, OS, and a timestamp. - Versions of all installed `@foundryprotocol/0gkit-*` packages. - The top 10 frames of the stack. ```bash # Run normally — error to stdout, report to stderr. 0g storage put ./big.bin --copy-issue-context # Capture only the report: 0g storage put ./big.bin --copy-issue-context 2> issue.md ``` ## `0g init [name]` Scaffold a runnable, testnet-default project. Defaults the directory name to `0g-app`. Writes `package.json`, `.env.example`, `index.mjs`, `README.md`, `.gitignore`. **Errors** (`ConfigError`) if the target directory exists and is non-empty. ```bash 0g init my-app cd my-app && npm install 0g doctor npm start ``` ## `0g doctor` Preflight every 0G surface for the selected network. Checks, in order: 1. **rpc** (required) — RPC reachable and reports the preset's chain id. 2. **signer** (soft) — key present and the address is funded (read-only is valid; absence is not a failure). 3. **storage-indexer** (soft) — indexer endpoint reachable (HTTP < 500). 4. **da-encoder** (soft) — DA encoder reachable (falls back to local mode). 5. **faucet** (informational) — faucet guidance for the network. Exit `1` only if a **required** check fails (i.e. RPC). ```bash 0g doctor --network galileo 0g doctor --json | jq '.checks[] | select(.ok==false)' ``` ### `0g doctor --fix` Pass `--fix` to apply the **safe** auto-fixes `doctor` can make without touching the network or installing anything (advisory-only by design — Decision D85): - Regenerates a missing `.env` / `.env.example` from your `define0GConfig`. - Prints the exact command to bump a stale package pin (it never edits your `package.json` for you). ```bash 0g doctor --fix # ✓ wrote .env.example from 0g.config.ts # → stale pin: run `npm i @foundryprotocol/0gkit-cli@latest` ``` `--fix` never installs dependencies, never sends a transaction, and never mutates network state — it only writes local `.env*` files and prints next steps. ## `0g dev` Spin up a **local 0G stack** (chain + storage + compute + DA) for offline development — no testnet funds, no RPC latency. | Subcommand | What | | ------------------ | ------------------------------------------------- | | `0g dev` / `start` | Start the local devnet (default subcommand). | | `0g dev stop` | Stop the running devnet. | | `0g dev status` | Inspect the running devnet (ports, pids, health). | | `0g dev reset` | Stop the devnet **and wipe** its state directory. | Common `start` flags: `--port-chain ` (default `8545`), `--port-da ` (default `5680`), `--mnemonic `, `--state-dir ` (default `~/.0g-dev`), `--detach` (exit once services are up — for tests/CI). ```bash 0g dev --detach # start in the background 0g test --local # run conformance against it (see below) 0g dev status 0g dev reset # tear down + wipe state ``` ## `0g test` Run the **offline conformance suites** — storage / DA / and other primitive round-trips — to prove your install and config work before you ship. Lazy-loads [`@foundryprotocol/0gkit-testing`](/packages/0gkit-testing) so it never bloats cold-start (Decision D39). | Flag | Meaning | | ---------------- | ------------------------------------------------------------------------- | | `--suite ` | Comma-separated subset, e.g. `--suite=storage,da` (default: all). | | `--galileo` | Run against the live galileo testnet (**default**). | | `--local` | Run against the running `0g dev` stack (`http://127.0.0.1:8545`). | | `--kits` | Also run each applied kit's conformance check (reads `.0gkit/kits.json`). | ```bash 0g test # all suites, galileo 0g test --suite=storage,da # just these two 0g test --local --kits # local stack + every applied kit's check 0g test --json | jq '.ok' ``` Exit `1` if any suite fails — wire it as a CI gate before deploy. ## `0g chain` Native-chain helpers. | Subcommand | What | | ---------------------------- | --------------------------------------------------------- | | `0g chain faucet
` | Request testnet funds (galileo points at the web faucet). | | `0g chain balance
` | Native 0G balance (prints `0G` and wei). | | `0g chain tx ` | Wait for a tx receipt + explorer link. | ```bash 0g chain balance 0xYourAddress --network aristotle 0g chain tx 0xYourTxHash --json 0g chain faucet 0xNew --network galileo # → https://faucet.0g.ai ``` ## `0g storage` 0G Storage. Networks restricted to `aristotle` | `galileo` (a `ConfigError` otherwise). | Subcommand | What | | ----------------------------- | -------------------------------------------------------------------- | | `0g storage put ` | Upload a file's bytes; prints `root` + `tx`. **Needs a signer key.** | | `0g storage get [out]` | Download by root; writes to `[out]` or prints byte count. | | `0g storage exists ` | `true` if the root is retrievable. | ```bash export ZEROG_PRIVATE_KEY=0x... # funds the upload tx ROOT=$(0g storage put model.bin --network galileo --json | jq -r .root) 0g storage get "$ROOT" ./out.bin 0g storage exists "$ROOT" --json ``` ## `0g infer` Run a chat completion against a 0G compute provider. Requires a broker key (`ZEROG_BROKER_KEY`, falling back to `ZEROG_PRIVATE_KEY` / `--private-key`) and a provider (`--provider` or `ZEROG_PROVIDER`). | Flag | Meaning | | ---------------------- | --------------------------------------- | | `-m, --message ` | Prompt text (default: read stdin). | | `--provider
` | 0G inference provider (or env). | | `--model ` | Model id (provider default if omitted). | | `--temperature ` | Sampling temperature. | ```bash export ZEROG_BROKER_KEY=0x... ZEROG_PROVIDER=0xPROVIDER 0g infer -m "Summarize 0G in one line" --network galileo echo "What is 0G DA?" | 0g infer --json | jq -r .output ``` ## `0g da` 0G Data Availability. | Subcommand | What | | ------------------------------ | -------------------------------------------------------- | | `0g da publish ` | Publish a blob (`-` = stdin); local-digest mode off-net. | | `0g da verify ` | Local integrity check: recompute the digest and compare. | ```bash 0g da publish payload.json --network galileo --json | jq -r .digest echo '{"a":1}' | 0g da publish - --json 0g da verify payload.json 0xDIGEST # prints MATCH / MISMATCH ``` ## `0g attest` TEE attestation. Operates on a `SignedEnvelope` JSON file (`{ envelope, digest, signature }`). | Subcommand | What | | -------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `0g attest verify --signer
` | Verify digest integrity **and** signer identity. `--signer` is required. Exit `1` if not verified. | | `0g attest report ` | Human-readable summary of the signed envelope. | ```bash 0g attest verify signed.json --signer 0xCoordinator 0g attest report signed.json --json ``` ## `0g contracts` Generate a **typed TypeScript client** from a contract ABI — either an off-chain Foundry artifact or a verified ABI fetched from the chain explorer. | Subcommand | What | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `0g contracts generate` | Codegen from a local Foundry artifact JSON. `--abi ` + `--out ` required; `--name` overrides the contract name. | | `0g contracts import [address]` | Fetch a **verified** ABI from the explorer (or `--abi `) → codegen. `--out` defaults to `./0gkit/contracts`. | | `0g contracts list` | List the bundled standard 0G contracts and their pinned addresses. | | `0g contracts info ` | Show the ABI summary for a bundled standard contract. | `import` fetches the ABI from 0G ChainScan's Etherscan-compatible `/open/api` (keyless; set `OG_EXPLORER_API_KEY` only for rate-limit relief). **`--name` is required when importing by address** — the explorer's `getabi` carries no contract name. An unverified contract, an HTTP error, or a malformed payload throws a `ConfigError` pointing you at `--abi .json` — it **never** fabricates an ABI. ```bash # From a verified on-chain contract (galileo default): 0g contracts import 0xAbc…DEF --name MyToken # → wrote ./0gkit/contracts/MyToken.ts # From a local Foundry artifact: 0g contracts generate --abi ./out/MyToken.sol/MyToken.json --out ./src/contracts 0g contracts list --json ``` The generated client is fully typed and wraps `@foundryprotocol/0gkit-contracts` — see the [contracts package docs](/packages/0gkit-contracts). ## `0g cost` Cost forecasting across 0G primitives. Two modes — synthesise estimates from flags, or aggregate _real_ per-op costs from a [Jaeger](https://www.jaegertracing.io/) trace emitted by `@foundryprotocol/0gkit-observability`. | Subcommand | What | | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | | `0g cost forecast --storage --compute --da ` | Sum hypothetical estimates across one or more ops. Pass any combination of the three primitives. | | `0g cost forecast --from-jaeger ` | Aggregate **real** per-op gas + fee totals from a Jaeger v1 trace dump. Mutually exclusive with the above. | ```bash # Hypothetical: forecast a workload before you run it 0g cost forecast \ --storage 1024,4096 \ --compute "summarise|llama-3-8b|512" \ --da 2048 \ --json # Real: replay an existing trace through the cost calculator 0g cost forecast --from-jaeger ./trace.json --json ``` `--from-jaeger` scans every span carrying the `0gkit.op` attribute (emitted automatically by [`@foundryprotocol/0gkit-observability`](/packages/0gkit-observability)), groups by op, and sums `0gkit.fee_native` + `0gkit.gas_native`. Dry-run spans (`0gkit.dry_run=true`) and errored spans (any `0gkit.error_code` tag) are counted but excluded from totals — they did not spend on-chain resources. The JSON envelope from `--from-jaeger`: ```json { "ok": true, "source": "jaeger", "file": "trace.json", "spansScanned": 42, "spansAttributed": 18, "spansSkipped": 2, "byOp": { "storage.upload": { "count": 3, "totalGas": "240000", "totalFeeWei": "3000000000", "totalSizeBytes": 12288 }, "compute.inference": { "count": 12, "totalGas": "0", "totalFeeWei": "6000000000", "totalInputTokens": 2400, "totalOutputTokens": 6144 }, "da.publish": { "count": 3, "totalGas": "0", "totalFeeWei": "1536000000", "totalSizeBytes": 1536 } }, "totalGas": "240000", "totalFeeWei": "10536000000" } ``` Download a trace dump from the Jaeger UI (Trace → ⋮ → Download JSON) or hit the query API directly (`/api/traces/`). The same parser works against any OTLP collector that exports the Jaeger JSON format. Pass `-` as the file path to read a Jaeger envelope from stdin — pipes cleanly from `0g traces inspect --json` (see below). ## `0g estimate` Quick per-op cost estimates for a **concrete** input (a real file, a prompt, a byte size). Where `0g cost forecast` sums hypothetical or trace-aggregated workloads, `estimate` answers "what will _this one_ op cost?" | Subcommand | What | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `0g estimate storage ` | Estimate the cost to upload `` to 0G Storage. | | `0g estimate compute` | Estimate a chat completion. `-p, --prompt `, `--model `, `--max-output ` (default 512). | | `0g estimate da [file]` | Estimate a 0G DA publish. `[file]` or `--bytes `. | | `0g estimate contracts` | Estimate gas + fee for a contract write. Required: `--abi `, `--address <0x>`, `--method `; optional `--args ''`. | ```bash 0g estimate storage ./model.bin --json 0g estimate compute -p "Summarize 0G" --model llama-3.1-8b --max-output 256 0g estimate da --bytes 4096 ``` ## `0g traces` Inspect local 0gkit trace JSONL files written by the `OGKIT_TRACE_DIR` opt-in mirror in `@foundryprotocol/0gkit-observability`. Off by default; when the env var is set, every instrumented span is written to `/-.jsonl` in addition to your configured OTel exporter. Pure local sink — no network, no extra infra. | Subcommand | What it does | | --------------------------- | ------------------------------------------------------------------- | | `0g traces list [--last N]` | Show trace files newest-first with span count + fee total + top op. | | `0g traces inspect ` | Pretty-print every span in a trace (op, fee, gas, attributes). | ### Quickstart ```bash # In the process that runs your 0gkit code: export OGKIT_TRACE_DIR=.0gkit/traces # After requests have run: 0g traces list --last 5 0g traces inspect # Replay one local trace as a cost forecast: 0g traces inspect --json | 0g cost forecast --from-jaeger - ``` Both subcommands accept `--dir ` to override `OGKIT_TRACE_DIR` for a one-off look at a directory copied from a teammate. `--json` is supported on both for piping into other tooling. ## `0g kits` Compose your app from **kits** — overlays that drop in a portable `lib/`, per-base `adapters/`, and (for React bases) a `ui/` tier, deduping dependencies as they apply. See the [kits guide](/kits) and [authoring guide](/kits/authoring). | Subcommand | What | | -------------------- | ------------------------------------------------------------------ | | `0g add ` | Apply one or more kits to the current project. | | `0g kits list` | List kits compatible with the detected (or `--base`) project base. | | `0g kits info ` | Show a kit's description, tiers, deps, and env. | `0g add` flags: `--base ` (force the base: `react-app` \| `mcp-agent` \| `node` \| …), `--pm ` (which package manager appears in the printed install hint), `--dry-run` (preview what would be written, touch nothing). Kits are fetched from the template registry via `giget` at apply-time. ```bash 0g kits list --base react-app 0g kits info agent-memory 0g add agent-memory live-feed # applies both (composed deps resolved first) 0g add ai-oracle --dry-run # preview only ``` ## `0g jobs` Inspect `@foundryprotocol/0gkit-jobs` queues. | Subcommand | What | | --------------------- | ----------------------------------------- | | `0g jobs status ` | Print the `JobRecord` for a given job id. | Flags: `--backend ` (default `sqlite`), `--path ` (sqlite file, default `./.jobs.db`). ```bash 0g jobs status job_abc123 --backend sqlite --path ./.jobs.db --json ``` ## `0g mcp init ` Wire the 0gkit tool set into your AI editor. Writes the MCP server config for the given agent — one of `cursor`, `claude`, `windsurf`, `codex`. In a kitted `mcp-agent` project it also surfaces your applied kits' tools. | Flag | Meaning | | ---------- | ------------------------------------------------------------ | | `--global` | Install to the agent's user-level config (default: project). | ```bash 0g mcp init cursor # writes project-level MCP config 0g mcp init claude --global # user-level config # → restart the agent to pick up the 0gkit tools ``` Writes editor config only — it never mutates network state. See the [MCP guide](/mcp). ## `0g foundry …` (opt-in, hidden) The optional Foundry plugin namespace. Hidden unless `@foundryprotocol/mcp` is installed or you pass `--foundry`. Absent by default — the neutrality boundary stays green by construction. ## Scripting from any language `--json` is stable and meaningful exit codes make `0g` a clean subprocess: ```bash # Python import json, subprocess r = subprocess.run(["0g","storage","exists",root,"--json"], capture_output=True, text=True) print(json.loads(r.stdout)["exists"]) ``` ## Related [storage](/packages/storage) · [compute](/packages/compute) · [da](/packages/da) · [attestation](/packages/attestation) · [MCP guide](/mcp) (the same surface for agents). ============================================================================ # Concepts Route: /concepts URL: https://docs.0gkit.com/concepts ============================================================================ # Concepts Five ideas run through every 0gkit package. Learn them once and every package behaves predictably. ## The `Receipt` envelope Every 0gkit operation that touches the chain returns a uniform `Receipt` (`@foundryprotocol/0gkit-core`). It is the single result shape you handle everywhere. ```ts interface Receipt { /** Transaction hash. `0x${string} | string` — the `| string` is a * deliberate escape hatch so an untyped JSON/HTTP source (e.g. a faucet * response) can be assigned without a cast. */ txHash?: `0x${string}` | string; /** Present only when the active network preset has a verified explorer. */ explorerUrl?: string; blockNumber?: bigint; /** Wall-clock duration of the operation, milliseconds. Always present. */ latencyMs: number; /** Opaque here; @foundryprotocol/0gkit-attestation gives it a concrete type. */ attestation?: unknown; } ``` Only `latencyMs` is guaranteed. `txHash`, `explorerUrl`, and `blockNumber` are optional by design — a local faucet may not return a tx hash, a network may have no explorer. Always null-check them. ## The `ZeroGError` taxonomy No 0gkit code path fails silently. Every error 0gkit throws is a `ZeroGError` (or a subclass) carrying two extra fields: - `.code` — a `ZeroGErrorCode`: `"CONFIG" | "NETWORK" | "CHAIN" | "ATTESTATION"`. - `.hint` — an actionable string: the exact remedy (the missing env var, run `0g doctor`, which attestation check failed). ```ts import { ZeroGError } from "@foundryprotocol/0gkit-core"; try { await storage.upload(bytes); } catch (err) { if (err instanceof ZeroGError) { console.error(`[${err.code}] ${err.message}`); console.error(`→ ${err.hint}`); // do exactly this to fix it } else { throw err; } } ``` ### Every error class | Class | `code` | Thrown when… | | ------------------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ZeroGError` | _(base)_ | The base. You usually catch this — every other class extends it. Carries `code` + `hint`. | | `ConfigError` | `CONFIG` | Misconfiguration: unknown network, missing `privateKey`/`brokerKey`/`provider`, an unresolved preset (`createClient` with no rpcUrl/chainId), an optional peer (`ethers`, the 0G SDK) not installed, a malformed digest, a non-JSON envelope. | | `NetworkError` | `NETWORK` | A reachable endpoint failed: balance read failed, faucet HTTP error / unreachable, storage indexer / DA encoder error or unexpected result shape, a compute provider returned non-2xx. | | `ChainError` | `CHAIN` | A chain interaction failed: a transaction did not confirm (`waitForReceipt`). | | `AttestationError` | `ATTESTATION` | An attestation envelope is structurally invalid (`parseEnvelope`) or `signEnvelope` got an invalid private key. Note: `verifyEnvelope` never throws — a bad signature resolves `ok:false`. | `ConfigError`, `NetworkError`, `ChainError`, and `AttestationError` all extend `ZeroGError`, so `instanceof ZeroGError` catches every 0gkit failure. Catch the specific subclass when you want to branch (e.g. retry on `NetworkError`, prompt for a key on `ConfigError`). ## Networks & presets A `NetworkPreset` (`@foundryprotocol/0gkit-core`) is the static description of a 0G network: ```ts interface NetworkPreset { readonly name: NetworkName; // "aristotle" | "galileo" | "local" readonly chainId?: number; // undefined ⇒ createClient throws ConfigError readonly rpcUrl?: string; // undefined ⇒ createClient throws ConfigError readonly explorer?: string; // undefined ⇒ explorerUrl() throws readonly faucetUrl?: string; // programmatic faucet endpoint (testnet) readonly faucetWebUrl?: string; // human faucet page, used in faucet()'s hint readonly testnet: boolean; } ``` Get one with `getNetwork(name)` (throws a `ConfigError` for an unknown name), or import the singletons `aristotle`, `galileo`, `local`, or the `networks` record. See [Getting started → networks](/getting-started#supported-networks) for the resolved values. ## The viem client factory `createClient` (`@foundryprotocol/0gkit-core`) turns a preset (plus optional overrides) into a `ZeroGClient`: ```ts import { createClient } from "@foundryprotocol/0gkit-core"; const client = createClient({ network: "aristotle", // optional overrides: // rpcUrl: "https://my-node", // chainId: 16661, // privateKey: process.env.ZEROG_PRIVATE_KEY, // adds client.wallet }); client.network; // the resolved NetworkPreset client.public; // a viem PublicClient — read anything client.wallet; // a viem WalletClient, only if privateKey was passed client.public.chain?.id; // 16661 ``` `ZeroGClient` is `{ network, public, wallet? }`. `public` is a full viem [`PublicClient`](https://viem.sh) and `wallet` (present iff you passed a `privateKey`) is a viem `WalletClient` — this is your escape hatch to raw viem. `buildChain(preset, rpcUrl?, chainId?)` is exported too if you only want the viem `Chain` object. An unresolved preset or a malformed private key throws a `ConfigError` (with a hint pointing at the exact fix). ## Canonical JSON & the cross-package digest `@foundryprotocol/0gkit-core` exports two pure helpers used by DA and Attestation so a digest is identical across packages and on-chain: - `canonicalJsonStringify(value)` — deterministic JSON: object keys sorted recursively, no whitespace, arrays keep order. Two logically-equal objects always produce the identical string. - `digestJson(value)` — `keccak256` of the canonical JSON encoding. This is the cross-package, on-chain digest anchor. ```ts import { canonicalJsonStringify, digestJson } from "@foundryprotocol/0gkit-core"; canonicalJsonStringify({ b: 1, a: 2 }); // '{"a":2,"b":1}' digestJson({ a: 2, b: 1 }) === digestJson({ b: 1, a: 2 }); // true ``` ## Deeper concept guides Topics that don't fit on one page — each is its own walkthrough: - [Durable async jobs](/concepts/durable-jobs) — at-least-once delivery model, retry + backoff semantics, the three backends (memory / sqlite / redis), HMAC-signed webhooks, and graceful shutdown on Vercel Fluid Compute. - [Observability with OpenTelemetry](/concepts/observability) — the `0gkit.*` semantic attribute namespace, the `instrument0g()` prototype-patching strategy, cost attribution, and the bundle budget that keeps observability "free." - Exporter wire-ups: [Honeycomb](/concepts/observability/exporters/honeycomb) · [Datadog](/concepts/observability/exporters/datadog) · [Vercel](/concepts/observability/exporters/vercel) — each is a runnable `instrument0g({...})` snippet with auth notes. - [MCP — make 0gkit native to your AI tool](/concepts/mcp) — one-command `0g mcp init ` editor wiring for Cursor / Claude / Windsurf / Codex, the neutral `og_*` tool set, and how a kitted `mcp-agent` project exposes its own kit tools too. - [Compute — router() vs direct()](/concepts/compute-router-vs-direct) — model-first `Compute.router()` (the managed 0G Router endpoint, or honest client-side selection + fallback) versus the explicit-provider `direct()` path, and when to use each. ## The escape hatch to the raw SDK 0gkit is a thin, faithful wrapper, never a cage. When you outgrow a wrapper, drop straight to the underlying SDK without leaving your code: - `Storage#raw()` → the loaded `@0gfoundation/0g-storage-ts-sdk` module. - `Compute#raw()` → the underlying broker `{ inference }`. - `Compute#openai()` → a drop-in OpenAI-style `chat.completions.create` shim. - `createClient(...).public` / `.wallet` → raw viem clients. You are never blocked waiting for 0gkit to wrap a feature. ============================================================================ # Compute — router() vs direct() Route: /concepts/compute-router-vs-direct URL: https://docs.0gkit.com/concepts/compute-router-vs-direct ============================================================================ # Compute — `router()` vs `direct()` `@foundryprotocol/0gkit-compute` gives you two ways to run an inference, and the right default is **`router()`** — you name a model, not a provider address. ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; const compute = new Compute({ network: "galileo", routerApiKey: process.env.ROUTER_API_KEY, }); // Model-first: a provider is chosen for you, with retries + fallback. const { output } = await compute.router({ model: "llama-3.1-8b", messages: [{ role: "user", content: "hi" }], }); ``` ## `router()` — pick a provider for me `router()` resolves a provider two ways depending on how you configure the client: - **Managed 0G Router (recommended).** Set `routerApiKey` (or the `ROUTER_API_KEY` env var) — from the [pc.0g.ai](https://pc.0g.ai) dashboard — and `router()` calls the real, OpenAI-compatible **0G Router** endpoint (`router-api.0g.ai/v1`). The Router selects a provider **server-side** (lowest latency, or `sort: "price"`), fails over to a healthy provider automatically, and settles from a single pre-funded balance. No wallet signer required. - **Client-side routing (fallback).** With **no** `routerApiKey`, `router()` lists on-chain providers (`listProviders()`), orders the ones serving your `model` first, and tries them in turn — retrying the next candidate on failure. This uses your wallet signer / broker key and needs no dashboard key. It logs a one-time note so you know which path you're on. Either way the public surface is identical — only the internal resolver differs. ```ts compute.router({ model, // required for the managed Router; optional for client-side messages, temperature, // optional prefer: "0xProvider", // pin a provider (client-side: tried first) sort: "price", // managed Router routing knob maxAttempts: 3, // client-side retry cap }); ``` If no provider is reachable, `router()` throws a typed `NetworkError` with a `0g doctor` hint — never a silent failure. ## `direct()` — I own my provider relationship When you already know exactly which provider you want (you have a direct relationship, a negotiated rate, or a pinned enclave), skip routing: ```ts const compute = new Compute({ network: "galileo", signer }); const { output } = await compute.direct({ provider: "0xYourProvider", messages: [{ role: "user", content: "hi" }], }); ``` `direct()` is a thin alias for the explicit-provider `inference()` path — the published `Compute.inference()` signature and behaviour are unchanged; `direct()` and the optional per-call `{ provider }` are purely additive. ## Which do I use? | Situation | Use | | ------------------------------------------------------------------- | ------------------------------------------------------------------------- | | You just want an answer and don't care which provider serves it | **`router()`** | | You want managed failover, a single balance, and no wallet plumbing | **`router()`** with `ROUTER_API_KEY` | | You have a specific provider (rate, enclave, relationship) to pin | **`direct({ provider })`** — or `router({ prefer })` to pin-but-fall-back | | You're offline / testnet with a wallet and no Router key | **`router()`** (client-side fallback) | Templates default to `router()`. Kits that call compute (`ai-oracle`, `sealed-inference`, `yield-intel`, `prediction-market`) do too — so a scaffolded project runs without you hard-coding a provider address. > **Honesty note.** The managed 0G Router endpoint is real and OpenAI-compatible > ([0G docs](https://docs.0g.ai/developer-hub/building-on-0g/compute-network/router/overview)); > its API keys are issued from the pc.0g.ai Web UI, so you bring a key. When no > key is set, 0gkit routes **client-side over the provider list** and labels it > as such — it does not pretend to be the managed Router. ============================================================================ # Durable jobs Route: /concepts/durable-jobs URL: https://docs.0gkit.com/concepts/durable-jobs ============================================================================ # Durable jobs `@foundryprotocol/0gkit-jobs` exists because the synchronous HTTP request/ response cycle is the wrong shape for most interesting 0G workflows. Inference that takes 90 seconds, a five-step agent loop, a 50-MB upload with a Merkle root commit — all of these outlive a single request. A durable job runner gives you four things HTTP doesn't: 1. **Persistence across crashes.** The work survives the node going away. 2. **Bounded retry with backoff.** Transient failures don't escalate to the user. 3. **Out-of-band notification.** Webhooks deliver state changes back to your app on its own timeline. 4. **Cancellation.** A graceful `stop()` aborts in-flight work cleanly. ## Delivery model `0gkit-jobs` is **at-least-once**. A worker that crashes between handler completion and `backend.complete()` returning will retry on the next claim. This is the only honest delivery semantic on a runtime that can be evicted at any time (Fluid Compute, Kubernetes, anywhere). **Handlers must be idempotent on their input.** Use `jobId` as the idempotency key for any external side effect — a charge, a database write, an on-chain transaction. Re-running with the same `jobId` MUST be safe. Webhook receivers should dedupe on `(jobId, newState)`. The runner fires the webhook _after_ `complete()` returns, so duplicate webhook delivery is also possible during a retry window. ## Lifecycle ``` ┌── retry (attempts < maxAttempts) ──┐ │ ▼ enqueue ──► queued ──► running ──► done │ │ │ ├──► failed (attempts exhausted, or thrown after stop) │ │ └──► cancelled (cancel() called) ``` Transitions are owned by the backend. The runner only reads `state` and asks the backend to move it via `claim`, `complete`, `fail`, `cancel`. ## Backoff The default backoff is decorrelated exponential with jitter, capped at 60s: ``` upper = min(500ms · 2^attempt, 60_000ms) lower = upper / 2 delay = lower + random([0, upper - lower]) ``` The cap prevents a misconfigured `maxAttempts: 20` from sleeping for hours; the jitter avoids thundering-herd retries when many jobs fail simultaneously on a shared upstream outage. Pass `backoffMs: (attempt) => …` to `jobs.define` to override. ## Graceful shutdown A worker holds two important pieces of state when the runtime asks it to die: the running handler, and the in-progress backend transaction. `runner.stop()` takes two flavours: - **`stop({ drain: true })`** (default): stop accepting new jobs, let in-flight handlers finish, then close the backend. This is the right call on Vercel Fluid Compute's `beforeExit` hook — the platform gives you a grace period, use it. - **`stop({ drain: false })`**: abort the in-flight handlers via the `AbortSignal` in their ctx. Handlers that wire up the signal (recommended for any handler longer than ~10s) reject cleanly; the backend records them as failed with the abort error. Use this when the runtime won't give you a grace period. ## When NOT to use a job runner - **Sub-second work.** Just inline it; the persistence overhead and a poll loop will dominate the latency. - **Workloads that must finish in a single request.** A job is by definition out-of-band — the response only carries the `id`. - **Strict at-most-once delivery.** Different problem, different toolkit. ## See also - [`@foundryprotocol/0gkit-jobs`](/packages/jobs) - [`JOBS_*` error codes](/errors) ============================================================================ # MCP — make 0gkit native to your AI tool Route: /concepts/mcp URL: https://docs.0gkit.com/concepts/mcp ============================================================================ # MCP — make 0gkit native to your AI tool `@foundryprotocol/0gkit-mcp` is the neutral 0G [Model Context Protocol](https://modelcontextprotocol.io) server: every 0G primitive — Storage, Compute (inference), Data Availability, native chain, and TEE attestation — is exposed as an `og_*` tool your AI editor can call directly. `0g mcp init ` wires that server into your editor in one command. No copy-pasting JSON. ```bash 0g mcp init cursor # writes .cursor/mcp.json 0g mcp init claude # writes ./.mcp.json (project-scoped) 0g mcp init windsurf # writes .windsurf/mcp.json 0g mcp init codex # writes .codex/mcp.json ``` Restart the editor and the 0G tools show up. Project scope is the default so the config is committable and travels with the repo. ## Where each agent's config lands | Agent | `0g mcp init ` (project) | `--global` (user-level) | | ---------- | ------------------------------- | ------------------------------------- | | `cursor` | `.cursor/mcp.json` | `~/.cursor/mcp.json` | | `claude` | `./.mcp.json` | `~/.claude/mcp.json` | | `windsurf` | `.windsurf/mcp.json` | `~/.codeium/windsurf/mcp_config.json` | | `codex` | `.codex/mcp.json` | `~/.codex/mcp.json` | Add `--global` to install into the agent's user-level config instead of the project: ```bash 0g mcp init claude --global ``` `0g mcp init` only writes the editor config — it never installs a server. The generated config runs `npx -y @foundryprotocol/0gkit-mcp` on demand, so the server is always the published version with zero setup. ## The neutral tool set Every generated config points at these nine tools: | Tool | What it does | | ------------------- | --------------------------------------- | | `og_storage_put` | Upload bytes to 0G Storage → root hash | | `og_storage_get` | Download by root hash | | `og_storage_exists` | Check a root hash exists | | `og_infer` | Run inference via the 0G Compute broker | | `og_da_publish` | Publish a blob to 0G Data Availability | | `og_da_verify` | Verify a DA commitment | | `og_chain_faucet` | Request testnet funds | | `og_chain_balance` | Read a native balance | | `og_attest_verify` | Verify a signed inference receipt | Set the server's environment (network, key, provider) in the generated config's `env` block — see the [`0gkit-mcp` package reference](/packages/mcp) for the full variable list. ## Kits add tools to your own server Kits that ship an `mcp-agent` adapter (`agent-memory`, `ai-oracle`, `durable-agent`, `sealed-inference`) contribute **their own** MCP tools — `memory_remember`, `oracle_ask`, and friends. Those tools live in your project, not in the published neutral server, so `0g mcp init` wires them up honestly: When you run `0g mcp init` inside a project scaffolded from the **`mcp-agent` base** that has kits applied, it detects the applied kits (via `.0gkit/kits.json`) and points your editor at **your local project's server** (`npm --prefix start`) instead of the published `npx` one. That local server — through an auto-generated `src/kits.ts` — exposes the nine neutral tools **plus** every applied kit's tools. ```bash npm create 0gkit-app -- my-agent --template mcp-agent cd my-agent 0g add agent-memory # regenerates src/kits.ts wiring the kit's tools 0g mcp init cursor # → points Cursor at THIS project's server # Cursor now sees og_* AND memory_remember / memory_recall ``` The published `@foundryprotocol/0gkit-mcp` never imports a kit — the kit tools run in your project because your editor runs your project. Neutrality stays intact; the synergy is real, not faked with a flag. For a bare project (no kits, or not an `mcp-agent` base), `0g mcp init` writes the neutral `npx` config — exactly the nine tools, nothing more. ============================================================================ # Observability — OTel + cost attribution Route: /concepts/observability URL: https://docs.0gkit.com/concepts/observability ============================================================================ # Observability — OTel + cost attribution Every production app eventually needs to answer three questions: 1. **What's slow?** Which 0G call put the p95 latency over budget last night? 2. **What's expensive?** Which feature is burning the most gas / DA fees? 3. **What's failing?** Why is the SLO at 99.2% instead of 99.9%? `@foundryprotocol/0gkit-observability` answers all three with one call — `instrument0g({...})` — that patches every primitive's public methods to emit OTel spans tagged with `0gkit.*` semantic attributes. The spans go to whichever OTel collector you already use (Honeycomb, Datadog, Tempo, Vercel OTel, Grafana Cloud, …). ## Mental model Each public primitive method (`Storage.upload`, `Compute.inference`, `DA.publish`, etc.) is wrapped in a span at runtime. The span: - carries `0gkit.op = "."` so collectors can filter for "all 0G calls" with one predicate. - carries `0gkit.network` so you can split traces by `galileo` / `aristotle` / `local`. - carries per-op size + cost attributes (`0gkit.size_bytes`, `0gkit.gas_native`, `0gkit.fee_native`, …) — enough to build a cost dashboard by aggregating one or two attributes. - records exceptions and sets `0gkit.error_code` on failures, so error alerts can route by the SCREAMING_SNAKE codes you already use elsewhere. ## Wire-up (one call, two modes) The package supports two modes — pick the one that matches your existing OTel posture. ### Auto SDK setup You don't have an OTel SDK yet. We lazy-import `@opentelemetry/sdk-node` and the OTLP exporter, build the SDK, and start it. ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; await instrument0g({ serviceName: "my-app", exporter: { kind: "otlp", endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT!, headers: { authorization: `Bearer ${process.env.OTEL_TOKEN!}` }, }, }); ``` ### Attach mode You already have an OTel SDK configured (Vercel auto-instrumentation, `@opentelemetry/auto-instrumentations-node`, a homegrown SDK). We skip SDK setup and only patch the primitives. ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; await instrument0g({ mode: "attach" }); ``` In both modes, `instrument0g()` is idempotent — calling it twice is a no-op. ## Cost attribution playbook A "cost per feature" dashboard in any collector that supports attribute filtering is one query away: ```sql -- Honeycomb / Tempo / Grafana SQL-flavour pseudo-query SELECT resource.service_name AS service, attributes['0gkit.op'] AS op, SUM(CAST(attributes['0gkit.fee_native'] AS BIGINT)) AS total_fee_wei FROM spans WHERE attributes['0gkit.op'] IS NOT NULL GROUP BY service, op ORDER BY total_fee_wei DESC ``` You'll see something like: ``` my-app | compute.inference | 4_500_000_000_000_000_000 my-app | storage.upload | 210_000_000_000_000_000 my-app | da.publish | 85_000_000_000_000_000 ``` …which translates directly to "compute is 95% of our spend." For per-feature attribution, layer on a custom attribute (`feature: "summarize"`) at the parent-span level via `tracer.startActiveSpan(...)` in your own code — the 0gkit spans automatically inherit it as part of the trace context. ## Delivery & failure semantics - Wrapping a method preserves its original semantics. If the method throws, we record the exception, set `0gkit.error_code` if available, and re-throw — your error handling continues to work. - We never swallow errors. If your handler fails for any reason, the span ends with `status: ERROR` and the throw propagates. - Span end is in a `finally`-equivalent: success or failure both close the span, so we never leak open spans on long-running runtimes. ## Bundle budget The public entry is ≤ 20 KB gzipped (currently ~2.2 KB). `@opentelemetry/api` is externalised; the SDK + exporter peers are lazy-imported only when `mode: "auto"` triggers SDK setup. So an "attach"-only app with its own SDK pays just the 2 KB cost of the wrapping logic + attribute mappers. ## Recipes by exporter - [Honeycomb](/concepts/observability/exporters/honeycomb) - [Datadog](/concepts/observability/exporters/datadog) - [Vercel OTel](/concepts/observability/exporters/vercel) ## See also - [Package: `@foundryprotocol/0gkit-observability`](/packages/0gkit-observability) - [Error codes](/errors) - [DECISIONS D32 — prototype patching](https://github.com/rajkaria/0gkit/blob/main/docs/DECISIONS.md) - [DECISIONS D33 — `0gkit.*` attribute namespace](https://github.com/rajkaria/0gkit/blob/main/docs/DECISIONS.md) - [DECISIONS D34 — bundle budget 20 KB](https://github.com/rajkaria/0gkit/blob/main/docs/DECISIONS.md) ============================================================================ # Datadog — instrument0g wire-up Route: /concepts/observability/exporters/datadog URL: https://docs.0gkit.com/concepts/observability/exporters/datadog ============================================================================ # Datadog Datadog accepts OTLP traces through the Datadog Agent (recommended) or through the OTLP-native intake. Both work — the agent route is the most common because it gives you per-host metrics, log correlation, and traces in one place. ## Wire-up (via the Datadog Agent) Run the agent locally or as a sidecar; its OTLP HTTP receiver listens on `:4318` by default. ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; await instrument0g({ serviceName: "my-0g-app", exporter: { kind: "otlp", endpoint: "http://localhost:4318/v1/traces", // No headers needed — the agent authenticates outbound to Datadog. }, }); ``` For the OTLP-native intake (no agent), point at the regional Datadog endpoint and include the API key: ```ts await instrument0g({ serviceName: "my-0g-app", exporter: { kind: "otlp", endpoint: "https://trace.agent.datadoghq.com/v0.4/traces", // adjust by region headers: { "dd-api-key": process.env.DD_API_KEY! }, }, }); ``` ## Auth When sending via the agent, the agent itself holds `DD_API_KEY` and the in-app code is unauthenticated. When sending directly, the API key goes in the `dd-api-key` header. Either way, load from env — never hardcode. ## Where the traces land Datadog's APM trace explorer groups spans into flame graphs. Each `0gkit.` span carries the full `0gkit.*` attribute set. Useful views: - **Resource page**: pick `0gkit.compute.inference` (or any op) to see all invocations as a sorted list with p50/p95/p99 latency. - **Trace search**: filter on `0gkit.error_code:STORAGE_QUOTA_EXCEEDED` to pull all instances of a specific failure mode. - **Custom metrics**: Datadog auto-generates trace metrics from span attributes, so `0gkit.fee_native` becomes graphable in dashboards without any extra wiring. ## Service map If you also instrument your HTTP / DB calls with the standard OTel auto-instrumentations, the service map shows 0G as a downstream — each `0gkit.*` span links into the parent service's request span via OTel context propagation. No extra config required. ============================================================================ # Honeycomb — instrument0g wire-up Route: /concepts/observability/exporters/honeycomb URL: https://docs.0gkit.com/concepts/observability/exporters/honeycomb ============================================================================ # Honeycomb Honeycomb's OTLP endpoint accepts traces over HTTP with a single auth header. Sign up at [honeycomb.io](https://www.honeycomb.io), create an environment, and grab the API key — there's a free tier that's more than enough to play with `0gkit.*` attributes. ## Wire-up ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; await instrument0g({ serviceName: "my-0g-app", exporter: { kind: "otlp", endpoint: "https://api.honeycomb.io/v1/traces", headers: { "x-honeycomb-team": process.env.HONEYCOMB_API_KEY!, // If you have multiple datasets, also set the dataset header: // "x-honeycomb-dataset": "my-0g-app", }, }, }); ``` That's it. Every `Storage.upload` / `Compute.inference` / `DA.publish` call now lands as a span in Honeycomb tagged with the `0gkit.*` attributes. ## Auth Honeycomb accepts the ingest API key via the `x-honeycomb-team` HTTP header. Don't commit it — load from `HONEYCOMB_API_KEY` in your `.env`. The key has write-only ingest permission so it's lower-risk than a query key, but treat it as a secret regardless. ## Where the traces land Honeycomb's UI groups spans into traces by trace ID. Each span shows: - **Span name**: `0gkit.storage.upload`, `0gkit.compute.inference`, etc. - **Service**: whatever you passed as `serviceName`. - **Attributes**: the full `0gkit.*` set plus any standard OTel attributes layered on by your own instrumentation. Useful queries: - **What's slow?** Heatmap on duration grouped by `0gkit.op` — surfaces outliers immediately. - **What's expensive?** Sum `0gkit.fee_native` (cast as a number) grouped by `0gkit.op` over the last 24h. - **What's failing?** Filter `status_code = ERROR` and group by `0gkit.error_code` to see which SCREAMING_SNAKE codes are spiking. ## Triage tip If spans aren't showing up, set `kind: "console"` temporarily — every span will print to stdout, so you can confirm the wrapping is firing before debugging the network path. ============================================================================ # Vercel OTel — instrument0g wire-up Route: /concepts/observability/exporters/vercel URL: https://docs.0gkit.com/concepts/observability/exporters/vercel ============================================================================ # Vercel OTel Vercel ships an OpenTelemetry collector on every function deployment. If your app is Next.js on Vercel, your function spans already flow into the platform Observability tab; with `instrument0g`, your 0G primitive calls show up there too — under the same trace ID as the request that triggered them. ## Wire-up — attach mode (recommended) Vercel's runtime registers the OTel SDK for you when `@vercel/otel` is loaded, so `instrument0g` only needs to patch the primitives: ```ts // next.config.js — keep this; @vercel/otel reads it. module.exports = { experimental: { instrumentationHook: true }, }; ``` ```ts // instrumentation.ts (Next.js convention) import { registerOTel } from "@vercel/otel"; import { instrument0g } from "@foundryprotocol/0gkit-observability"; export async function register() { registerOTel({ serviceName: "my-0g-app" }); await instrument0g({ mode: "attach" }); } ``` That's it. Next.js calls `register()` on cold start, before any request handler runs. ## Wire-up — direct OTLP If you're outside Next.js (Edge functions, custom serverless) you can hit the Vercel OTel ingest endpoint directly: ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; await instrument0g({ serviceName: "my-0g-app", exporter: { kind: "otlp", endpoint: "https://api.vercel.com/v1/otel/v1/traces", headers: { authorization: `Bearer ${process.env.VERCEL_OTEL_TOKEN!}` }, }, }); ``` ## Auth `@vercel/otel` reads the project's deployment token automatically — no env vars to set for the attach-mode path. For direct OTLP, generate a token in the Vercel dashboard (Project → Settings → Tokens) and pass via `VERCEL_OTEL_TOKEN`. ## Where the traces land Vercel's Observability tab (Project → Logs → Observability) shows traces grouped by request. Each request expands into: 1. The Vercel-emitted request span (`GET /api/...`, with status + duration). 2. Any spans you emit yourself. 3. The `0gkit.` spans for every primitive call, with the full `0gkit.*` attribute set. The trace ID is consistent across all three, so jumping from "slow request" to "which 0G call was the bottleneck" is one click. ## Fluid Compute / `waitUntil` interactions Vercel's Fluid Compute runtime gives you 25 seconds of `beforeExit` grace after the response. If you're using [`@foundryprotocol/0gkit-jobs`](/packages/jobs) inside the same function, the job runner's `stop({ drain: true, timeoutMs: 25_000 })` and instrument0g's spans coexist cleanly — handler spans for in-flight jobs finish before the SDK shuts down. No special wiring needed; both packages target the same shutdown semantics. ============================================================================ # Contributing Route: /contributing URL: https://docs.0gkit.com/contributing ============================================================================ # Contributing Thanks for helping build the neutral 0G toolkit. The canonical source for this guide lives at [CONTRIBUTING.md](https://github.com/rajkaria/0gkit/blob/main/CONTRIBUTING.md) in the repo — this page mirrors it for discoverability. ## 1. Local setup ```bash git clone https://github.com/rajkaria/0gkit.git cd 0gkit pnpm install pnpm --filter @foundryprotocol/0gkit-core build pnpm build ``` Requirements: Node `>=20.10`, pnpm `9.12.0`. See it working: ```bash pnpm --filter @foundryprotocol/0gkit-cli dev -- dev ``` ## 2. Running tests ```bash pnpm test pnpm --filter @foundryprotocol/0gkit-storage test pnpm boundary:check pnpm docs:check pnpm templates:check ``` Each package targets 80% lines / 70% branches. ## 3. Adding a template Templates live under `templates/` (workspace-excluded — see D24). Each is a standalone project that builds against published `@foundryprotocol/0gkit-*` versions. - Create `templates//` with `package.json`, `tsconfig.json`, `vitest.config.ts`, `README.md`, `src/index.ts`, `src/-flow.ts` (testable surface, D25), and `src/__tests__/`. - Register in `packages/create-0g-app/src/types.ts` and the `TEMPLATES` registry. Add a smoke case to the scaffolder tests. - Append a row with a Vercel deploy URL to `apps/docs/app/templates/page.mdx`. ## 4. Adding an error code Append the SCREAMING_SNAKE code to `ERROR_CODES` in `0gkit-core`, throw it via the right typed subclass, write a docs page at `apps/docs/app/errors//page.mdx`. `pnpm docs:check` enforces both. ## 5. Writing a sub-project plan Plans live under `docs/superpowers/plans/` as `--.md`. Use `superpowers:writing-plans` to draft. Tasks should be bite-sized: one test - one implementation + one commit each. Execute via `superpowers:subagent-driven-development` (same session, fresh subagent per task) or `superpowers:executing-plans` (parallel session). ## 6. Changesets Every change to a published package needs a changeset: ```bash pnpm changeset ``` Choose **patch** (bug fix), **minor** (additive), or **major** (breaking, with a `## Migration` section in the body). ## 7. Sign-off (DCO) We require the [Developer Certificate of Origin](https://developercertificate.org/). Sign your commits: ```bash git commit -s -m "feat: ..." ``` PRs without DCO sign-off won't merge. ## 8. Code of Conduct By participating, you agree to abide by the [Code of Conduct](https://github.com/rajkaria/0gkit/blob/main/CODE_OF_CONDUCT.md). Report incidents to `conduct@foundryprotocol.xyz`. ============================================================================ # Cookbook Route: /cookbook URL: https://docs.0gkit.com/cookbook ============================================================================ # Cookbook End-to-end tutorials. Pick an archetype, follow it from `npm create` to a running app, then graduate to production hardening. Every tutorial uses only the published `@foundryprotocol/0gkit-*` packages — nothing fabricated, every command verified. These are deeper than the [template READMEs](/templates) — they walk you through _why_ each piece exists, not just _what_ the code does. ## Tutorials ### [Chat app](/cookbook/chat-app) A real-time chat where every message is persisted to **0G Storage** and the on-chain `MessagePosted` event log is the source of truth for the message list. Teaches: SP3 wallet (server), SP4 typed contracts, SP6 indexer with reorg-safe `useEvent`, SP3 storage upload + download. **Stack:** Next.js 16 App Router · React 19 · `0gkit-storage` · `0gkit-indexer` · `0gkit-react` · `0gkit-contracts`. ### [AI agent with TEE-attested inference](/cookbook/ai-agent) A LangChain-style ReAct agent where every step is paid through **0G Compute**, gated on a TEE attestation, and orchestrated as a durable job. Survives worker crashes when you point the runner at sqlite or redis. Teaches: `Compute.inference`, attestation envelopes, durable jobs, webhook delivery, swappable backends. **Stack:** `0gkit-compute` · `0gkit-attestation` · `0gkit-wallet` · `0gkit-jobs`. ### [NFT minter with media + metadata on 0G Storage](/cookbook/nft-minter) Mint an ERC-721 token whose **metadata JSON** and **media file** both live on 0G Storage. `tokenURI` resolves to `0g-storage://`. Teaches: SP4 Foundry codegen, two-step storage uploads with cross-referencing roots, typed contract `write.mint(...)` calls, marketplace gateway design. **Stack:** Foundry · `0gkit-storage` · `0gkit-contracts` · `0gkit-wallet`. ## What you need before starting - **Node 20.10+** (CI tests 20, 22, 24). - **A funded testnet key** for `galileo` — grab one from [`faucet.0g.ai`](https://faucet.0g.ai). You never need real funds. - **A terminal**, an editor, and `npm` (or `pnpm` / `yarn` / `bun`). The AI-agent tutorial additionally needs a 0G Compute prepaid balance; the NFT tutorial additionally needs [Foundry](https://getfoundry.sh) installed. Each tutorial spells these out at the top. ## How these are structured Every tutorial follows the same shape: 1. **What you're building** — a screenshot or sample output of the end state. 2. **Prerequisites** — exact versions and key requirements. 3. **Scaffold** — one `npm create 0gkit-app` command. 4. **Walk the code, file-by-file** — every line you'll touch, with the "why" inline. 5. **Run it** — copy/paste commands; you should see the live thing. 6. **Extend it** — one or two real upgrades, not toy modifications. 7. **Production hardening** — the gap between "works on my laptop" and "won't page you at 3 AM". ## Something missing? If you want a tutorial for a workflow we haven't covered (e.g. a fully on-chain marketplace, a TEE-enclaved fine-tuning loop, a multi-tenant storage SaaS), open a discussion at [github.com/rajkaria/0gkit/discussions](https://github.com/rajkaria/0gkit/discussions). ============================================================================ # Cookbook · AI agent Route: /cookbook/ai-agent URL: https://docs.0gkit.com/cookbook/ai-agent ============================================================================ # Build an AI agent with TEE-attested inference A multi-step ReAct agent where every inference call is paid through **0G Compute**, gated on a TEE attestation, and orchestrated as a durable job. The loop survives worker restarts when you point the runner at sqlite or redis. By the end you'll have an agent that can read a goal, decide which tool to invoke, call the tool, fold the result back into context, and keep going until it produces a final answer — with every reasoning step provably run on a trusted enclave. ## What you're building ``` $ pnpm dev "What is 17 + 25? Use the add tool." step 1: action=tool add step 2: action=done Agent result: final Answer: 42 Steps : 2 [1] tx=0xabc… tool=add [2] tx=0xdef… ``` Each step is a durable job, persisted in the backend. Each inference call returns a receipt with the on-chain compute transaction hash. The whole loop is auditable end-to-end. ## Prerequisites - Node 20.10 or newer. - A funded `galileo` testnet key (storage upload gas, contract gas). - **A 0G Compute prepaid balance.** Inference is billed per token; see the [0G Compute docs](https://docs.0g.ai/build-with-0g/compute-network) for how to fund a broker key. - About 20 minutes. ## Scaffold ```bash npm create 0gkit-app@latest my-agent -- --template ai-agent cd my-agent cp .env.example .env ``` Fill in `.env`: ``` NETWORK=galileo PRIVATE_KEY=0x… # gas ZEROG_BROKER_KEY=0x… # compute prepaid wallet ZEROG_PROVIDER=0x… # an inference provider address ``` `ZEROG_PROVIDER` is the address of the provider you want to route through. The 0G Compute network has a list at [`docs.0g.ai/build-with-0g/compute-network`](https://docs.0g.ai/build-with-0g/compute-network). ## Run the math agent ```bash pnpm install pnpm dev "What is 17 + 25? Use the add tool." ``` You should see two `step` lines followed by `action=done` and a final answer. If you see `error=compute_inference_failed`, your broker balance is empty — top it up and retry. If you see `error=verify_failed`, the stub attestation rejected (it's designed to be replaced). The template ships with `verifyStep: async () => true` — replace it before going to prod (see "Wire real attestation" below). ## Walk the code ### 1. The tool registry — `src/tools.ts` ```ts export interface Tool { name: string; description: string; invoke(args: unknown): Promise; } export class ToolRegistry { private tools = new Map(); register(tool: Tool) { this.tools.set(tool.name, tool); } list(): { name: string; description: string }[] { return [...this.tools.values()].map(({ name, description }) => ({ name, description, })); } invoke(name: string, args: unknown): Promise { const tool = this.tools.get(name); if (!tool) throw new Error(`unknown tool: ${name}`); return tool.invoke(args); } } ``` A flat registry of typed tools. The agent prompt lists `tool.name + tool.description` to the model; when the model emits `{"action":"tool","name":"add","args":{"a":17,"b":25}}` the runner looks the name up and invokes it. ### 2. The per-step job — `src/agent.ts` ```ts import { jobs } from "@foundryprotocol/0gkit-jobs"; import { z } from "zod"; export function buildStepJob({ compute, verifyStep, }: { compute: Compute; verifyStep: (step: number, res: InferenceResult) => Promise; }) { return jobs.define({ name: "agent.step", input: z.object({ step: z.number(), messages: z.array(z.object({ role: z.string(), content: z.string() })), model: z.string(), }), output: z.object({ receipt: z.object({ txHash: z.string() }), decision: z.union([ z.object({ action: z.literal("tool"), name: z.string(), args: z.any() }), z.object({ action: z.literal("done"), answer: z.string() }), ]), }), maxAttempts: 2, handler: async ({ input }) => { const res = await compute.inference({ messages: input.messages, model: input.model, }); const verified = await verifyStep(input.step, res); if (!verified) { throw new Error("AGENT_VERIFY_FAILED"); } const decision = parseDecision(res.output); return { receipt: res.receipt, decision }; }, }); } ``` Key choices: - **Zod schemas** validate input/output at the runner boundary. A bad upstream call surfaces as a validation error, not a runtime mystery. - **`maxAttempts: 2`** = one retry. Inference failures are usually transient (rate limit, provider hiccup). More retries would burn compute budget on a persistent issue. - **Verify gate throws** rather than returning a verified flag. Throwing lets the runner's retry + backoff policy handle the failure mode consistently. ### 3. The orchestration loop — `src/agent.ts` (continued) ```ts export async function runAgent( prompt: string, deps: { runner: JobRunner; stepJob: JobDefinition; tools: ToolRegistry; log: (msg: string) => void; maxSteps?: number; stepTimeoutMs?: number; } ): Promise { const { runner, stepJob, tools, log, maxSteps = 8, stepTimeoutMs = 60_000 } = deps; const messages: Message[] = [ { role: "system", content: systemPrompt(tools) }, { role: "user", content: prompt }, ]; const trace: { txHash: string; tool?: string }[] = []; for (let step = 1; step <= maxSteps; step++) { const jobId = await runner.enqueue({ name: "agent.step", input: { step, messages, model: "default" }, }); const result = await runner.waitFor(jobId, { timeoutMs: stepTimeoutMs }); if (result.state !== "done") { log(`step ${step}: failed (${result.error?.message})`); return { kind: "error", reason: result.error?.message, trace }; } const { decision, receipt } = result.output; trace.push({ txHash: receipt.txHash }); if (decision.action === "done") { log(`step ${step}: action=done`); return { kind: "final", answer: decision.answer, trace }; } log(`step ${step}: action=tool ${decision.name}`); trace[trace.length - 1].tool = decision.name; const toolResult = await tools.invoke(decision.name, decision.args); messages.push({ role: "assistant", content: JSON.stringify(decision), }); messages.push({ role: "tool", content: JSON.stringify(toolResult), }); } return { kind: "error", reason: "max_steps_exceeded", trace }; } ``` What's happening: 1. Push a fresh per-step job onto the runner. 2. Wait for it to finish (with a timeout — never block forever). 3. Inspect the decision. 4. If `done`, return. 5. If `tool`, invoke the tool, fold its result back into context as a `tool`-role message, loop. Because each step is a job, **the loop is durable**. If the worker crashes after step 3, restart and step 3's persisted state lets you resume from step 4. Your tools must be idempotent — running them twice should be safe. ### 4. Wiring — `src/index.ts` ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; import { JobRunner } from "@foundryprotocol/0gkit-jobs"; import { MemoryBackend } from "@foundryprotocol/0gkit-jobs/backends/memory"; import { buildStepJob, runAgent } from "./agent"; import { ToolRegistry } from "./tools"; import { addTool } from "./tools/add"; const compute = new Compute({ network: process.env.NETWORK as "galileo", brokerKey: process.env.ZEROG_BROKER_KEY!, provider: process.env.ZEROG_PROVIDER as `0x${string}`, }); const verifyStep = async () => true; // ← stub. Replace before prod. const stepJob = buildStepJob({ compute, verifyStep }); const runner = new JobRunner({ backend: new MemoryBackend() }); runner.register(stepJob); await runner.start(); const tools = new ToolRegistry(); tools.register(addTool); const prompt = process.argv.slice(2).join(" "); const result = await runAgent(prompt, { runner, stepJob, tools, log: console.log }); console.log("Agent result:", result.kind); if (result.kind === "final") console.log(" Answer:", result.answer); console.log(" Steps :", result.trace.length); await runner.stop({ drain: true }); ``` `MemoryBackend` is the right default for a tutorial — zero infrastructure, perfect for ergonomics. We replace it later. ## Wire real attestation The template's `verifyStep` is a stub that always returns `true`. In production, replace it with a real attestation gate: ```ts import { verifyEnvelope } from "@foundryprotocol/0gkit-attestation"; const PROVIDER_SIGNER = "0xabc…" as const; // your trusted enclave's signing addr const verifyStep = async (_step: number, _res: InferenceResult) => { // Fetch the envelope from your provider's sidecar API. // The exact shape varies by provider — check their docs. const envelope = await fetch( `${PROVIDER_ATTESTATION_URL}/latest?broker=${BROKER_ADDR}` ).then((r) => r.json()); const { ok } = await verifyEnvelope(envelope, PROVIDER_SIGNER); return ok; }; ``` The shape `InferenceResult` returned by `Compute.inference` is `{ output, receipt, raw }` — there is **no `attestation` field** on the inference response itself, by design. Attestations are a separate envelope you fetch out-of-band so the same template works against providers who hand it back over a sidecar API, a websocket, or an on-chain event. `verifyEnvelope` checks the signature on the envelope's claim — typically a hash of the enclave measurement plus the broker address. If it returns `{ ok: true }`, the inference was provably executed by a TEE binary matching the expected measurement. ## Make it durable Swap `MemoryBackend` for `SqliteBackend` so a worker crash mid-loop resumes on restart: ```ts import { SqliteBackend } from "@foundryprotocol/0gkit-jobs/backends/sqlite"; const backend = new SqliteBackend({ path: "./.jobs.db" }); ``` For multi-node setups (multiple workers consuming the same queue): ```bash pnpm add ioredis ``` ```ts import { RedisBackend } from "@foundryprotocol/0gkit-jobs/backends/redis"; const backend = new RedisBackend({ url: process.env.REDIS_URL! }); ``` `RedisBackend` uses `BLPOP` semantics to ensure exactly one worker claims each job. Failed claims unwind cleanly — see the [durable jobs concept guide](/concepts/durable-jobs#delivery-model) for the at-least-once contract. ## Webhook delivery Want your app notified when an agent run finishes (or each step completes)? Pass a `webhook` config to the runner: ```ts const runner = new JobRunner({ backend, webhook: { url: process.env.AGENT_WEBHOOK_URL!, secret: process.env.AGENT_WEBHOOK_SECRET!, }, }); ``` On your receiver: ```ts import { jobs } from "@foundryprotocol/0gkit-jobs"; app.post("/agent-webhook", async (req, res) => { const signature = req.headers["x-0gkit-signature"] as string; const body = await readBody(req); // raw bytes! const ok = jobs.verifyWebhook({ body, signature, secret: process.env.AGENT_WEBHOOK_SECRET!, }); if (!ok) return res.status(403).end(); const event = JSON.parse(body.toString()); // event = { jobId, name, state: "done" | "failed", output?, error? } await persistAgentResult(event); res.status(204).end(); }); ``` **Important:** verify against the raw body bytes, not a re-serialized object. The HMAC signs exactly what the runner sent. ## Production hardening ### Cost estimation Every step burns compute budget. Estimate before you run a long agent: ```bash 0g estimate compute --messages "What is 17+25?" --model default ``` For programmatic budgeting, `compute.estimate({ messages })` returns the same `Estimate` envelope. Wrap `runAgent` in a budget check: ```ts const est = await compute.estimate({ messages, model }); if (est.gas > BUDGET) throw new Error("AGENT_BUDGET_EXCEEDED"); ``` ### Observability Instrument once, get spans for every inference + every job state transition: ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; await instrument0g({ serviceName: "agent-worker", exporter: { kind: "otlp" } }); ``` The agent loop then produces a span tree per run: one root span per `runAgent` call, child spans per step, grandchild spans per `compute.inference` with `0gkit.input_tokens` / `0gkit.output_tokens` / `0gkit.fee_native`. Filter on `0gkit.error_code` to spot which steps fail. ### Tool isolation The toy `add` tool does math; real tools call APIs, read files, talk to chains. Run untrusted tools in a sandbox: - `@foundryprotocol/0gkit-jobs` tool sub-jobs in a worker pool with a smaller `maxAttempts` budget than the main loop. - Use Vercel Sandbox for arbitrary code execution. - Always set a `stepTimeoutMs` ceiling — runaway tools are the #1 way agents bankrupt their broker balance. ### Idempotency `runAgent` is at-least-once. Your tools must be safe to invoke twice with the same args. Persist any external side effect (a charge, a file write, an email) keyed on `(jobId, stepIndex)`. ## What you built You have: - An agent loop that's durable (resumes after crash) and attested (every step provably executed by a TEE). - A swappable backend story (memory → sqlite → redis) for laptop → single node → multi-node. - Webhook delivery for out-of-process notification. - A budget-bounded, observable, idempotent pattern that scales to real workloads. ## See also - [`@foundryprotocol/0gkit-compute`](/packages/compute) — inference + cost estimation. - [`@foundryprotocol/0gkit-jobs`](/packages/jobs) — durable runner API. - [`@foundryprotocol/0gkit-attestation`](/packages/attestation) — envelope verification. - [Durable jobs concept](/concepts/durable-jobs) — delivery semantics, backoff, shutdown. - [Observability concept](/concepts/observability) — OTel setup. ============================================================================ # Cookbook · Chat app Route: /cookbook/chat-app URL: https://docs.0gkit.com/cookbook/chat-app ============================================================================ # Build a chat app on 0G A real-time chat where every message is persisted to **0G Storage** and the on-chain `MessagePosted` event log is the source of truth. By the end you'll have a Next.js app that lets you type a message, persists it, and renders the running history reactively as other writers post. This tutorial uses the `chat` template under the hood — but instead of just reading the README, you'll understand the design choices and graduate to a production-shaped extension. ## What you're building ``` ┌────────────────────────────────────────┐ │ 0Gkit chat │ ├────────────────────────────────────────┤ │ alice Hi everyone │ │ bob Hi alice! │ │ alice How is the testnet treating │ │ you? │ │ │ │ [ type a message … ] [Send] │ └────────────────────────────────────────┘ ``` Each row is one `MessagePosted` event on chain → one root in storage → one decoded body. Reorg-safe via `useEvent`: if a chain reorg drops the block the message was in, the row disappears automatically. ## Prerequisites - Node 20.10 or newer. - A funded `galileo` testnet key. Get one at [`faucet.0g.ai`](https://faucet.0g.ai). - About 10 minutes. ## Scaffold ```bash npm create 0gkit-app@latest my-chat -- --template chat cd my-chat cp .env.example .env ``` Open `.env` and paste your testnet key into `PRIVATE_KEY`. Leave `NETWORK=galileo` unless you're running a local devnet. ## Deploy the `MessageRegistry` contract The contract is 30 lines of Solidity: ```solidity // contracts/MessageRegistry.sol pragma solidity ^0.8.20; contract MessageRegistry { event MessagePosted(address indexed author, bytes32 root, uint256 ts); function post(bytes32 root, uint256 ts) external { emit MessagePosted(msg.sender, root, ts); } } ``` It does one thing: emit an event tying an author to a storage root. No on-chain text, no balance, no permission checks — those add up to gas you don't need. The text lives in 0G Storage, the chain just bookmarks it. Deploy options, in order of friction: - **Local devnet (zero setup):** run `0g dev` in another terminal. It boots an Anvil node and deploys the registry automatically. Copy the printed address into `NEXT_PUBLIC_MESSAGE_REGISTRY_ADDRESS`. - **Galileo testnet via Foundry:** ```bash forge create --rpc-url https://evmrpc-testnet.0g.ai \ --private-key $PRIVATE_KEY \ contracts/MessageRegistry.sol:MessageRegistry ``` Paste the returned `Deployed to:` address into the env. ## Walk the code ### 1. The wire format — `lib/message.ts` ```ts export type Message = { v: 1; author: `0x${string}`; ts: number; body: string }; const MAX_BODY = 4 * 1024; // 4 KiB export function encodeMessage(m: Message): Uint8Array { if (m.body.length > MAX_BODY) throw new Error("body too long"); return new TextEncoder().encode(JSON.stringify(m)); } export function decodeMessage(bytes: Uint8Array): Message { const m = JSON.parse(new TextDecoder().decode(bytes)); if (m.v !== 1) throw new Error("unknown message version"); return m; } ``` A versioned JSON envelope is the right call here. The `v: 1` field future-proofs the codec: when you change the format you bump the version and write a decoder that branches on it. Old roots stay readable forever. ### 2. Server-side write — `app/api/post/route.ts` ```ts import { NextRequest, NextResponse } from "next/server"; import { Storage } from "@foundryprotocol/0gkit-storage"; import { createTypedContract } from "@foundryprotocol/0gkit-contracts"; import { encodeMessage } from "@/lib/message"; import { registryAbi } from "@/lib/abi"; const storage = new Storage({ network: process.env.NETWORK as "galileo", privateKey: process.env.PRIVATE_KEY!, }); const registry = createTypedContract({ abi: registryAbi, address: process.env.NEXT_PUBLIC_MESSAGE_REGISTRY_ADDRESS as `0x${string}`, signer: { privateKey: process.env.PRIVATE_KEY! }, network: process.env.NETWORK as "galileo", }); export async function POST(req: NextRequest) { const { author, body } = await req.json(); const ts = Math.floor(Date.now() / 1000); const bytes = encodeMessage({ v: 1, author, ts, body }); const { root } = await storage.upload(bytes); const { txHash } = await registry.write.post([root, BigInt(ts)]); return NextResponse.json({ root, txHash, ts }); } export async function GET(req: NextRequest) { const root = req.nextUrl.searchParams.get("root") as `0x${string}`; const bytes = await storage.download(root); return new Response(bytes, { headers: { "Content-Type": "application/octet-stream" }, }); } ``` Two endpoints sharing one signer. The server holds the private key so the browser never sees it. Production refinement: rate-limit by `author`, authenticate the post call against a real wallet signature, charge per message. ### 3. The UI — `app/page.tsx` ```tsx "use client"; import { useEvent } from "@foundryprotocol/0gkit-react"; import { registryAbi } from "@/lib/abi"; import { decodeMessage } from "@/lib/message"; export default function Page() { const events = useEvent({ contract: { abi: registryAbi, address: process.env.NEXT_PUBLIC_MESSAGE_REGISTRY_ADDRESS as `0x${string}`, }, event: "MessagePosted", }); // events is an array; on reorg, rolled-back blocks are filtered out // automatically. return (
    {events.map((e) => ( ))}
); } function MessageRow({ ev }: { ev: { args: { root: `0x${string}` } } }) { const [body, setBody] = useState(null); useEffect(() => { fetch(`/api/post?root=${ev.args.root}`) .then((r) => r.arrayBuffer()) .then((buf) => decodeMessage(new Uint8Array(buf))) .then((m) => setBody(m.body)); }, [ev.args.root]); return
  • {body ?? "loading…"}
  • ; } ``` `useEvent` is the magic here. It subscribes to a single contract event through the indexer's polling loop, dedupes against the current cursor, and **filters out rolled-back blocks on reorg** automatically. You don't write the reorg handler — it's the indexer's job. ### 4. Provider plumbing — `app/providers.tsx` ```tsx "use client"; import { ZeroGIndexerProvider } from "@foundryprotocol/0gkit-react"; export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` One provider per app. Every `useEvent` / `useLogs` hook in the tree shares its single polling loop. `reorgDepth: 32` means we keep the last 32 block hashes for divergence detection — generous for testnet, conservative enough that detection is cheap. ## Run it ```bash pnpm dev ``` Open [`http://localhost:3000`](http://localhost:3000). The first message takes ~3 seconds round-trip (storage upload + on-chain confirm). Subsequent messages appear within the poll interval. Try opening two tabs as different authors — both see each other's messages without a manual refresh. ## Extend it: per-room channels Add a `room` field to the event signature so users can chat in distinct rooms: ```solidity event MessagePosted( address indexed author, bytes32 indexed room, bytes32 root, uint256 ts ); function post(bytes32 room, bytes32 root, uint256 ts) external { emit MessagePosted(msg.sender, room, root, ts); } ``` Update `useEvent` to filter by indexed topic: ```ts const events = useEvent({ contract: { abi: registryAbi, address: REGISTRY }, event: "MessagePosted", args: { room: keccak256(toUtf8Bytes(roomName)) }, // filtered server-side }); ``` Because `room` is `indexed`, the chain stores it as a topic — filtering by topic is free at the RPC layer. Don't index `body`; that defeats the storage offload. ## Production hardening ### Move the signer off the server The server-key model is fine for a tutorial. In production, the user signs their own post call: - Add `@foundryprotocol/0gkit-wallet-react` to the client bundle. - Use a wallet hook to obtain a viem `WalletClient`. - Call `createTypedContract({ ..., signer: walletClient })` client-side. - The storage upload still needs a server-side service key (storage uploads fund segments — you don't want every user to hold storage gas), or you can move uploads to your own gateway and charge users out-of-band. ### Rate limiting and abuse - Rate-limit `/api/post` by author (Upstash, Vercel KV, anything). One message per user per second is a generous bound. - Cap `body.length` server-side before the storage call. Storage gas is cheap but not free. - Reject roots whose decoded `author` doesn't match a signed payload from the browser. Otherwise anyone can impersonate. ### Durable uploads A 50-MB attachment shouldn't block a request handler. Move uploads onto [`@foundryprotocol/0gkit-jobs`](/packages/jobs): ```ts // app/api/post/route.ts becomes: const jobId = await runner.enqueue({ name: "post-message", input: { author, body }, }); return NextResponse.json({ jobId, status: "queued" }); ``` Wire a webhook on the runner to push the final root to your browser via SSE / websocket / poll. See the [durable jobs concept guide](/concepts/durable-jobs) for the full pattern. ### Observability Instrument every call so you can see latency, cost, and failure mode in your trace backend: ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; await instrument0g({ serviceName: "chat-server", exporter: { kind: "otlp" } }); ``` Every `storage.upload` and `contract.write.post` then emits a span tagged with `0gkit.op`, `0gkit.size_bytes`, `0gkit.gas_native`, etc. See the [observability concept](/concepts/observability) and the [Honeycomb guide](/concepts/observability/exporters/honeycomb). ## What you built You have: - A chat where every message has a Merkle root + an on-chain event. - A reorg-safe UI that doesn't need polling code. - A scalable storage path that doesn't congest the chain. Production handoff: rate-limit, move signing client-side, durable uploads, spans. Each of these is a one-day project, not a rewrite. ## See also - [`@foundryprotocol/0gkit-indexer`](/packages/0gkit-indexer) reference. - [`useEvent` and `useLogs`](/packages/react) hooks. - [`@foundryprotocol/0gkit-contracts`](/packages/contracts) — typed contracts and codegen. - [Durable jobs](/concepts/durable-jobs). - [Observability](/concepts/observability). ============================================================================ # Cookbook · NFT minter Route: /cookbook/nft-minter URL: https://docs.0gkit.com/cookbook/nft-minter ============================================================================ # Mint an NFT with media + metadata on 0G Storage Mint an ERC-721 token whose **metadata JSON** and **media file** both live on **0G Storage** rather than IPFS or AWS S3. `tokenURI(id)` returns `0g-storage://` where `` is the Merkle root returned by the storage upload — the chain bookmarks the storage location, never the content itself. By the end you'll have a Foundry-deployed ERC-721, typed TypeScript clients generated from the ABI, and a runnable mint flow that uploads two files and submits one on-chain transaction. ## What you're building ``` $ pnpm dev 0xRecipient "Genesis" ./genesis.png Media uploaded: 0xabc… (tx 0xfeed…) Metadata uploaded: 0xdef… (tx 0xcafe…) Minted to 0xRecipient: tx 0xmint… Mint OK. media : 0g-storage://0xabc… metadata : 0g-storage://0xdef… tx : 0xmint… ``` The recipient now owns an ERC-721 whose `tokenURI` resolves to a metadata JSON containing the title, a description, and a pointer to the media file. All of it is durable on 0G Storage. ## Prerequisites - Node 20.10 or newer. - A funded `galileo` testnet key (gas + storage segments). - [Foundry installed](https://book.getfoundry.sh/getting-started/installation): ```bash curl -L https://foundry.paradigm.xyz | bash foundryup ``` - About 25 minutes. ## Scaffold ```bash npm create 0gkit-app@latest my-nft -- --template nft-with-storage cd my-nft cp .env.example .env ``` Fill in `.env`: ``` NETWORK=galileo PRIVATE_KEY=0x… # gas + storage signer RPC_URL=https://evmrpc-testnet.0g.ai # Foundry deploy target NFT_ADDRESS= # filled in after deploy ``` ## Walk the contract `contracts/StorageNFT.sol`: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; contract StorageNFT { error NotOwner(); error AlreadyMinted(); string public name; string public symbol; address public owner; uint256 public nextId; mapping(uint256 => address) private _owners; mapping(uint256 => bytes32) private _roots; event Transfer(address indexed from, address indexed to, uint256 indexed id); constructor(string memory n, string memory s) { name = n; symbol = s; owner = msg.sender; } function mint(address to, bytes32 metadataRoot) external returns (uint256 id) { if (msg.sender != owner) revert NotOwner(); id = ++nextId; _owners[id] = to; _roots[id] = metadataRoot; emit Transfer(address(0), to, id); } function ownerOf(uint256 id) external view returns (address) { return _owners[id]; } function tokenURI(uint256 id) external view returns (string memory) { bytes32 root = _roots[id]; return string(abi.encodePacked("0g-storage://", _toHex(root))); } // … (transfer, approval — omitted here for brevity, see the template) function _toHex(bytes32 b) internal pure returns (string memory) { bytes memory out = new bytes(66); out[0] = "0"; out[1] = "x"; bytes16 alphabet = 0x30313233343536373839616263646566; for (uint256 i = 0; i < 32; i++) { out[2 + 2 * i] = alphabet[uint8(b[i]) >> 4]; out[2 + 2 * i + 1] = alphabet[uint8(b[i]) & 0x0f]; } return string(out); } } ``` Key choices: - **Inline ERC-721** (not OpenZeppelin) so a tutorial reader can read the whole contract top-to-bottom. **In production, use [`@openzeppelin/contracts`](https://github.com/OpenZeppelin/openzeppelin-contracts).** See "Production hardening" below. - **`mint(to, metadataRoot)`** takes the metadata root as a `bytes32`, not a string URI. Smaller calldata, cheaper gas, type-safe. - **`tokenURI`** prefixes with `0g-storage://` — a URI scheme that's not resolvable by browsers directly. We'll address that with a gateway in "Production hardening". ## Build the contract ```bash pnpm build:contracts # → calls `forge build`, writes out/StorageNFT.sol/StorageNFT.json ``` ## Generate the typed TS client ```bash pnpm generate:contracts # → calls `0g contracts generate --abi out/StorageNFT.sol/StorageNFT.json --out src/generated` ``` This produces `src/generated/StorageNFT.ts` — a deterministic TypeScript module with full IntelliSense on every contract method: ```ts // src/generated/StorageNFT.ts (generated) export const StorageNFTAbi = [ /* … typed ABI … */ ] as const; export type StorageNFT = { read: { name(): Promise; symbol(): Promise; ownerOf(id: bigint): Promise<`0x${string}`>; tokenURI(id: bigint): Promise; // … }; write: { mint(args: [to: `0x${string}`, metadataRoot: `0x${string}`]): Promise; }; events: { /* … */ }; }; ``` You get autocomplete on `read.tokenURI(...)`, type errors when you pass the wrong argument shape, and the runtime path goes through viem with the SP4 `createTypedContract` wrapper. ## Deploy ```bash forge script scripts/Deploy.s.sol \ --rpc-url $RPC_URL --broadcast --private-key $PRIVATE_KEY ``` The script prints `Deployed to: 0x…`. Paste that address into `NFT_ADDRESS` in your `.env`. ## Walk the mint flow — `src/mint-flow.ts` ```ts import { Storage } from "@foundryprotocol/0gkit-storage"; import { createTypedContract } from "@foundryprotocol/0gkit-contracts"; import { StorageNFTAbi } from "./generated/StorageNFT"; import { encodeMetadata } from "./metadata"; export async function runMintFlow( input: { to: `0x${string}`; title: string; mediaBytes: Uint8Array; mediaContentType: string; }, deps: { storage: Storage; nft: ReturnType } ): Promise { // 1. Upload media. const { root: mediaRoot, tx: mediaTx } = await deps.storage.upload(input.mediaBytes); // 2. Build + upload metadata referencing the media root. const metadataBytes = encodeMetadata({ name: input.title, description: `0G-native NFT for ${input.title}`, image: `0g-storage://${mediaRoot}`, contentType: input.mediaContentType, }); const { root: metadataRoot, tx: metaTx } = await deps.storage.upload(metadataBytes); // 3. Mint on chain, passing the metadata root. const { txHash } = await deps.nft.write.mint([input.to, metadataRoot]); return { kind: "ok", media: mediaRoot, metadata: metadataRoot, mediaTx: mediaTx.txHash, metaTx: metaTx.txHash, mintTx: txHash, }; } ``` The flow is intentionally three sequential steps because each one **depends** on the previous: metadata references the media root; the mint references the metadata root. There's no way to parallelize without breaking the dependency chain. For very large media (multi-MB), the upload latency is the long pole. Consider pre-uploading media before the user finishes choosing options, then committing the mint when they confirm. ## Run it ```bash pnpm dev 0xRecipient "Genesis" ./genesis.png ``` You should see three transactions: media upload, metadata upload, mint. Each prints its hash. The final line shows the mint receipt. Verify on chain: ```bash cast call $NFT_ADDRESS \ "tokenURI(uint256)(string)" 1 \ --rpc-url $RPC_URL # → 0g-storage://0xdef… ``` That's the metadata root. Resolve it: ```bash 0g storage get 0xdef… - | jq # → { "name": "Genesis", "description": "…", "image": "0g-storage://0xabc…", … } ``` ## Marketplace gateway Marketplaces like OpenSea expect `tokenURI` to be HTTPS-resolvable. The `0g-storage://` scheme isn't — it points at a content-addressed storage layer the browser doesn't understand. The solution is a **read-through gateway**: a tiny HTTP service that resolves `0g-storage://` to JSON. ```ts // gateway/api/[root].ts import { Storage } from "@foundryprotocol/0gkit-storage"; const storage = new Storage({ network: "galileo" }); export async function GET(req, { params }) { const bytes = await storage.download(params.root as `0x${string}`); return new Response(bytes, { headers: { "Content-Type": "application/json", "Cache-Control": "public, max-age=31536000, immutable", }, }); } ``` Then update your contract's `tokenURI` to return an HTTPS URL: ```solidity function tokenURI(uint256 id) external view returns (string memory) { return string(abi.encodePacked( "https://nft-gateway.your-domain.com/api/", _toHex(_roots[id]) )); } ``` Two design notes: - **Set `Cache-Control: immutable`.** A storage root is content-addressed — its content can never change. Browsers and CDNs can cache forever. - **The gateway is read-only.** No keys, no signers, no privileged ops. Deploy on Vercel / Cloudflare Workers / a static $5 box. It's a CDN proxy with a fancier resolver. ## Production hardening ### Use OpenZeppelin's ERC-721 The inline contract is for legibility, not safety. Replace with OZ: ```solidity import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract StorageNFT is ERC721, Ownable { mapping(uint256 => bytes32) private _roots; uint256 public nextId; constructor() ERC721("Storage NFT", "SNFT") Ownable(msg.sender) {} function mint(address to, bytes32 metadataRoot) external onlyOwner returns (uint256 id) { id = ++nextId; _safeMint(to, id); _roots[id] = metadataRoot; } function tokenURI(uint256 id) public view override returns (string memory) { _requireOwned(id); return string(abi.encodePacked("https://nft-gateway.your-domain.com/api/", Strings.toHexString(uint256(_roots[id]), 32))); } } ``` OZ gives you audited `safeTransferFrom`, approval logic, owner enumeration, the `IERC721` interface, and EIP-165 support — months of subtle gotchas already solved. ### Durable uploads A 50-MB media upload shouldn't block a request handler. Move uploads onto [`@foundryprotocol/0gkit-jobs`](/packages/jobs): ```ts const uploadJobId = await runner.enqueue({ name: "upload-media", input: { mediaBytes, mediaContentType }, }); // Return jobId to the client. Webhook fires when upload completes. ``` The webhook handler then enqueues the metadata upload + mint. See [durable jobs concept](/concepts/durable-jobs) for the full pattern. ### Cost estimation Storage uploads cost gas + fee per 256-KiB segment. Estimate before mint: ```ts const est = await storage.estimate(input.mediaBytes.length); console.log(`Estimated cost: ${est.fee} wei across ${est.breakdown.segments} segments`); if (est.fee > userBudget) throw new Error("upload too expensive"); ``` For a CLI smoke test: ```bash 0g estimate storage ./genesis.png ``` ### Royalty enforcement (ERC-2981) Marketplaces query ERC-2981's `royaltyInfo(tokenId, salePrice)` to read royalty terms. OZ has a base class: ```solidity import "@openzeppelin/contracts/token/common/ERC2981.sol"; contract StorageNFT is ERC721, ERC2981, Ownable { constructor() ERC2981() { _setDefaultRoyalty(owner, 500); /* 5% */ } // … remember to override supportsInterface for ERC2981 + ERC721 } ``` ### Observability Instrument once, get spans for every storage upload and every mint transaction: ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; await instrument0g({ serviceName: "nft-minter", exporter: { kind: "otlp" } }); ``` The mint flow produces a span tree per call: root span per `runMintFlow`, child spans per `storage.upload` (with `0gkit.size_bytes`, `0gkit.segments`), child span for the contract mint (`0gkit.gas_native`, `0gkit.tx_hash`). ## What you built You have: - An ERC-721 contract whose metadata and media live on 0G Storage — off-chain, content-addressed, durable. - Typed TypeScript clients generated from your Foundry artifact. - A two-upload, one-mint flow that's auditable end-to-end. - A gateway design that makes the NFT marketplace-compatible without giving up storage neutrality. - Production wiring: OZ contracts, durable uploads, royalties, observability. ## See also - [`@foundryprotocol/0gkit-storage`](/packages/storage) — upload + download. - [`@foundryprotocol/0gkit-contracts`](/packages/contracts) — typed contracts and codegen. - [`0g contracts` CLI](/cli) — `generate`, `list`, `info`. - [Durable jobs concept](/concepts/durable-jobs) — for moving uploads off the request path. ============================================================================ # Error codes Route: /errors URL: https://docs.0gkit.com/errors ============================================================================ # Error codes Every error `0gkit-*` throws carries a stable `code` from this list plus a `helpUrl` that links back here. Click a code for cause, fix, and a minimal example. > **Stuck?** Re-run any `0g` command with `--copy-issue-context`. On a thrown error, the CLI prints a redacted markdown report — error code, hint, redacted CLI args, Node + OS versions, installed package versions, and the top of the stack — ready to paste into a [new issue](https://github.com/rajkaria/0gkit/issues/new). ## CONFIG - [`CONFIG_MISSING_ENV`](/errors/CONFIG_MISSING_ENV) — Missing required environment variable - [`CONFIG_INVALID_NETWORK`](/errors/CONFIG_INVALID_NETWORK) — Unknown network name - [`CONFIG_INVALID_ADDRESS`](/errors/CONFIG_INVALID_ADDRESS) — Not a valid 0x address - [`CONFIG_INVALID_ARGUMENT`](/errors/CONFIG_INVALID_ARGUMENT) — Invalid argument passed to a primitive ## WALLET - [`WALLET_NO_PRIVATE_KEY`](/errors/WALLET_NO_PRIVATE_KEY) — No private key derived from mnemonic - [`WALLET_KMS_SIGN_FAILED`](/errors/WALLET_KMS_SIGN_FAILED) — AWS KMS Sign returned no signature - [`WALLET_KMS_PUBKEY_FAILED`](/errors/WALLET_KMS_PUBKEY_FAILED) — AWS KMS GetPublicKey failed - [`WALLET_BAD_DER_SIGNATURE`](/errors/WALLET_BAD_DER_SIGNATURE) — Malformed DER signature from KMS - [`WALLET_NO_CONNECTOR`](/errors/WALLET_NO_CONNECTOR) — No wagmi connector available - [`WALLET_CHAIN_MISMATCH`](/errors/WALLET_CHAIN_MISMATCH) — Signer chainId doesn't match active chain ## CHAIN - [`CHAIN_RPC_UNREACHABLE`](/errors/CHAIN_RPC_UNREACHABLE) — RPC endpoint unreachable - [`CHAIN_RPC_TIMEOUT`](/errors/CHAIN_RPC_TIMEOUT) — RPC call timed out - [`CHAIN_TX_REVERTED`](/errors/CHAIN_TX_REVERTED) — Transaction reverted on-chain - [`CHAIN_TX_TIMEOUT`](/errors/CHAIN_TX_TIMEOUT) — Transaction did not confirm in time - [`CHAIN_INSUFFICIENT_FUNDS`](/errors/CHAIN_INSUFFICIENT_FUNDS) — Account has insufficient funds - [`CHAIN_NONCE_TOO_LOW`](/errors/CHAIN_NONCE_TOO_LOW) — Nonce already used ## STORAGE - [`STORAGE_QUOTA_EXCEEDED`](/errors/STORAGE_QUOTA_EXCEEDED) — Storage quota exceeded - [`STORAGE_UPLOAD_FAILED`](/errors/STORAGE_UPLOAD_FAILED) — Upload to 0G storage failed - [`STORAGE_DOWNLOAD_FAILED`](/errors/STORAGE_DOWNLOAD_FAILED) — Download from 0G storage failed - [`STORAGE_ROOT_NOT_FOUND`](/errors/STORAGE_ROOT_NOT_FOUND) — Storage root not found on network - [`STORAGE_ROOT_MISMATCH`](/errors/STORAGE_ROOT_MISMATCH) — Computed root doesn't match expected - [`STORAGE_INVALID_BYTES`](/errors/STORAGE_INVALID_BYTES) — Storage input bytes empty or invalid ## COMPUTE - [`COMPUTE_PROVIDER_UNREACHABLE`](/errors/COMPUTE_PROVIDER_UNREACHABLE) — 0G Compute provider unreachable - [`COMPUTE_NO_PROVIDER`](/errors/COMPUTE_NO_PROVIDER) — No provider serving the requested model - [`COMPUTE_INFERENCE_FAILED`](/errors/COMPUTE_INFERENCE_FAILED) — Inference call returned an error - [`COMPUTE_BAD_ATTESTATION`](/errors/COMPUTE_BAD_ATTESTATION) — TEE attestation verification failed - [`COMPUTE_BUDGET_EXCEEDED`](/errors/COMPUTE_BUDGET_EXCEEDED) — Inference cost exceeded budget ## DA - [`DA_PUBLISH_FAILED`](/errors/DA_PUBLISH_FAILED) — DA publish failed - [`DA_VERIFY_FAILED`](/errors/DA_VERIFY_FAILED) — DA proof verification failed - [`DA_INVALID_PAYLOAD`](/errors/DA_INVALID_PAYLOAD) — DA payload empty or oversized ## ATTESTATION - [`ATTESTATION_BAD_SIGNATURE`](/errors/ATTESTATION_BAD_SIGNATURE) — Attestation signature did not recover - [`ATTESTATION_BAD_PAYLOAD`](/errors/ATTESTATION_BAD_PAYLOAD) — Attestation envelope malformed - [`ATTESTATION_EXPIRED`](/errors/ATTESTATION_EXPIRED) — Attestation is too old ## CONTRACTS - [`CONTRACTS_REVERTED`](/errors/CONTRACTS_REVERTED) — Typed contract write reverted - [`CONTRACTS_NO_ADDRESS`](/errors/CONTRACTS_NO_ADDRESS) — Standard contract has no known address - [`CONTRACTS_ABI_MISMATCH`](/errors/CONTRACTS_ABI_MISMATCH) — ABI doesn't match deployed contract - [`CONTRACTS_CODEGEN_FAILED`](/errors/CONTRACTS_CODEGEN_FAILED) — 0g contracts generate failed ## INDEXER - [`INDEXER_REORG_LIMIT_EXCEEDED`](/errors/INDEXER_REORG_LIMIT_EXCEEDED) — Reorg depth exceeded configured limit - [`INDEXER_CURSOR_BACKEND_UNREACHABLE`](/errors/INDEXER_CURSOR_BACKEND_UNREACHABLE) — Cursor backend unreachable - [`INDEXER_EVENT_DECODE_FAILED`](/errors/INDEXER_EVENT_DECODE_FAILED) — Log topic doesn't match any ABI event ## JOBS - [`JOBS_BACKEND_UNREACHABLE`](/errors/JOBS_BACKEND_UNREACHABLE) — Jobs backend unreachable - [`JOBS_JOB_NOT_FOUND`](/errors/JOBS_JOB_NOT_FOUND) — Job ID not found - [`JOBS_HANDLER_THREW`](/errors/JOBS_HANDLER_THREW) — Job handler threw an exception - [`JOBS_WEBHOOK_BAD_SIGNATURE`](/errors/JOBS_WEBHOOK_BAD_SIGNATURE) — Webhook HMAC signature invalid ## OBSERVABILITY - [`OBSERVABILITY_EXPORTER_FAILED`](/errors/OBSERVABILITY_EXPORTER_FAILED) — OTel exporter rejected the export ============================================================================ # ATTESTATION_BAD_PAYLOAD Route: /errors/ATTESTATION_BAD_PAYLOAD URL: https://docs.0gkit.com/errors/ATTESTATION_BAD_PAYLOAD ============================================================================ # ATTESTATION_BAD_PAYLOAD **Attestation envelope malformed** ## What happened The attestation JSON couldn't be parsed or is missing required fields (typically `payload`, `signature`, `signer`). ## How to fix Inspect the raw envelope and confirm it matches the expected shape. If it came from a provider, the provider may be running an outdated TEE image — update or pick another provider. ## Example ```ts try { parseEnvelope(rawJson); } catch (e) { // bad payload } ``` ## Reference - Namespace: `ATTESTATION` - Help URL: `https://0gkit.com/errors/ATTESTATION_BAD_PAYLOAD` - See also: [all error codes](/errors) ============================================================================ # ATTESTATION_BAD_SIGNATURE Route: /errors/ATTESTATION_BAD_SIGNATURE URL: https://docs.0gkit.com/errors/ATTESTATION_BAD_SIGNATURE ============================================================================ # ATTESTATION_BAD_SIGNATURE **Attestation signature did not recover** ## What happened The signature on a TEE attestation envelope didn't recover to the expected signer address — either tampering or a key mismatch. ## How to fix Confirm you're verifying against the right signer. For the testing fixture, use `FIXTURE_ATTESTATION_SIGNER`. For a real provider, fetch the provider's signing address from its public registry entry. ## Example ```ts import { verifyEnvelope } from "@foundryprotocol/0gkit-attestation"; const ok = verifyEnvelope(signed, expectedSigner); ``` ## Reference - Namespace: `ATTESTATION` - Help URL: `https://0gkit.com/errors/ATTESTATION_BAD_SIGNATURE` - See also: [all error codes](/errors) ============================================================================ # ATTESTATION_EXPIRED Route: /errors/ATTESTATION_EXPIRED URL: https://docs.0gkit.com/errors/ATTESTATION_EXPIRED ============================================================================ # ATTESTATION_EXPIRED **Attestation is too old** ## What happened The attestation timestamp is older than the freshness window (default 5 minutes). Stale attestations don't prove current TEE state. ## How to fix Request a fresh attestation by re-running the inference call. If you cached an attestation, drop the cache and re-fetch. ## Example ```ts // Always re-attest at request time, not on a long-lived cache const fresh = await compute.inference({ messages }); ``` ## Reference - Namespace: `ATTESTATION` - Help URL: `https://0gkit.com/errors/ATTESTATION_EXPIRED` - See also: [all error codes](/errors) ============================================================================ # CHAIN_INSUFFICIENT_FUNDS Route: /errors/CHAIN_INSUFFICIENT_FUNDS URL: https://docs.0gkit.com/errors/CHAIN_INSUFFICIENT_FUNDS ============================================================================ # CHAIN_INSUFFICIENT_FUNDS **Account has insufficient funds** ## What happened The signing account's native token balance can't cover (value + maxFee × gasLimit) for this transaction. ## How to fix Top up the account — on Galileo, use https://faucet.0g.ai. Or reduce the value/gas estimate. ## Example ```ts 0g chain faucet # surfaces the faucet URL — Galileo has no programmatic faucet ``` ## Reference - Namespace: `CHAIN` - Help URL: `https://0gkit.com/errors/CHAIN_INSUFFICIENT_FUNDS` - See also: [all error codes](/errors) ============================================================================ # CHAIN_NONCE_TOO_LOW Route: /errors/CHAIN_NONCE_TOO_LOW URL: https://docs.0gkit.com/errors/CHAIN_NONCE_TOO_LOW ============================================================================ # CHAIN_NONCE_TOO_LOW **Nonce already used** ## What happened The nonce on the outgoing transaction is lower than the account's on-chain nonce. Usually means a previous tx with the same nonce already confirmed. ## How to fix Refetch the nonce via `eth_getTransactionCount` and retry. Most wallets do this automatically; if you're managing nonces yourself, sync after every send. ## Example ```ts import { createClient } from "viem"; const nonce = await client.getTransactionCount({ address }); ``` ## Reference - Namespace: `CHAIN` - Help URL: `https://0gkit.com/errors/CHAIN_NONCE_TOO_LOW` - See also: [all error codes](/errors) ============================================================================ # CHAIN_RPC_TIMEOUT Route: /errors/CHAIN_RPC_TIMEOUT URL: https://docs.0gkit.com/errors/CHAIN_RPC_TIMEOUT ============================================================================ # CHAIN_RPC_TIMEOUT **RPC call timed out** ## What happened The RPC node accepted the request but didn't respond within the timeout window. Either the node is overloaded or the call is genuinely slow (e.g. a wide `eth_getLogs` range). ## How to fix Increase the timeout via the client option, narrow the range of `eth_getLogs`-style calls, or point at a more responsive RPC endpoint. ## Example ```ts const client = createClient({ network: "galileo", transport: http("https://...", { timeout: 30_000 }), }); ``` ## Reference - Namespace: `CHAIN` - Help URL: `https://0gkit.com/errors/CHAIN_RPC_TIMEOUT` - See also: [all error codes](/errors) ============================================================================ # CHAIN_RPC_UNREACHABLE Route: /errors/CHAIN_RPC_UNREACHABLE URL: https://docs.0gkit.com/errors/CHAIN_RPC_UNREACHABLE ============================================================================ # CHAIN_RPC_UNREACHABLE **RPC endpoint unreachable** ## What happened A JSON-RPC call to the configured 0G node failed — network down, DNS error, wrong URL, or rate-limited. ## How to fix Run `0g doctor` to ping the configured RPC. If your network is fine, the public Galileo RPC may be rate-limiting — provide your own via `--rpc` or `ZEROG_RPC_URL`. ## Example ```ts 0g doctor 0g chain balance 0xabc... --rpc https://my-own-galileo-rpc.example ``` ## Reference - Namespace: `CHAIN` - Help URL: `https://0gkit.com/errors/CHAIN_RPC_UNREACHABLE` - See also: [all error codes](/errors) ============================================================================ # CHAIN_TX_REVERTED Route: /errors/CHAIN_TX_REVERTED URL: https://docs.0gkit.com/errors/CHAIN_TX_REVERTED ============================================================================ # CHAIN_TX_REVERTED **Transaction reverted on-chain** ## What happened The transaction was accepted into a block but execution reverted. The receipt's `revertReason` (when available) names the specific contract error. ## How to fix Decode the revert reason from the receipt. Common causes: insufficient allowance, failing `require(...)` check, out-of-gas. Re-run with `--dry-run` to simulate without broadcasting. ## Example ```ts 0g storage put ./bigfile.bin --dry-run # Inspect the simulation output for the revert reason ``` ## Reference - Namespace: `CHAIN` - Help URL: `https://0gkit.com/errors/CHAIN_TX_REVERTED` - See also: [all error codes](/errors) ============================================================================ # CHAIN_TX_TIMEOUT Route: /errors/CHAIN_TX_TIMEOUT URL: https://docs.0gkit.com/errors/CHAIN_TX_TIMEOUT ============================================================================ # CHAIN_TX_TIMEOUT **Transaction did not confirm in time** ## What happened The transaction was broadcast but didn't reach the configured number of confirmations within the timeout. Possible reasons: network congestion, gas price too low, or the tx was dropped from the mempool. ## How to fix Bump the gas price and re-send (with the same nonce, to replace), or extend the wait timeout. The receipt is recoverable later via the tx hash. ## Example ```ts import { waitForReceipt } from "@foundryprotocol/0gkit-chain"; const receipt = await waitForReceipt({ hash, timeoutMs: 120_000 }); ``` ## Reference - Namespace: `CHAIN` - Help URL: `https://0gkit.com/errors/CHAIN_TX_TIMEOUT` - See also: [all error codes](/errors) ============================================================================ # COMPUTE_BAD_ATTESTATION Route: /errors/COMPUTE_BAD_ATTESTATION URL: https://docs.0gkit.com/errors/COMPUTE_BAD_ATTESTATION ============================================================================ # COMPUTE_BAD_ATTESTATION **TEE attestation verification failed** ## What happened The provider's inference completed but the attached TEE attestation didn't verify — signer mismatch, expired quote, or tampered envelope. ## How to fix Re-run the inference call; if the failure repeats, the provider's TEE may be misconfigured. Open an issue with the provider ID + attestation envelope for inspection. ## Example ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; const { output, raw } = await compute.inference({ messages }); ``` ## Reference - Namespace: `COMPUTE` - Help URL: `https://0gkit.com/errors/COMPUTE_BAD_ATTESTATION` - See also: [all error codes](/errors) ============================================================================ # COMPUTE_BUDGET_EXCEEDED Route: /errors/COMPUTE_BUDGET_EXCEEDED URL: https://docs.0gkit.com/errors/COMPUTE_BUDGET_EXCEEDED ============================================================================ # COMPUTE_BUDGET_EXCEEDED **Inference cost exceeded budget** ## What happened The estimated cost of the inference call exceeded the configured budget cap. The estimate uses ~chars/4 tokens × per-token rate. ## How to fix Either raise the budget, shorten the prompt, or lower `maxOutputTokens`. Always estimate first with `Compute.estimate(...)`. ## Example ```ts const est = await compute.estimate({ messages, maxOutputTokens: 256 }); if (est.fee > budget) throw new Error("too expensive"); ``` ## Reference - Namespace: `COMPUTE` - Help URL: `https://0gkit.com/errors/COMPUTE_BUDGET_EXCEEDED` - See also: [all error codes](/errors) ============================================================================ # COMPUTE_INFERENCE_FAILED Route: /errors/COMPUTE_INFERENCE_FAILED URL: https://docs.0gkit.com/errors/COMPUTE_INFERENCE_FAILED ============================================================================ # COMPUTE_INFERENCE_FAILED **Inference call returned an error** ## What happened The provider accepted the request but returned a non-OK response — could be a model-specific error (context window, malformed prompt) or a transient provider issue. ## How to fix Re-run with `--json` and inspect the raw provider response. If the failure is consistent, try a smaller prompt or a different model. ## Example ```ts 0g infer "..." --model llama-3.1-8b --json ``` ## Reference - Namespace: `COMPUTE` - Help URL: `https://0gkit.com/errors/COMPUTE_INFERENCE_FAILED` - See also: [all error codes](/errors) ============================================================================ # COMPUTE_NO_PROVIDER Route: /errors/COMPUTE_NO_PROVIDER URL: https://docs.0gkit.com/errors/COMPUTE_NO_PROVIDER ============================================================================ # COMPUTE_NO_PROVIDER **No provider serving the requested model** ## What happened The broker has no active provider that advertises the requested model name. ## How to fix List currently-online providers + their models with `0g infer --list-providers`, then either pick an available model or wait for a provider to come back online. ## Example ```ts 0g infer --list-providers 0g infer "hello" --model llama-3.1-8b ``` ## Reference - Namespace: `COMPUTE` - Help URL: `https://0gkit.com/errors/COMPUTE_NO_PROVIDER` - See also: [all error codes](/errors) ============================================================================ # COMPUTE_PROVIDER_UNREACHABLE Route: /errors/COMPUTE_PROVIDER_UNREACHABLE URL: https://docs.0gkit.com/errors/COMPUTE_PROVIDER_UNREACHABLE ============================================================================ # COMPUTE_PROVIDER_UNREACHABLE **0G Compute provider unreachable** ## What happened The TEE compute provider's HTTP endpoint didn't respond — network issue or provider downtime. ## How to fix Run `0g doctor` to confirm endpoint reachability. If the provider is down, list available providers via `0g infer --list-providers` and retry against another. ## Example ```ts 0g doctor 0g infer --list-providers ``` ## Reference - Namespace: `COMPUTE` - Help URL: `https://0gkit.com/errors/COMPUTE_PROVIDER_UNREACHABLE` - See also: [all error codes](/errors) ============================================================================ # CONFIG_INVALID_ADDRESS Route: /errors/CONFIG_INVALID_ADDRESS URL: https://docs.0gkit.com/errors/CONFIG_INVALID_ADDRESS ============================================================================ # CONFIG_INVALID_ADDRESS **Not a valid 0x address** ## What happened An address argument did not match the `0x` + 40 hex chars format (EIP-55 mixed-case is also accepted). ## How to fix Double-check the address — common mistakes are missing the `0x` prefix or copying a contract name instead of the address. Use `viem.getAddress(...)` to normalize if needed. ## Example ```ts import { getAddress } from "viem"; const normalized = getAddress("0xAbC..."); ``` ## Reference - Namespace: `CONFIG` - Help URL: `https://0gkit.com/errors/CONFIG_INVALID_ADDRESS` - See also: [all error codes](/errors) ============================================================================ # CONFIG_INVALID_ARGUMENT Route: /errors/CONFIG_INVALID_ARGUMENT URL: https://docs.0gkit.com/errors/CONFIG_INVALID_ARGUMENT ============================================================================ # CONFIG_INVALID_ARGUMENT **Invalid argument passed to a primitive** ## What happened A function was called with an argument that fails a precondition (wrong type, out of range, empty value, etc.). The error message names the specific field. ## How to fix Read the error message — it always says exactly which argument and what the constraint is. If you're integrating via the CLI, double-check the flag value. ## Example ```ts try { // ... } catch (e) { if (e instanceof ZeroGError && e.code === "CONFIG_INVALID_ARGUMENT") { console.error(e.message, e.hint); } } ``` ## Reference - Namespace: `CONFIG` - Help URL: `https://0gkit.com/errors/CONFIG_INVALID_ARGUMENT` - See also: [all error codes](/errors) ============================================================================ # CONFIG_INVALID_NETWORK Route: /errors/CONFIG_INVALID_NETWORK URL: https://docs.0gkit.com/errors/CONFIG_INVALID_NETWORK ============================================================================ # CONFIG_INVALID_NETWORK **Unknown network name** ## What happened The `--network` flag (or `ZEROG_NETWORK` env var) was set to a value that doesn't match any known preset. Valid values are `galileo`, `aristotle`, and `local`. ## How to fix Pick one of the supported networks, or pass an explicit RPC URL via `--rpc` if you're targeting a custom node. ## Example ```ts 0g chain balance 0xabc... --network galileo # or 0g chain balance 0xabc... --rpc https://my-custom-rpc.example ``` ## Reference - Namespace: `CONFIG` - Help URL: `https://0gkit.com/errors/CONFIG_INVALID_NETWORK` - See also: [all error codes](/errors) ============================================================================ # CONFIG_MISSING_ENV Route: /errors/CONFIG_MISSING_ENV URL: https://docs.0gkit.com/errors/CONFIG_MISSING_ENV ============================================================================ # CONFIG_MISSING_ENV **Missing required environment variable** ## What happened A required environment variable (e.g. `ZEROG_PRIVATE_KEY`, `ZEROG_BROKER_KEY`) was not set when the primitive was constructed. ## How to fix Set the variable in your shell or `.env` file before invoking the CLI / starting your app. For local dev, copy `.env.example` to `.env` and fill it in. ## Example ```ts // .env ZEROG_PRIVATE_KEY=0x... ZEROG_NETWORK=galileo ``` ## Reference - Namespace: `CONFIG` - Help URL: `https://0gkit.com/errors/CONFIG_MISSING_ENV` - See also: [all error codes](/errors) ============================================================================ # CONTRACTS_ABI_MISMATCH Route: /errors/CONTRACTS_ABI_MISMATCH URL: https://docs.0gkit.com/errors/CONTRACTS_ABI_MISMATCH ============================================================================ # CONTRACTS_ABI_MISMATCH **ABI doesn't match deployed contract** ## What happened The ABI passed to `createTypedContract({ abi, address })` doesn't match the deployed bytecode — typically a stale codegen output after the contract was redeployed. ## How to fix Re-run `0g contracts generate --abi .json --out ` to refresh the typed client. ## Example ```ts 0g contracts generate --abi ./out/MyContract.sol/MyContract.json --out ./src/typed ``` ## Reference - Namespace: `CONTRACTS` - Help URL: `https://0gkit.com/errors/CONTRACTS_ABI_MISMATCH` - See also: [all error codes](/errors) ============================================================================ # CONTRACTS_CODEGEN_FAILED Route: /errors/CONTRACTS_CODEGEN_FAILED URL: https://docs.0gkit.com/errors/CONTRACTS_CODEGEN_FAILED ============================================================================ # CONTRACTS_CODEGEN_FAILED **0g contracts generate failed** ## What happened The codegen step couldn't read the Foundry artifact JSON — either the file path is wrong, the JSON is malformed, or it's a Hardhat artifact (not currently supported). ## How to fix Confirm the path points to a Foundry artifact: it should have top-level `abi` and `contractName` fields. For Hardhat artifacts, extract the ABI with `jq '.abi'` and wrap it manually until plugin support lands. ## Example ```ts # Foundry — supported forge build 0g contracts generate --abi ./out/MyContract.sol/MyContract.json --out ./src/typed ``` ## Reference - Namespace: `CONTRACTS` - Help URL: `https://0gkit.com/errors/CONTRACTS_CODEGEN_FAILED` - See also: [all error codes](/errors) ============================================================================ # CONTRACTS_NO_ADDRESS Route: /errors/CONTRACTS_NO_ADDRESS URL: https://docs.0gkit.com/errors/CONTRACTS_NO_ADDRESS ============================================================================ # CONTRACTS_NO_ADDRESS **Standard contract has no known address** ## What happened A `standardContracts.({...})` factory was called for a contract whose address isn't published yet for the active network (e.g. Registry / AttestationVerifier on Galileo as of mid-2026). ## How to fix Pass an explicit `{ address }` when 0G publishes one. Until then, deploy your own and configure via `address` directly. ## Example ```ts // Once 0G publishes: const registry = standardContracts.registry({ address: "0x...known...", client, }); ``` ## Reference - Namespace: `CONTRACTS` - Help URL: `https://0gkit.com/errors/CONTRACTS_NO_ADDRESS` - See also: [all error codes](/errors) ============================================================================ # CONTRACTS_REVERTED Route: /errors/CONTRACTS_REVERTED URL: https://docs.0gkit.com/errors/CONTRACTS_REVERTED ============================================================================ # CONTRACTS_REVERTED **Typed contract write reverted** ## What happened A `typedContract.write.method(...)` call's simulation reverted. The revert reason is in the error message — usually a `require(...)` check or a custom error. ## How to fix Read the revert reason; it names the failing precondition. Run `typedContract.write.method([...], { dryRun: true })` for the simulation-only path. ## Example ```ts await typedContract.write.transfer([to, amount], { dryRun: true }); // Inspect the simulated revert reason without broadcasting ``` ## Reference - Namespace: `CONTRACTS` - Help URL: `https://0gkit.com/errors/CONTRACTS_REVERTED` - See also: [all error codes](/errors) ============================================================================ # DA_INVALID_PAYLOAD Route: /errors/DA_INVALID_PAYLOAD URL: https://docs.0gkit.com/errors/DA_INVALID_PAYLOAD ============================================================================ # DA_INVALID_PAYLOAD **DA payload empty or oversized** ## What happened The DA layer received a payload that's either zero-length or exceeds the per-blob size limit. ## How to fix Validate the payload length before submitting. For very large payloads, chunk into multiple DA publishes and reference them in a manifest. ## Example ```ts if (payload.length === 0 || payload.length > MAX) { throw new Error("invalid payload size"); } ``` ## Reference - Namespace: `DA` - Help URL: `https://0gkit.com/errors/DA_INVALID_PAYLOAD` - See also: [all error codes](/errors) ============================================================================ # DA_PUBLISH_FAILED Route: /errors/DA_PUBLISH_FAILED URL: https://docs.0gkit.com/errors/DA_PUBLISH_FAILED ============================================================================ # DA_PUBLISH_FAILED **DA publish failed** ## What happened The 0G DA encoder rejected the publish — typically a network blip, a payload that doesn't meet encoder requirements, or a misconfigured endpoint. ## How to fix Retry with `--dry-run` to verify the digest + estimate are sane. Confirm the configured DA URL is reachable via `0g doctor`. ## Example ```ts 0g da publish ./payload.bin --dry-run 0g da publish ./payload.bin ``` ## Reference - Namespace: `DA` - Help URL: `https://0gkit.com/errors/DA_PUBLISH_FAILED` - See also: [all error codes](/errors) ============================================================================ # DA_VERIFY_FAILED Route: /errors/DA_VERIFY_FAILED URL: https://docs.0gkit.com/errors/DA_VERIFY_FAILED ============================================================================ # DA_VERIFY_FAILED **DA proof verification failed** ## What happened The Merkle proof returned by the DA layer didn't verify against the expected digest — either the digest is wrong or the proof is corrupted. ## How to fix Recompute the digest from the original payload (`DA.digest(bytes)`) and re-verify. If still failing, re-fetch the proof from a different DA node. ## Example ```ts import { DA } from "@foundryprotocol/0gkit-da"; const digest = DA.digest(payload); const ok = await da.verify(digest, proof); ``` ## Reference - Namespace: `DA` - Help URL: `https://0gkit.com/errors/DA_VERIFY_FAILED` - See also: [all error codes](/errors) ============================================================================ # INDEXER_CURSOR_BACKEND_UNREACHABLE Route: /errors/INDEXER_CURSOR_BACKEND_UNREACHABLE URL: https://docs.0gkit.com/errors/INDEXER_CURSOR_BACKEND_UNREACHABLE ============================================================================ # INDEXER_CURSOR_BACKEND_UNREACHABLE **Cursor backend unreachable** ## What happened The configured cursor store (sqlite file, Redis instance) wasn't reachable — file permissions, missing Redis, wrong connection string. ## How to fix Verify the backend is up and configured correctly. For Redis, confirm the URL via `redis-cli -u $REDIS_URL ping`. For sqlite, check the file path is writable. ## Example ```ts import { RedisCursorStore } from "@foundryprotocol/0gkit-indexer/cursors/redis"; const cursor = new RedisCursorStore({ url: process.env.REDIS_URL }); ``` ## Reference - Namespace: `INDEXER` - Help URL: `https://0gkit.com/errors/INDEXER_CURSOR_BACKEND_UNREACHABLE` - See also: [all error codes](/errors) ============================================================================ # INDEXER_EVENT_DECODE_FAILED Route: /errors/INDEXER_EVENT_DECODE_FAILED URL: https://docs.0gkit.com/errors/INDEXER_EVENT_DECODE_FAILED ============================================================================ # INDEXER_EVENT_DECODE_FAILED **Log topic doesn't match any ABI event** ## What happened A log was fetched with a `topic0` that isn't in the contract's ABI — typically a stale ABI after a contract upgrade, or wrong contract address. ## How to fix Refresh the ABI for the contract address (via `cast interface` or a redeployed artifact) and restart the indexer. ## Example ```ts // Refresh the typed contract after upgrade const typed = createTypedContract({ abi: freshAbi, address, client }); ``` ## Reference - Namespace: `INDEXER` - Help URL: `https://0gkit.com/errors/INDEXER_EVENT_DECODE_FAILED` - See also: [all error codes](/errors) ============================================================================ # INDEXER_REORG_LIMIT_EXCEEDED Route: /errors/INDEXER_REORG_LIMIT_EXCEEDED URL: https://docs.0gkit.com/errors/INDEXER_REORG_LIMIT_EXCEEDED ============================================================================ # INDEXER_REORG_LIMIT_EXCEEDED **Reorg depth exceeded configured limit** ## What happened A reorg deeper than the indexer's configured `reorgDepth` (default 64) was detected. The indexer can't safely rewind past this limit. ## How to fix Increase `reorgDepth` if you expect deep reorgs on this chain, or reset the cursor and rebuild from a known-safe block. ## Example ```ts import { Indexer } from "@foundryprotocol/0gkit-indexer"; const indexer = new Indexer({ network: "galileo", reorgDepth: 128 }); ``` ## Reference - Namespace: `INDEXER` - Help URL: `https://0gkit.com/errors/INDEXER_REORG_LIMIT_EXCEEDED` - See also: [all error codes](/errors) ============================================================================ # JOBS_BACKEND_UNREACHABLE Route: /errors/JOBS_BACKEND_UNREACHABLE URL: https://docs.0gkit.com/errors/JOBS_BACKEND_UNREACHABLE ============================================================================ # JOBS_BACKEND_UNREACHABLE **Jobs backend unreachable** ## What happened The configured jobs backend (memory/sqlite/redis) wasn't reachable when enqueuing or claiming a job. ## How to fix Verify the backend is healthy. For Redis, ping with `redis-cli`. For sqlite, check disk space + file permissions. ## Example ```ts import { JobRunner } from "@foundryprotocol/0gkit-jobs"; import { SqliteBackend } from "@foundryprotocol/0gkit-jobs/backends/sqlite"; const runner = new JobRunner({ backend: new SqliteBackend({ path: ".jobs.db" }) }); await runner.start(); // If the sqlite file lives on an unwritable disk this throws JOBS_BACKEND_UNREACHABLE. ``` ## Reference - Namespace: `JOBS` - Help URL: `https://0gkit.com/errors/JOBS_BACKEND_UNREACHABLE` - See also: [all error codes](/errors) ============================================================================ # JOBS_HANDLER_THREW Route: /errors/JOBS_HANDLER_THREW URL: https://docs.0gkit.com/errors/JOBS_HANDLER_THREW ============================================================================ # JOBS_HANDLER_THREW **Job handler threw an exception** ## What happened The user-provided handler for a job threw — the runner catches and surfaces this so the job state machine can move to `failed` cleanly. ## How to fix Inspect the job's `error` field for the original stack. Fix the handler bug (or add retry logic via `retries:` on the job definition). ## Example ```ts import { jobs } from "@foundryprotocol/0gkit-jobs"; import { z } from "zod"; const broken = jobs.define({ name: "broken", input: z.object({}), output: z.object({}), maxAttempts: 3, handler: async () => { throw new Error("boom"); }, }); // After maxAttempts exhausted, the runner records JOBS_HANDLER_THREW on the row. ``` ## Reference - Namespace: `JOBS` - Help URL: `https://0gkit.com/errors/JOBS_HANDLER_THREW` - See also: [all error codes](/errors) ============================================================================ # JOBS_JOB_NOT_FOUND Route: /errors/JOBS_JOB_NOT_FOUND URL: https://docs.0gkit.com/errors/JOBS_JOB_NOT_FOUND ============================================================================ # JOBS_JOB_NOT_FOUND **Job ID not found** ## What happened A `jobs.get(id)` / `jobs.cancel(id)` call referenced a job that doesn't exist or has been purged. ## How to fix Check the job ID is correct. If the job is older than the retention window, it may have been purged — adjust `retentionMs` on the runner config. ## Example ```ts import { JobRunner } from "@foundryprotocol/0gkit-jobs"; // `status()` / `cancel()` throw JOBS_JOB_NOT_FOUND when no row matches. await runner.status("00000000-deadbeef"); ``` ## Reference - Namespace: `JOBS` - Help URL: `https://0gkit.com/errors/JOBS_JOB_NOT_FOUND` - See also: [all error codes](/errors) ============================================================================ # JOBS_WEBHOOK_BAD_SIGNATURE Route: /errors/JOBS_WEBHOOK_BAD_SIGNATURE URL: https://docs.0gkit.com/errors/JOBS_WEBHOOK_BAD_SIGNATURE ============================================================================ # JOBS_WEBHOOK_BAD_SIGNATURE **Webhook HMAC signature invalid** ## What happened An incoming webhook to the jobs runner had an HMAC signature that didn't match the expected secret — either tampered, wrong secret, or wrong canonicalization. ## How to fix Confirm both sides are using the same secret + canonical-JSON serialization. The runner exposes `verifyWebhookSignature(body, secret, signature)` for testing. ## Example ```ts import { jobs } from "@foundryprotocol/0gkit-jobs"; const ok = jobs.verifyWebhook(rawBody, headerSig, secret); if (!ok) throw new Error("JOBS_WEBHOOK_BAD_SIGNATURE"); ``` ## Reference - Namespace: `JOBS` - Help URL: `https://0gkit.com/errors/JOBS_WEBHOOK_BAD_SIGNATURE` - See also: [all error codes](/errors) ============================================================================ # OBSERVABILITY_EXPORTER_FAILED Route: /errors/OBSERVABILITY_EXPORTER_FAILED URL: https://docs.0gkit.com/errors/OBSERVABILITY_EXPORTER_FAILED ============================================================================ # OBSERVABILITY_EXPORTER_FAILED **OTel exporter rejected the export** ## What happened The configured OpenTelemetry exporter (OTLP, Honeycomb, Jaeger) rejected a span/metric batch — usually network or auth misconfiguration. ## How to fix Confirm the exporter endpoint + headers (e.g. `OTEL_EXPORTER_OTLP_HEADERS`) are correct. Drop logs to inspect the raw export attempt. ## Example ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; await instrument0g({ serviceName: "my-app", exporter: { kind: "otlp", endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT! }, }); // If the OTLP endpoint is unreachable on first flush, the SDK throws OBSERVABILITY_EXPORTER_FAILED. ``` ## Reference - Namespace: `OBSERVABILITY` - Help URL: `https://0gkit.com/errors/OBSERVABILITY_EXPORTER_FAILED` - See also: [all error codes](/errors) ============================================================================ # OBSERVABILITY_TRACE_DIR_NOT_SET Route: /errors/OBSERVABILITY_TRACE_DIR_NOT_SET URL: https://docs.0gkit.com/errors/OBSERVABILITY_TRACE_DIR_NOT_SET ============================================================================ # OBSERVABILITY_TRACE_DIR_NOT_SET **No trace directory configured for `0g traces`.** ## What happened You ran `0g traces list` or `0g traces inspect ` but neither the `OGKIT_TRACE_DIR` environment variable is set in your shell nor `--dir ` was passed on the command line. The CLI has no idea where to look for JSONL trace mirrors written by [`@foundryprotocol/0gkit-observability`](/packages/0gkit-observability#local-trace-mirror-ogkit_trace_dir). ## How to fix Either point the env var at a directory the process that ran your 0gkit code wrote to: ```bash export OGKIT_TRACE_DIR=.0gkit/traces 0g traces list ``` …or pass it inline for a one-off look at a directory copied from a teammate: ```bash 0g traces list --dir ./shared-traces ``` If you haven't enabled the sink yet, set `OGKIT_TRACE_DIR` in the process that calls `instrument0g(...)` (a server, a script, a Vercel Function) and re-run that workload before listing. Off by default; opt-in only. ## Example ```bash $ 0g traces list ✗ No trace directory configured. → Set OGKIT_TRACE_DIR= in the process that emitted spans, or pass --dir . Help: https://0gkit.com/errors/OBSERVABILITY_TRACE_DIR_NOT_SET ``` ## Reference - Namespace: `OBSERVABILITY` - Help URL: `https://0gkit.com/errors/OBSERVABILITY_TRACE_DIR_NOT_SET` - See also: [`0g traces`](/cli#0g-traces) · [`OGKIT_TRACE_DIR`](/packages/0gkit-observability#local-trace-mirror-ogkit_trace_dir) · [all error codes](/errors) ============================================================================ # OBSERVABILITY_TRACE_NOT_FOUND Route: /errors/OBSERVABILITY_TRACE_NOT_FOUND URL: https://docs.0gkit.com/errors/OBSERVABILITY_TRACE_NOT_FOUND ============================================================================ # OBSERVABILITY_TRACE_NOT_FOUND **Trace id not found in the configured trace directory.** ## What happened `0g traces inspect ` was called with a trace id that does not match any `-.jsonl` file in the resolved directory (`OGKIT_TRACE_DIR` or `--dir`). The most common causes: - **Typo in the id.** The id is the hex string after the date in the filename (e.g. for `2026-05-23-7f12ab.jsonl` the id is `7f12ab`). - **Wrong directory.** You're looking in a different directory than the process that emitted the spans wrote to. - **The workload never ran with `OGKIT_TRACE_DIR` set.** The sink is opt-in; nothing is written unless the env var is set in the process that calls `instrument0g(...)`. ## How to fix Run `0g traces list` to see what is actually in the directory: ```bash $ 0g traces list Traces in .0gkit/traces (3): 7f12ab... spans=4 feeWei=1200 topOp=storage.upload 3a91cd... spans=2 feeWei=400 topOp=compute.inference ... ``` Pick a trace id from that list, or pass `--dir ` if you meant a different directory: ```bash 0g traces inspect 7f12ab --dir /tmp/teammate-traces ``` ## Example ```bash $ 0g traces inspect missing-id ✗ Trace 'missing-id' not found in .0gkit/traces. → Run `0g traces list` to see what is in the directory. Trace ids are the hex string after the date in the filename. Help: https://0gkit.com/errors/OBSERVABILITY_TRACE_NOT_FOUND ``` ## Reference - Namespace: `OBSERVABILITY` - Help URL: `https://0gkit.com/errors/OBSERVABILITY_TRACE_NOT_FOUND` - See also: [`0g traces`](/cli#0g-traces) · [all error codes](/errors) ============================================================================ # OBSERVABILITY_TRACE_READ_FAILED Route: /errors/OBSERVABILITY_TRACE_READ_FAILED URL: https://docs.0gkit.com/errors/OBSERVABILITY_TRACE_READ_FAILED ============================================================================ # OBSERVABILITY_TRACE_READ_FAILED **Trace JSONL file could not be parsed.** ## What happened `0g traces inspect ` located the trace file in the configured directory but could not parse it. One of the JSONL lines is not valid JSON. Common causes: - **Truncated file.** The process was killed mid-write — the last line is partial. - **Hand-edited file.** Someone opened the file, made changes, and introduced a syntax error. - **Older toolkit version.** A trace mirrored by a pre-SP14 toolkit (if any forward-port exists) does not match the JSONL contract. The error message includes the file path and the failing line number so you can spot the bad record: ``` ✗ Could not read .0gkit/traces/2026-05-23-7f12ab.jsonl: .0gkit/traces/2026-05-23-7f12ab.jsonl:7 is not valid JSON: Unexpected end of JSON input ``` ## How to fix Easiest path — delete the file and re-run the workload with `OGKIT_TRACE_DIR` set so it regenerates cleanly: ```bash rm .0gkit/traces/2026-05-23-7f12ab.jsonl OGKIT_TRACE_DIR=.0gkit/traces node ./your-handler.js 0g traces list ``` If you can't reproduce the workload (e.g. the trace is from production): 1. Open the JSONL file in an editor. 2. Jump to the line number in the error message. 3. Either fix the line so it parses as JSON, or delete it. 4. Re-run `0g traces inspect `. The JSONL contract is one record per line; blank lines are tolerated. ## Reference - Namespace: `OBSERVABILITY` - Help URL: `https://0gkit.com/errors/OBSERVABILITY_TRACE_READ_FAILED` - See also: [`0g traces`](/cli#0g-traces) · [`OGKIT_TRACE_DIR`](/packages/0gkit-observability#local-trace-mirror-ogkit_trace_dir) · [all error codes](/errors) ============================================================================ # STORAGE_DOWNLOAD_FAILED Route: /errors/STORAGE_DOWNLOAD_FAILED URL: https://docs.0gkit.com/errors/STORAGE_DOWNLOAD_FAILED ============================================================================ # STORAGE_DOWNLOAD_FAILED **Download from 0G storage failed** ## What happened The 0G storage HTTP API returned non-2xx for a download. Could be a stale root, an unavailable node, or a network blip. ## How to fix Confirm the root exists via `0g storage exists `. If yes, retry — single-node failures are usually transient. ## Example ```ts 0g storage exists 0xroothere 0g storage get 0xroothere ./out.bin ``` ## Reference - Namespace: `STORAGE` - Help URL: `https://0gkit.com/errors/STORAGE_DOWNLOAD_FAILED` - See also: [all error codes](/errors) ============================================================================ # STORAGE_INVALID_BYTES Route: /errors/STORAGE_INVALID_BYTES URL: https://docs.0gkit.com/errors/STORAGE_INVALID_BYTES ============================================================================ # STORAGE_INVALID_BYTES **Storage input bytes empty or invalid** ## What happened An upload was attempted with an empty buffer, a non-buffer value, or bytes that exceed the per-upload size cap. ## How to fix Verify the bytes are non-empty and within size limits before calling `Storage.upload(...)`. For files, use `readFileSync` + length check. ## Example ```ts import { readFileSync } from "node:fs"; const bytes = readFileSync(path); if (bytes.length === 0) throw new Error("empty"); ``` ## Reference - Namespace: `STORAGE` - Help URL: `https://0gkit.com/errors/STORAGE_INVALID_BYTES` - See also: [all error codes](/errors) ============================================================================ # STORAGE_QUOTA_EXCEEDED Route: /errors/STORAGE_QUOTA_EXCEEDED URL: https://docs.0gkit.com/errors/STORAGE_QUOTA_EXCEEDED ============================================================================ # STORAGE_QUOTA_EXCEEDED **Storage quota exceeded** ## What happened The signer's account has uploaded more bytes than the network permits per epoch. ## How to fix Wait until the next epoch, request a quota increase via faucet support, or compress + dedup the bytes before re-uploading. ## Example ```ts try { await storage.upload(bigBuffer); } catch (e) { if (e instanceof ZeroGError && e.code === "STORAGE_QUOTA_EXCEEDED") { console.log("Try again in the next epoch."); } } ``` ## Reference - Namespace: `STORAGE` - Help URL: `https://0gkit.com/errors/STORAGE_QUOTA_EXCEEDED` - See also: [all error codes](/errors) ============================================================================ # STORAGE_ROOT_MISMATCH Route: /errors/STORAGE_ROOT_MISMATCH URL: https://docs.0gkit.com/errors/STORAGE_ROOT_MISMATCH ============================================================================ # STORAGE_ROOT_MISMATCH **Computed root doesn't match expected** ## What happened Two computed Merkle roots disagree — typically the local recomputation after download doesn't match the on-chain root, indicating tampering or a different chunking scheme. ## How to fix Re-download with the canonical chunk size (256 KiB) and recompute. If still mismatched, the upload may have been corrupted — re-upload from source bytes. ## Example ```ts import { Storage } from "@foundryprotocol/0gkit-storage"; const local = await Storage.computeRoot(bytes); ``` ## Reference - Namespace: `STORAGE` - Help URL: `https://0gkit.com/errors/STORAGE_ROOT_MISMATCH` - See also: [all error codes](/errors) ============================================================================ # STORAGE_ROOT_NOT_FOUND Route: /errors/STORAGE_ROOT_NOT_FOUND URL: https://docs.0gkit.com/errors/STORAGE_ROOT_NOT_FOUND ============================================================================ # STORAGE_ROOT_NOT_FOUND **Storage root not found on network** ## What happened The requested Merkle root is not available on the connected 0G storage network. Either the upload never landed, or you're pointed at the wrong network. ## How to fix Check the network (`--network`), then verify with `0g storage exists `. If the root truly isn't there, re-upload. ## Example ```ts 0g storage exists 0xroothere --network galileo ``` ## Reference - Namespace: `STORAGE` - Help URL: `https://0gkit.com/errors/STORAGE_ROOT_NOT_FOUND` - See also: [all error codes](/errors) ============================================================================ # STORAGE_UPLOAD_FAILED Route: /errors/STORAGE_UPLOAD_FAILED URL: https://docs.0gkit.com/errors/STORAGE_UPLOAD_FAILED ============================================================================ # STORAGE_UPLOAD_FAILED **Upload to 0G storage failed** ## What happened The 0G storage HTTP API rejected the upload (network issue, malformed body, rate limit, or backend error). ## How to fix Run `0g storage put` with `--dry-run` first to verify the bytes + estimate are sane, then re-attempt. Check `0g doctor` to confirm storage endpoint reachability. ## Example ```ts 0g storage put ./data.bin --dry-run 0g storage put ./data.bin # if dry-run succeeds ``` ## Reference - Namespace: `STORAGE` - Help URL: `https://0gkit.com/errors/STORAGE_UPLOAD_FAILED` - See also: [all error codes](/errors) ============================================================================ # WALLET_BAD_DER_SIGNATURE Route: /errors/WALLET_BAD_DER_SIGNATURE URL: https://docs.0gkit.com/errors/WALLET_BAD_DER_SIGNATURE ============================================================================ # WALLET_BAD_DER_SIGNATURE **Malformed DER signature from KMS** ## What happened The DER-encoded ECDSA signature returned by KMS couldn't be parsed into (r, s) components. Indicates either a corrupted response or — extremely rarely — a non-secp256k1 KMS key. ## How to fix Verify the KMS key's `KeySpec` is `ECC_SECG_P256K1`. If it is, this is a transient AWS issue — retry the operation. ## Example ```ts # Verify key spec aws kms describe-key --key-id --query 'KeyMetadata.KeySpec' ``` ## Reference - Namespace: `WALLET` - Help URL: `https://0gkit.com/errors/WALLET_BAD_DER_SIGNATURE` - See also: [all error codes](/errors) ============================================================================ # WALLET_CHAIN_MISMATCH Route: /errors/WALLET_CHAIN_MISMATCH URL: https://docs.0gkit.com/errors/WALLET_CHAIN_MISMATCH ============================================================================ # WALLET_CHAIN_MISMATCH **Signer chainId doesn't match active chain** ## What happened A signer derived for chainId X was asked to sign a transaction for chainId Y. EIP-155 signatures are chain-scoped, so this is a hard error. ## How to fix Either reconnect the wallet on the target chain (most wallets have a `switchChain` flow) or build a new signer pinned to the target chainId. ## Example ```ts import { useSwitchChain } from "wagmi"; const { switchChain } = useSwitchChain(); switchChain({ chainId: 16602 }); // Galileo ``` ## Reference - Namespace: `WALLET` - Help URL: `https://0gkit.com/errors/WALLET_CHAIN_MISMATCH` - See also: [all error codes](/errors) ============================================================================ # WALLET_KMS_PUBKEY_FAILED Route: /errors/WALLET_KMS_PUBKEY_FAILED URL: https://docs.0gkit.com/errors/WALLET_KMS_PUBKEY_FAILED ============================================================================ # WALLET_KMS_PUBKEY_FAILED **AWS KMS GetPublicKey failed** ## What happened The KMS `GetPublicKey` API call did not return a `PublicKey` SPKI blob. Usually means the key doesn't exist, is in a non-active state, or your role lacks `kms:GetPublicKey`. ## How to fix Run `aws kms describe-key --key-id ` to confirm the key is `Enabled`. If it is, grant the calling IAM principal `kms:GetPublicKey` on that key's resource ARN. ## Example ```ts # AWS CLI sanity check aws kms describe-key --key-id arn:aws:kms:us-east-1:...:key/... ``` ## Reference - Namespace: `WALLET` - Help URL: `https://0gkit.com/errors/WALLET_KMS_PUBKEY_FAILED` - See also: [all error codes](/errors) ============================================================================ # WALLET_KMS_SIGN_FAILED Route: /errors/WALLET_KMS_SIGN_FAILED URL: https://docs.0gkit.com/errors/WALLET_KMS_SIGN_FAILED ============================================================================ # WALLET_KMS_SIGN_FAILED **AWS KMS Sign returned no signature** ## What happened The AWS KMS `Sign` API call succeeded but returned an empty `Signature` field. Almost always indicates a misconfigured KMS key (wrong KeySpec, wrong KeyUsage, or missing permissions). ## How to fix Confirm the KMS key has `KeySpec: ECC_SECG_P256K1` and `KeyUsage: SIGN_VERIFY`, and that your IAM role has `kms:Sign` permission. Re-run with `AWS_SDK_DEBUG=1` for raw KMS response logs. ## Example ```ts import { fromKMS } from "@foundryprotocol/0gkit-wallet"; const signer = await fromKMS({ keyId: "arn:aws:kms:..." }); ``` ## Reference - Namespace: `WALLET` - Help URL: `https://0gkit.com/errors/WALLET_KMS_SIGN_FAILED` - See also: [all error codes](/errors) ============================================================================ # WALLET_NO_CONNECTOR Route: /errors/WALLET_NO_CONNECTOR URL: https://docs.0gkit.com/errors/WALLET_NO_CONNECTOR ============================================================================ # WALLET_NO_CONNECTOR **No wagmi connector available** ## What happened `useConnect()` was called from a React component, but the wagmi `WagmiProvider` has no connectors configured — so there's nothing for the user to pick. ## How to fix Wrap your app in `` where `config` has at least one connector. The `0gkit-wallet-react` starter templates wire MetaMask + WalletConnect by default. ## Example ```ts import { createConfig, http } from "wagmi"; import { injected } from "wagmi/connectors"; import { galileo } from "@foundryprotocol/0gkit-core"; export const config = createConfig({ chains: [galileo.chain], connectors: [injected()], transports: { [galileo.chain.id]: http() }, }); ``` ## Reference - Namespace: `WALLET` - Help URL: `https://0gkit.com/errors/WALLET_NO_CONNECTOR` - See also: [all error codes](/errors) ============================================================================ # WALLET_NO_PRIVATE_KEY Route: /errors/WALLET_NO_PRIVATE_KEY URL: https://docs.0gkit.com/errors/WALLET_NO_PRIVATE_KEY ============================================================================ # WALLET_NO_PRIVATE_KEY **No private key derived from mnemonic** ## What happened `mnemonicToAccount()` returned a Hierarchical Deterministic key with no `privateKey`. Either the mnemonic is invalid, or the derivation index isn't reachable on `m/44'/60'/0'/0/i`. ## How to fix Verify the mnemonic is a valid BIP-39 phrase and the index is in range (0–9 for the standard 10-account anvil/devnet derivation). ## Example ```ts import { testWallet } from "@foundryprotocol/0gkit-testing"; const w = testWallet({ index: 0 }); // anvil dev account 0 ``` ## Reference - Namespace: `WALLET` - Help URL: `https://0gkit.com/errors/WALLET_NO_PRIVATE_KEY` - See also: [all error codes](/errors) ============================================================================ # Getting started Route: /getting-started URL: https://docs.0gkit.com/getting-started ============================================================================ # Getting started ## One command: `create-0gkit-app` The fastest path from zero to a running 0G app — pick a template, get a runnable project with `.env.example`, `git init`, and dependencies installed. ```bash npm create 0gkit-app@latest my-app cd my-app 0g dev # in another terminal — starts the local devnet npm run dev ``` Works with `npm`, `pnpm`, `yarn`, and `bun`. See the [`create-0gkit-app` reference](/getting-started/create-0gkit-app) for flags, template descriptions, and troubleshooting. ## Install the packages directly 0gkit packages are published independently under the `@foundryprotocol/0gkit-*` scope on npm; install the latest with `@latest`. Install only what you need. Every primitive depends on `@foundryprotocol/0gkit-core` and `viem`. ```bash # Core + a primitive (storage shown): npm install @foundryprotocol/0gkit-core@latest @foundryprotocol/0gkit-storage@latest viem # Storage uploads also need the optional Node-only 0G SDK peers: npm install @0gfoundation/0g-storage-ts-sdk ethers ``` Prefer the CLI? You do not have to install anything: ```bash npx @foundryprotocol/0gkit-cli doctor ``` Requirements: Node `>=20.10`. `viem` `^2.21` is a peer dependency of every primitive. `@0gfoundation/0g-storage-ts-sdk`, `@0gfoundation/0g-compute-ts-sdk`, and `ethers` are **optional** peers — install them only for live storage uploads / compute inference. (See [Troubleshooting](/troubleshooting) for the exact peer matrix.) ## A 60-second end-to-end example Upload bytes to 0G Storage and read them back. This is a Node script — storage uploads need a funded signer and the Node-only SDK peers. ```ts import { Storage } from "@foundryprotocol/0gkit-storage"; const storage = new Storage({ network: "galileo", // testnet — no real funds needed privateKey: process.env.ZEROG_PRIVATE_KEY, }); // upload returns { root, tx, raw } const { root, tx } = await storage.upload(new TextEncoder().encode("hello 0G")); console.log("stored at root:", root); console.log("funding tx:", tx.txHash, `(${tx.latencyMs} ms)`); // download by root → Uint8Array const bytes = await storage.download(root); console.log("read back:", new TextDecoder().decode(bytes)); // "hello 0G" ``` Or do the same thing without writing any code, using the CLI: ```bash echo "hello 0G" > hello.txt export ZEROG_NETWORK=galileo export ZEROG_PRIVATE_KEY=0x... # funded testnet key ROOT=$(npx @foundryprotocol/0gkit-cli storage put hello.txt --json | jq -r .root) npx @foundryprotocol/0gkit-cli storage get "$ROOT" out.txt cat out.txt ``` ## Environment & keys Every entrypoint reads the same environment variables. Flags always override env; env overrides the preset default. | Variable | Purpose | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ZEROG_NETWORK` | `galileo` (default), `aristotle`, or `local`. | | `ZEROG_RPC_URL` | Override the preset JSON-RPC URL. | | `ZEROG_PRIVATE_KEY` | Signer key — funds storage upload transactions. | | `ZEROG_BROKER_KEY` | Funded 0G compute broker key for inference. | | `ZEROG_PROVIDER` | Default 0G inference provider address (for `direct()`). | | `ROUTER_API_KEY` | 0G Router key from [pc.0g.ai](https://pc.0g.ai) — enables the managed endpoint in `Compute.router()`. Optional; without it `router()` selects a provider client-side. | | `ZEROG_FOUNDRY` | `1` to enable the opt-in Foundry plugin (MCP). Off by default. | > Keys fund real transactions. Use a **testnet** key on `galileo` while > developing — you never need real funds there. See > [Troubleshooting → key handling](/troubleshooting#key-handling--safety). ## Supported networks These are the network presets from `@foundryprotocol/0gkit-core` (`networks.ts`). `getNetwork(name)` returns the `NetworkPreset`. | Name | Chain ID | RPC | Explorer | Testnet | Notes | | ----------- | -------- | ------------------------------ | --------------------------------- | ------- | ------------------------------------------------------------- | | `aristotle` | `16661` | `https://evmrpc.0g.ai` | `https://chainscan.0g.ai` | no | Mainnet. Chain id + RPC + explorer verified. | | `galileo` | `16602` | `https://evmrpc-testnet.0g.ai` | `https://chainscan-galileo.0g.ai` | yes | Testnet. Web faucet: `https://faucet.0g.ai`. **The default.** | | `local` | `31337` | `http://127.0.0.1:8545` | _(none)_ | yes | Local Anvil defaults. No explorer, no faucet. | `aristotle` and `local` are fully resolved. `galileo` is the default everywhere (testnet-first). A preset with no `rpcUrl`/`chainId` makes `createClient` throw a `ConfigError` — pass `{ rpcUrl, chainId }` explicitly to target a custom network. ## Where to go next - [Concepts](/concepts) — the shared `Receipt`, the error taxonomy, the client factory, the escape hatch. - [Packages](/packages) — full API reference and examples for all 9 packages. ============================================================================ # create-0g-app Route: /getting-started/create-0g-app URL: https://docs.0gkit.com/getting-started/create-0g-app ============================================================================ # `create-0g-app` `create-0g-app` was the original planned npm-create package, but that npm name is held by another publisher. Use the working public initializer instead: ```bash npm create 0gkit-app@latest my-app cd my-app 0g dev # in another terminal — starts the local devnet npm run dev # runs the scaffolded app ``` See the [`create-0gkit-app` reference](/getting-started/create-0gkit-app) for flags, template descriptions, and troubleshooting. ============================================================================ # create-0gkit-app Route: /getting-started/create-0gkit-app URL: https://docs.0gkit.com/getting-started/create-0gkit-app ============================================================================ # `create-0gkit-app` The fastest way to start a 0G project — one command, a runnable app, no manual setup. Pairs with [`0g dev`](/cli) for zero-faucet local development. ```bash npm create 0gkit-app@latest my-app cd my-app 0g dev # in another terminal — starts the local devnet npm run dev # runs the scaffolded app ``` Works with any package manager (`npm`, `pnpm`, `yarn`, `bun`) — `npm create 0gkit-app` will install dependencies with whichever one you invoked it through. ## What it does `create-0gkit-app` is a thin scaffolder: 1. Downloads the chosen [template](/templates) (degit a subtree of [`rajkaria/0gkit/templates/`](https://github.com/rajkaria/0gkit/tree/main/templates)) into your new project directory. 2. Writes a network-aware `.env.example` — `local` ports for `0g dev`, or the public Galileo testnet RPC. 3. Installs dependencies with your package manager (skippable with `--no-install`). 4. Runs `git init` and creates a single bootstrap commit (skippable with `--no-git`). 5. Prints a "next step" banner telling you exactly what to run. The whole flow is interactive by default. Pass `--template` and a name to skip the prompts. ## Templates | Name | Use case | | -------------------- | ------------------------------------------------------------------- | | `storage-app` | Upload + download a file, verify the Merkle root. | | `inference-app` | OpenAI-shaped chat against 0G Compute. | | `attestation-verify` | Parse + verify a TEE attestation report (pure crypto, no network). | | `mcp-agent` | Expose every 0G primitive as MCP tools for Claude/Cursor/Cline/etc. | | `react-app` | Next.js App Router app using `@foundryprotocol/0gkit-react` hooks. | See [Templates](/templates) for descriptions, what each one uses, and how the file layout is organised. ## Flags | Flag | Default | Description | | ------------------------ | ----------------- | ------------------------------------------- | | `-t, --template ` | _(prompt)_ | One of the five templates above. | | `-n, --network ` | `local` | `local` (pairs with `0g dev`) or `galileo`. | | `--package-manager ` | _(auto-detected)_ | `pnpm` / `npm` / `yarn` / `bun`. | | `--no-install` | install enabled | Skip dependency install. | | `--no-git` | git init enabled | Skip git init. | `--network local` writes a `.env.example` that points at the ports [`0g dev`](/cli) exposes (`8545` for the chain, `5678` for storage, etc.). `--network galileo` points the same keys at the public testnet endpoints — you'll need a [funded Galileo key](https://faucet.0g.ai). ## Project names `create-0gkit-app` validates the project name and refuses anything that could escape the current directory or that npm would balk at: - ASCII letters, digits, `_` and `-` only. - Max 64 characters. - No `.`, `..`, slashes, spaces, or path-escape sequences. If the target directory already exists and is non-empty, the command exits with an error and writes nothing. ## What gets generated For `--template storage-app --network local --no-git`: ```text my-app/ ├── package.json # template-provided ├── tsconfig.json # template-provided ├── src/ │ └── index.ts # template-provided ├── README.md # template-provided ├── .gitignore # template-provided └── .env.example # WRITTEN by create-0gkit-app, points at 0g dev ports ``` After `npm install` you have a runnable script. `npm run dev` (or whatever the template defines) starts it. ## Package name The originally planned `npm create 0g-app` name is held by another npm publisher, so the working public command is `npm create 0gkit-app`. ## Troubleshooting **`Directory ./my-app is not empty`** — pick a different name, or remove the existing folder. **`Template fetch failed`** — usually a network or GitHub-API issue. The templates are pulled from the public `rajkaria/0gkit` repository; check your connection and retry. Behind a corporate proxy, set `HTTPS_PROXY`. **The dev command didn't work** — make sure you `cd` into the new directory first. The banner reminds you, but it's the most common first-day mistake. **Wrong package manager** — `create-0gkit-app` detects the package manager from `npm_config_user_agent`. Force it with `--package-manager pnpm` (or `npm` / `yarn` / `bun`). ============================================================================ # Kits Route: /kits URL: https://docs.0gkit.com/kits ============================================================================ # Kits Kits are **drop-in, composable, multi-framework feature overlays** for 0G apps. A kit injects a working feature — files, dependencies, and env var stubs — into any compatible template without rewriting the base. Kits compose: some kits build on other kits automatically. > **Built with Kits — `0gkit-status`.** Our showcase app, a live 0G network > dashboard, is composed from the [`agent-memory`](/kits/agent-memory) + > [`live-feed`](/kits/live-feed) kits on the **published** `@foundryprotocol/0gkit-*` > packages, using `Compute.router()` and `0g test` as its CI gate — the epic's > end-to-end dogfood. > [See it live at apps.0gkit.com](https://apps.0gkit.com) · > [source](https://github.com/rajkaria/0gkit/tree/main/showcase/0gkit-status). ## How kits work Every kit follows a **3-tier model**: | Tier | What it contains | Always applied? | | ------------ | ---------------------------------------------------------------------- | ----------------------------------- | | **lib** | Portable core — pure TypeScript, all deps injected | Yes | | **adapters** | Per-base wiring — one file per compatible base (Next.js, Hono, MCP, …) | Only the adapter matching your base | | **ui** | React components and hooks | Only for React-capable bases | The **lib tier** has zero hard `@foundryprotocol/*` imports — dependencies are injected by the adapter. This makes each lib independently testable and framework-agnostic. ### Composition Some kits declare `composes` in their `kit.json`. When you apply `prediction-market`, the engine automatically applies `ai-oracle` first, then merges both kits' files and `package.json` dependencies. The resolution order is depth-first, deduped, and cycle-safe. ### Upgradeable overlay vs. code dump Kits are **overlays**: the engine writes only the files from the kit's tiers for your base. Your base files are unchanged except for merged `package.json` dependencies and `.env.example` additions. You own the written files — edit them freely. ## Commands ```bash # List kits compatible with your current project base 0g kits list # Filter by a specific base 0g kits list --base react-app # Print details for a kit (summary, tiers, env vars) 0g kits info ai-oracle # Apply a kit to the current project 0g add ai-oracle # Apply multiple kits at once 0g add agent-memory durable-agent # Apply at scaffold time (comma-separated) npm create 0gkit-app -- --kits agent-memory,sealed-inference ``` CLI commands verified in `packages/0gkit-cli/src/commands/kits.ts` and `packages/create-0g-app/src/index.ts`. ## Kit catalog ### Verifiable AI | Kit | Summary | | ------------------------------------------ | --------------------------------------------------------------------------------------------------------- | | [AI Oracle](/kits/ai-oracle) | Signed AI answers with commitments anchored to 0G Storage (default) or on-chain. ✓ signature verified. | | [Sealed Inference](/kits/sealed-inference) | Signed AI inference with a verification badge driven by the real verify result. Badge is never hardcoded. | ### Agent Infra | Kit | Summary | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | | [Agent Memory](/kits/agent-memory) | Persistent agent memory on 0G Storage — append-only JSONL blobs per namespace with keyword-indexed recall. | | [Durable Agent](/kits/durable-agent) | Resumable multi-step agent loop on 0gkit-jobs — step ledger persisted to 0G Storage, every step traced via OpenTelemetry. | ### Markets | Kit | Summary | | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | [Prediction Market](/kits/prediction-market) | AI-resolved, proof-anchored prediction market. Composes `ai-oracle` automatically. ✓ signature verified. | | [Live Feed](/kits/live-feed) | Reorg-safe live social feed on 0G Storage + 0gkit-indexer. Falls back to storage-only mode without a contract address. | ### Assets | Kit | Summary | | -------------------------------- | ------------------------------------------------------------------------------------------- | | [iNFT Studio](/kits/inft-studio) | Mint intelligent NFTs with AI-generated media stored on 0G Storage and attested provenance. | ### DeFi | Kit | Summary | | --------------------------------------- | ------------------------------------------------------------------------------------------------------ | | [Yield Intelligence](/kits/yield-intel) | Read-only AI yield analysis with an attested decision log. No automated execution. Testnet-only. | | [Trade Signal](/kits/trade-signal) | Advisory AI buy/sell/hold signals with attested receipts. No order execution — advisory only. Testnet. | ## After applying a kit 1. Run `pnpm install` (or `npm install`) to pull the kit's declared `dependencies` into `node_modules`. 2. Copy the new entries from `.env.example` into your `.env` and fill in the values. 3. Commit the kit files to your repo — they are yours to edit. See [Authoring a kit](/kits/authoring) if you want to build your own kit and contribute it to the catalog. ============================================================================ # agent-memory kit Route: /kits/agent-memory URL: https://docs.0gkit.com/kits/agent-memory ============================================================================ # agent-memory > **Persistent agent memory on 0G Storage — append-only JSONL blobs per > namespace with keyword-indexed recall.** Memories survive across restarts > because they live in 0G Storage, not in-process state. ## What it does The `agent-memory` kit adds a persistent, namespace-scoped memory store to your 0G app. On each operation the kit: 1. Stores memory entries as append-only JSONL blobs in **0G Storage** via `@foundryprotocol/0gkit-storage`. Each namespace maps to a separate blob path under `OG_STORAGE_NAMESPACE`. 2. Supports two operations — **append** (add a new memory item) and **recall** (retrieve items, optionally filtered by keyword). Both go through the portable lib injected with a real `Storage` adapter. 3. The `react-app` adapter exposes `POST /api/memory` (append) and `GET /api/memory?q=` (recall) as Next.js route handlers. The `mcp-agent` adapter wires the same operations as MCP tools (`memory_append`, `memory_recall`). **Recall caveat:** keyword filtering is a simple in-memory scan of the decoded JSONL blob — not a vector index. It scales to moderate memory sizes (~thousands of entries per namespace). For large corpora use a dedicated vector store and inject it via the `MemoryBackend` interface. **Cold-start resume caveat:** a new process loads the blob from 0G Storage on the first recall. If the namespace blob was written by an earlier run, those entries are recovered. If the blob does not exist (first run, or namespace cleared), the store starts empty. The same root-pointer caveat applies here as to `durable-agent`. ## Compatible bases `react-app` · `chat` · `storage-app` · `mcp-agent` ## Apply ```bash # scaffold-time npm create 0gkit-app -- --kits agent-memory # add to an existing project 0g add agent-memory ``` ## Environment variables | Variable | Example | Notes | | ---------------------- | ------------------------------ | ---------------------------------------------------------- | | `OG_STORAGE_NAMESPACE` | `agent-memory` | Namespace prefix for memory blobs in 0G Storage | | `OG_PRIVATE_KEY` | `0x...` | Private key for signing 0G Storage transactions (required) | | `OG_RPC_URL` | `https://evmrpc-testnet.0g.ai` | 0G chain RPC endpoint | ## Quick start ```bash 0g add agent-memory ``` The portable `lib/` is storage-agnostic — it takes an injected `MemoryStorage` (`putBlob(ns, data)` / `getBlob(ns)`). This is the exact bridge the `react-app` / `mcp-agent` adapters build over `@foundryprotocol/0gkit-storage`: ```ts import { Storage } from "@foundryprotocol/0gkit-storage"; import { createMemory, type MemoryStorage } from "./lib/agent-memory.js"; const storage = new Storage({ privateKey: process.env.OG_PRIVATE_KEY as `0x${string}`, rpcUrl: process.env.OG_RPC_URL!, }); // Bridge 0G Storage (content-addressed) to the mutable-namespace MemoryStorage. const roots = new Map(); const memStorage: MemoryStorage = { async putBlob(ns, data) { const { root } = await storage.upload(new TextEncoder().encode(data)); roots.set(ns, root); }, async getBlob(ns) { const root = roots.get(ns); if (!root) return undefined; const bytes = await storage.download(root); return bytes ? new TextDecoder().decode(bytes) : undefined; }, }; const memory = createMemory({ storage: memStorage, namespace: "memories" }); await memory.remember("user:favorite-chain", "0G"); const hits = await memory.recall("chain"); // MemoryEntry[] — { key, value, ts } console.log(hits); ``` > **Root-persistence caveat:** 0G Storage is content-addressed — every > `remember` writes a new immutable blob with a new `root`. The `roots` map above > lives in-process and is **not** durable across restarts. Persist the > namespace→root pointer (another 0G blob or a database) if memory must survive a > cold start. ## Tiers - **lib** — portable `createMemory()` → `AgentMemory` (`remember` / `recall` / `list`) over an injected `MemoryStorage` (no hard `@foundryprotocol/*` imports — deps injected by adapters). - **adapters** — `app/api/memory/route.ts` for `react-app`/`chat` bases (POST append + GET recall); `src/tools/memory.ts` for `mcp-agent` (MCP tool pair `memory_append` / `memory_recall`). - **ui** — `components/MemoryPanel.tsx` (scrollable memory list + append form), `hooks/useAgentMemory.ts` (React hook — `react-app` / `chat` bases only). ## 0gkit packages used - `@foundryprotocol/0gkit-storage` — `Storage.upload` / `Storage.download` for reading and writing the JSONL blob. ## Honesty note Memory persistence relies on 0G Storage content-addressing. Every append creates a new blob root; the adapter tracks the latest root. Without a persistent pointer to that root, a new process starts with an empty store. The blob never mutates in-place — 0G Storage is append-by-replace (a new upload returns a new root). ============================================================================ # ai-oracle kit Route: /kits/ai-oracle URL: https://docs.0gkit.com/kits/ai-oracle ============================================================================ # ai-oracle > **Signed AI answers with commitments anchored to 0G Storage (default) or > on-chain.** Each answer is hashed, signed by the operator key > (✓ signature verified — not TEE-quote), and anchored as an immutable proof. > Composes into `prediction-market`. ## What it does The `ai-oracle` kit adds a verifiable AI query endpoint to your 0G app. When a request arrives the kit: 1. Calls `@foundryprotocol/0gkit-compute` to run inference against the configured model. 2. Serialises the result into an oracle receipt (`{ question, answer, answerHash, ts }`). 3. Signs the receipt with the operator key via EIP-191 personal-sign: `digestJson(receipt)` is hashed and signed with `signMessage`; the badge reads **✓ signature verified** and is backed by `recoverSigner` — this is an operator-signed receipt, not a hardware TEE quote. 4. Anchors the receipt to **0G Storage** by default (immutable, content-addressed `root` = the commitment). Set `OG_ANCHOR_ONCHAIN=1` to also write the root on-chain via the bundled `Anchor.sol` contract (`@foundryprotocol/0gkit-contracts`). ## Compatible bases `react-app` · `chat` · `tee-attested-api` · `mcp-agent` ## Apply ```bash # scaffold-time npm create 0gkit-app -- --kits ai-oracle # add to an existing project 0g add ai-oracle ``` ## Environment variables | Variable | Example | Notes | | ------------------- | --------------------------------------------- | ----------------------------------------------------------------------------- | | `OG_COMPUTE_MODEL` | `neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8` | Model for inference (optional — provider default if omitted) | | `OG_PRIVATE_KEY` | `0x...` | Operator key for signing receipts and 0G transactions | | `OG_RPC_URL` | `https://evmrpc-testnet.0g.ai` | 0G chain RPC endpoint | | `OG_ANCHOR_ONCHAIN` | `1` | Set to `1` to commit receipts on-chain via `Anchor.sol` (default: 0G Storage) | | `OG_ANCHOR_ADDRESS` | `0x...` | Deployed `Anchor.sol` address — required when `OG_ANCHOR_ONCHAIN=1` | ## Quick start ```bash 0g add ai-oracle ``` `resolveOracle(deps, question)` runs inference → hashes the answer → signs the receipt → anchors it. `deps` is injected; here is the default wiring the `react-app` adapter builds (compute router + signed-receipt attestor + 0G Storage anchor). The `.env` keys are the ones in the table above: ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; import { Storage } from "@foundryprotocol/0gkit-storage"; import { digestJson } from "@foundryprotocol/0gkit-core"; import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet"; import { recoverSigner } from "@foundryprotocol/0gkit-attestation"; import { resolveOracle, type Attestor, type Anchor } from "./lib/oracle.js"; const privateKey = process.env.OG_PRIVATE_KEY as `0x${string}`; const signer = await fromPrivateKey(privateKey); const compute = new Compute({ signer }); const infer = { async infer({ prompt, model }: { prompt: string; model?: string }) { const r = await compute.router({ messages: [{ role: "user" as const, content: prompt }], ...(model ? { model } : {}), }); return { output: r.output }; }, }; const attestor: Attestor = { async sign(receipt) { const digest = digestJson(receipt); return { digest, signature: await signer.signMessage({ raw: digest }) }; }, async verify(receipt, signed, expectedSigner) { const match = digestJson(receipt).toLowerCase() === signed.digest.toLowerCase(); const recovered = await recoverSigner({ digest: signed.digest as `0x${string}`, signature: signed.signature as `0x${string}`, }); return { ok: match && recovered.toLowerCase() === expectedSigner.toLowerCase(), signer: recovered, }; }, }; const storage = new Storage({ privateKey, rpcUrl: process.env.OG_RPC_URL! }); const anchor: Anchor = { async anchor(payload) { const bytes = typeof payload === "string" ? new TextEncoder().encode(payload) : payload; const { root } = await storage.upload(bytes); return { ref: root, kind: "storage" }; }, }; const result = await resolveOracle( { infer, attestor, anchor, model: process.env.OG_COMPUTE_MODEL }, "Will 0G mainnet launch in 2026?" ); console.log(result.answer, result.answerHash, result.commitment); // { ref, kind: "storage" } ``` > **Honesty caveat:** `attestation` is a **signed receipt** (operator key signs > `digestJson(receipt)` via EIP-191), **not** a TEE-quote. The badge means "the > expected operator signed this exact receipt" — swap the `Attestor` for a real > TEE-quote verifier later without touching `resolveOracle`. ## Tiers - **lib** — `lib/oracle.ts` (portable `resolveOracle` + `OracleReceipt` type), `lib/anchor-abi.ts` (ABI constant for the optional on-chain anchor). - **adapters** — one per base (`app/api/oracle/route.ts` for Next.js, `src/routes/oracle.ts` for Hono, `src/tools/oracle.ts` for MCP). - **contracts** — `Anchor.sol` + deploy script (opt-in on-chain path only). ## Honesty note The attestation layer signs `digestJson(receipt)` via EIP-191 personal-sign (`signMessage`); verification recovers the signer via `recoverSigner`. There is no TEE quote verification in the current stack. The badge is always "✓ signature verified" — never "TEE attested". The `Attestor` interface is injected so a real TEE-quote verifier can slot in later without changing calling code. ============================================================================ # Build & publish a kit Route: /kits/authoring URL: https://docs.0gkit.com/kits/authoring ============================================================================ # Build & publish your own kit Kits are the **shared skills repo for 0G**. Anything you build once — an attested inference flow, a storage-backed cache, an indexer view — can be packaged as a kit and published to the catalog, where **every** project on 0gkit can install it with a single `0g add `. This guide takes you from an empty directory to a merged kit in four steps: 1. **Scaffold** — `0g kits new ` generates a valid kit skeleton. 2. **Build** — fill in the portable core and per-base adapters. 3. **Validate** — `pnpm kits:check` proves the kit applies and type-checks. 4. **Publish** — open a PR to the catalog. Once merged it's live for everyone. ## 1. Scaffold with `0g kits new` The fastest way to start is the scaffolder — it writes a registry-valid `kit.json`, a dependency-injected portable core, one adapter per base you name, and (for React-capable bases) a UI component + hook: ```bash 0g kits new my-feature \ --title "My Feature" \ --domain agent-infra \ --summary "Does a useful thing on 0G." \ --bases react-app,mcp-agent ``` | Flag | Default | Notes | | ----------- | ------------------------------------------ | ------------------------------------------------------------------- | | `--title` | Title Case of the name | Human-readable name shown in `0g kits list`. | | `--domain` | `agent-infra` | One of `verifiable-ai`, `agent-infra`, `markets`, `assets`, `defi`. | | `--summary` | a `TODO:` placeholder | One-sentence description. | | `--bases` | `react-app` | Comma-separated compatible bases. | | `--dir` | `templates/_kits` in-repo, otherwise `cwd` | Where the kit folder is written. | | `--dry-run` | off | Print the plan without writing files. | Run inside a clone of [`rajkaria/0gkit`](https://github.com/rajkaria/0gkit) and the command writes straight into `templates/_kits//`, adds a docs-page stub under `apps/docs/app/kits//`, and prints the exact `nav.ts` line to register. Names must be kebab-case, the domain must be one of the canonical five, and an existing kit is never overwritten. > Prefer to wire it by hand? The scaffolder is optional — the manifest schema > and directory layout below are all you need. ## 2. Build — the 3-tier model Every kit follows a **3-tier model**: ``` templates/_kits// kit.json ← manifest (required) lib/ ← portable tier (always applied) my-feature.ts adapters/ react-app/ ← files applied only when base = react-app app/api/… mcp-agent/ src/tools/… ui/ ← React tier (applied for React-capable bases) components/ hooks/ ``` **lib tier** — portable TypeScript. All 0G primitives are **injected** through a `Deps` interface, so the lib compiles and unit-tests with mocks and has **zero** hard `@foundryprotocol/*` imports. Framework-agnostic. **adapters tier** — one sub-directory per base. Each adapter wires the real 0gkit packages into the lib's injected-deps interface. Adapters are the **only** tier allowed to import `@foundryprotocol/0gkit-*`. Only the adapter matching the detected base is written to the project. **ui tier** — React components and hooks. Applied only for React-capable bases (`react-app`, `chat`). Must import from lib/hook interfaces only — never from adapters. Templates live under `templates/_kits/` — they are **not** listed in `pnpm-workspace.yaml` (see D24). The engine fetches them at apply-time via `giget`. ### kit.json schema Every field is validated by `KitManifestSchema` (zod) in `packages/0gkit-kits/src/manifest.ts`. A minimal valid `kit.json`: ```json { "name": "my-feature", "title": "My Feature", "domain": "agent-infra", "summary": "One-sentence description of what the kit adds.", "compatibleBases": ["react-app", "chat"], "tiers": { "lib": ["lib/my-feature.ts"], "adapters": { "react-app": ["app/api/my-feature/route.ts"] }, "ui": ["components/MyFeature.tsx"] }, "env": [ { "key": "OG_MY_VAR", "example": "my-value", "note": "What this env var controls" } ], "dependencies": { "@foundryprotocol/0gkit-storage": "^1.0.0" }, "devDependencies": {}, "requires": [], "composes": [], "conflicts": [] } ``` #### Field reference | Field | Type | Required | Description | | ----------------- | --------------------------- | -------- | --------------------------------------------------------------------------------------------------- | | `name` | kebab-case string | Yes | Unique kit identifier — matches the directory name. | | `title` | string | Yes | Human-readable name shown in `0g kits list`. | | `domain` | enum | Yes | One of `verifiable-ai`, `agent-infra`, `markets`, `assets`, `defi`. | | `summary` | string | Yes | One-sentence description. Shown in `0g kits info`. | | `compatibleBases` | string[] | Yes | Bases this kit supports (e.g. `react-app`, `chat`, `mcp-agent`, `storage-app`, `tee-attested-api`). | | `tiers.lib` | string[] | No | Lib-tier file paths (relative to `templates/_kits//`). Always applied. | | `tiers.adapters` | Record\ | No | Adapter files per base. Only the matching base's files are applied. | | `tiers.ui` | string[] | No | UI-tier files. Applied for React-capable bases only. | | `env` | `{ key, example, note? }[]` | No | Env vars added to `.env.example`. `note` is shown by `0g kits info`. | | `dependencies` | Record\ | No | NPM deps merged into the target project's `package.json`. | | `devDependencies` | Record\ | No | Dev deps merged into the target project's `package.json`. | | `requires` | string[] | No | 0gkit package names the target base must already declare (e.g. `"0gkit-storage"`). | | `composes` | string[] | No | Other kit names auto-applied before this kit (deps-first, deduped, cycle-safe). | | `conflicts` | string[] | No | Kit names that are illegal to apply together with this kit. | ### Neutrality rule (hard invariant) No file in `templates/_kits//` may import from `@foundryprotocol/*` app packages (anything outside `@foundryprotocol/0gkit-*`). 0gkit-\* packages are allowed in adapter files — they are injected at apply-time, not at lib level. This invariant is enforced at CI by `pnpm boundary:check`. A kit that imports an app package will fail CI. ## 3. Validate Run locally before opening a PR: ```bash # regenerate the compiled registry from templates/_kits/*/kit.json pnpm --filter @foundryprotocol/0gkit-kits build # apply the kit to every compatible base and type-check the overlay pnpm kits:check ``` `pnpm kits:check` is the real gate. For every (kit × base) combo it: 1. Validates `kit.json` against `KitManifestSchema`. 2. Confirms every file named in the manifest exists on disk. 3. Confirms the kit is in the compiled `KITS` registry. 4. Runs `applyKit()` into a temp copy of the base template. 5. Type-checks the applied overlay against the real `@foundryprotocol/0gkit-*` types (isolated `tsc` for Next.js bases; full `tsc` for the rest). ## 4. Publish to the catalog A published kit is available to every 0gkit user. To publish: 1. Fork / clone [`rajkaria/0gkit`](https://github.com/rajkaria/0gkit). 2. Put your kit under `templates/_kits//` (the scaffolder does this for you when run in-repo). 3. Add a docs page at `apps/docs/app/kits//page.mdx` and register it in `apps/docs/lib/nav.ts` under the `"Kits"` section: ```ts { title: "My Feature", href: "/kits/my-feature" }, ``` 4. Run `pnpm kits:check` and `pnpm format && pnpm docs:check`. 5. Open a PR. Once it's merged and the packages publish, anyone can run `0g add `. ### PR checklist - [ ] `kit.json` validates against `KitManifestSchema` (run `pnpm kits:check`). - [ ] `name` matches the directory name exactly (kebab-case). - [ ] `compatibleBases` lists only bases that have a matching `adapters//` entry (or lib-only if no adapter needed). - [ ] Lib tier has zero hard `@foundryprotocol/*` imports — all deps injected by adapters. - [ ] No file imports from Foundry app packages (neutrality rule). - [ ] `env` lists every environment variable the kit reads (with `example` and `note`). - [ ] `dependencies` lists every runtime npm package the kit's files import. - [ ] `composes` is set if this kit depends on another kit being applied first. - [ ] A doc page exists at `apps/docs/app/kits//page.mdx` and is registered in `apps/docs/lib/nav.ts`. - [ ] `pnpm kits:check` passes locally. - [ ] `pnpm format && pnpm docs:check` passes locally (new doc pages must be nav-registered and prettier-clean). ### Adding a doc page Every kit must have a doc page at `apps/docs/app/kits//page.mdx` and a nav entry in `apps/docs/lib/nav.ts` under the `"Kits"` section. Without both, `pnpm docs:check` will fail. The scaffolder writes a starter stub for you. Follow the shape of existing pages (see [Agent Memory](/kits/agent-memory), [AI Oracle](/kits/ai-oracle)) and include: - A blockquote summary with the honesty caveat if attestation is involved. - **What it does** — step-by-step description of real behavior. - **Compatible bases**. - **Apply** — both scaffold-time (`npm create 0gkit-app -- --kits `) and post-scaffold (`0g add `) commands. - **Environment variables** — table mirroring `kit.json` `env`. - **Tiers** — what each tier file contains. - **0gkit packages used**. - **Honesty note** — any caveats about attestation, network mode, or execution scope. ## Honesty guidelines - **Attestation** = a signed receipt. The operator key signs `digestJson(receipt)` via EIP-191 personal-sign; the signer is recovered with `recoverSigner`. There is no TEE quote verification in the current stack. Badge text must be **"✓ signature verified"** — never "TEE attested". Do not name `signEnvelope` or `verifyEnvelope` — those functions do not exist. - **Real exports only** — name only exports that exist in the package: `JobRunner`, `jobs.define`, `MemoryBackend`, `Storage.upload`, `Storage.download`, `Compute.router`, `createTypedContract`, `Erc721Abi`, `INFT_ABI`, `Indexer`, `onReorg`, `digestJson`, `recoverSigner`, `fromPrivateKey`. If unsure, read the package source before documenting. - **Execution scope** — if a kit is read-only (e.g. `yield-intel`), state that explicitly and include a non-removable disclaimer component. ============================================================================ # durable-agent kit Route: /kits/durable-agent URL: https://docs.0gkit.com/kits/durable-agent ============================================================================ # durable-agent > **Resumable multi-step agent loop with per-step durability on 0G Storage.** > Each step has an idempotent key; the step ledger is persisted to 0G Storage > so completed steps are never re-run on restart. Every executed step is traced > via OpenTelemetry (noop tracer used when OTel is not configured in the base). ## What it does The `durable-agent` kit adds a resumable multi-step agent loop to your 0G app. On each run the kit: 1. Creates a `JobRunner` via `@foundryprotocol/0gkit-jobs` backed by `MemoryBackend` (in-process, dev-friendly). For cross-process job durability swap in `SqliteBackend` from `@foundryprotocol/0gkit-jobs/backends/sqlite`. 2. Loads the step-completion ledger from **0G Storage** (if a prior run completed any steps, those roots are already in Storage). 3. For each step in the pipeline — `research → act → record` by default: - If the step key is already in the ledger → **SKIP** (no span emitted). - Otherwise: open an OTel span, run the step, write the key to the ledger, close the span. - On failure: `span.setError` is called, the span is closed, and the step is **not** marked done, so a resume will retry it. 4. Persists the updated ledger back to 0G Storage after the run. Steps are defined via `defineAgent({ steps: [...] })` and `AgentStep` (portable lib, no hard inference imports). The `researchStep` is capability-guarded: if `ctx.sealedInference` is injected by the adapter it is used; otherwise a placeholder is returned. No hard dep on any inference package. **OTel note:** the tracer is injected by the adapter. Bases that do not configure `@opentelemetry/api` receive the built-in `makeNoopTracer()` which emits no real spans. Swap in a real tracer from `@opentelemetry/api` for production observability. **Cold-start resume caveat:** full resume across process restarts requires a persistent pointer to the ledger root on 0G Storage. The adapter stores this pointer in the `OG_STORAGE_NAMESPACE` path. Without a persistent pointer (e.g. first run, or namespace cleared), the agent starts a fresh run — same caveat documented by the `agent-memory` kit. ## Compatible bases `react-app` · `chat` · `tee-attested-api` · `mcp-agent` · `storage-app` ## Apply ```bash # scaffold-time npm create 0gkit-app -- --kits durable-agent # add to an existing project 0g add durable-agent ``` ## Environment variables | Variable | Example | Notes | | ---------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `OG_PRIVATE_KEY` | `0xabc123...` | Required for 0gkit-jobs signer (signing job receipts) and 0G Storage (uploading/downloading step ledger) | | `OG_RPC_URL` | `https://rpc.0g.ai` | 0G chain RPC URL — required for 0G Storage operations | | `OG_STORAGE_NAMESPACE` | `durable-agent` | Namespace prefix for step-ledger blobs (default: `durable-agent`). Each run stored under `//steps` | | `OG_JOBS_BACKEND` | `memory` | Documents the active jobs backend. `memory` = `MemoryBackend` (default). Swap to `SqliteBackend` for cross-process durability. | ## Quick start ```bash 0g add durable-agent ``` Compose steps with `defineAgent`, then run them through `createRunner`. Each step has an idempotent `key`; the injected `AgentJobsBackend` is the durable step ledger — completed steps are skipped on resume. This uses an in-memory ledger + the built-in no-op tracer (the `storage-app` adapter swaps in a 0G-Storage-backed backend so resume survives restarts): ```ts import { defineAgent, createRunner, makeNoopTracer, type AgentJobsBackend, } from "./lib/agent.js"; import { defaultPipeline } from "./lib/steps.js"; // research → act → record const done = new Set(); const backend: AgentJobsBackend = { async getCompletedSteps() { return done; }, async markStepDone(key) { done.add(key); }, }; const agent = defineAgent({ name: "durable-agent", steps: defaultPipeline }); const runner = createRunner({ agent, backend, tracer: makeNoopTracer() }); await runner.run({ prompt: "Summarize the current state of the 0G network." }); console.log([...done]); // ["research", "act", "record"] — re-running is a no-op ``` > **Cold-start caveat:** step-skip durability is only as durable as the > `AgentJobsBackend`. The in-memory ledger above resets on restart; the > `storage-app` adapter persists it to 0G Storage and resumes from its `root`. ## Tiers - **lib** — `lib/agent.ts` (portable `defineAgent`, `createRunner`, `AgentJobsBackend`, `StepTracer`, `makeNoopTracer` interfaces + implementations), `lib/steps.ts` (sample `researchStep`, `actStep`, `recordStep` + `defaultPipeline`). - **adapters** — one per base (`app/api/agent/route.ts` for Next.js, `src/routes/agent.ts` for Hono/tee-attested-api, `src/tools/agent.ts` for MCP, `src/agent-runner.ts` for storage-app). ## Honesty note Step-level durability in the lib is guaranteed: skipped steps are provably never executed (the `completed` ledger snapshot is checked before each step). Full cold-start resume across process restarts requires a persistent root pointer in 0G Storage — the adapter stores this, but if the namespace is cleared or the storage root is lost, the next run starts fresh. The OTel integration is real when the adapter injects a real tracer from `@opentelemetry/api`; the default `makeNoopTracer()` is a documented noop. ============================================================================ # inft-studio kit Route: /kits/inft-studio URL: https://docs.0gkit.com/kits/inft-studio ============================================================================ # inft-studio > **Mint intelligent NFTs with AI-generated media stored on 0G Storage and > attested provenance.** Media and metadata are uploaded content-addressed to > 0G Storage; the token is minted via the bundled `Inft.sol` contract > (standard ERC-721 has no `mint` — `Inft.sol` adds > `mint(address to, bytes32 metadataRoot) → tokenId`). An optional signed > provenance receipt ties the model and prompt to the content hash > (✓ signature verified — not TEE-quote). ## What it does The `inft-studio` kit adds an intelligent NFT minting studio to your 0G app. On each mint the kit: 1. Uploads the raw media bytes to **0G Storage** via `@foundryprotocol/0gkit-storage`. The returned `root` is the immutable, content-addressed `contentHash`. 2. Builds enriched metadata JSON (with `mediaRoot: contentHash` embedded), uploads it to 0G Storage → `metadataRoot`. 3. Optionally attests provenance: the operator key signs `digestJson({ model, prompt, contentHash, ts })` via EIP-191 personal-sign. Badge: **✓ signature verified** — this is an operator-signed receipt, not a hardware TEE quote. A real TEE-quote verifier can slot in via the injected `Attestor` interface without changing calling code. 4. Mints via `createTypedContract({ address, abi: INFT_ABI, signer }).write.mint` (from `@foundryprotocol/0gkit-contracts`). The `tokenId` is read from the on-chain `Minted` event — **not** from the receipt return value, which only carries `{ txHash, blockNumber, latencyMs }`. 5. Returns `{ tokenId, tokenUri, contentHash, provenance? }`. **ERC-721 note:** `Erc721Abi` from `@foundryprotocol/0gkit-contracts` is the standard ERC-721 ABI and has **no** `mint` function. The kit ships `INFT_ABI` (`lib/inft-abi.ts`) for the mintable `Inft.sol` surface. Use `Erc721Abi` only for read operations (`ownerOf`, `tokenURI`, `balanceOf`). ## Compatible bases `react-app` · `chat` ## Apply ```bash # scaffold-time npm create 0gkit-app -- --kits inft-studio # add to an existing project 0g add inft-studio ``` ## Environment variables | Variable | Example | Notes | | ------------------ | --------------------------------------------- | -------------------------------------------------------------------------------------------- | | `OG_PRIVATE_KEY` | `0x...` | Operator key for signing 0G Storage transactions and provenance receipts | | `OG_RPC_URL` | `https://evmrpc-testnet.0g.ai` | 0G chain RPC endpoint | | `OG_INFT_ADDRESS` | `0x...` | Deployed `Inft.sol` contract address — deploy via `contracts/script/DeployInft.s.sol` | | `OG_COMPUTE_MODEL` | `neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8` | Default AI model for provenance attestation (optional — used in the prompt field if omitted) | ## Tiers - **lib** — `lib/inft.ts` (portable `mintInft`, `MintInput`, `MintResult`, `Provenance`, `ProvenanceReceipt`, `MintDeps` interfaces + implementation); `lib/inft-abi.ts` (`INFT_ABI` — the `Inft.sol` ABI including the `mint` function and `Minted` event; `Erc721Abi` from `0gkit-contracts` does not include `mint`). - **adapters** — `app/api/inft/mint/route.ts` (POST, orchestrates upload + mint), `app/api/inft/token/route.ts` (GET by tokenId), `app/api/inft/tokens/route.ts` (GET collection), `app/api/inft/verify/route.ts` (POST, verifies a provenance attestation). Same routes for `react-app` and `chat` bases. - **ui** — `components/MintForm.tsx` (upload + mint form), `components/Gallery.tsx` (token grid), `components/ProvenanceBadge.tsx` (attestation badge — see Provenance badge section below), `app/studio/page.tsx`. - **contracts** — `contracts/Inft.sol` + `contracts/script/DeployInft.s.sol` (Foundry deploy script). ## Provenance badge `ProvenanceBadge` (`components/ProvenanceBadge.tsx`) displays the real attestation result for a minted token. It calls `POST /api/inft/verify` with the provenance receipt and attestation, then shows one of three states: - **✓ signature verified** — `recoverSigner` confirms the operator key signed the digest and the digest matches. Click to expand attestation details. - **⚠ unverified** — verification failed (wrong signer or corrupted digest). - **○ verifying…** — request in flight. The badge always shows the live result from `attestor.verify()` — never a placeholder. Expanded details include a note that this is an EIP-191 signed receipt, not a TEE-quote / enclave attestation. ## Honesty note The attestation is a **signed receipt**: the operator private key signs `digestJson(provenanceReceipt)` via EIP-191 personal-sign (`signMessage` from `@foundryprotocol/0gkit-attestation`); the badge is backed by `recoverSigner` verifying the recovered address. There is no TEE-quote verification in the current stack. The `Attestor` interface is injected so a real TEE-quote verifier can slot in later without changing the lib or adapter calling code. ============================================================================ # live-feed kit Route: /kits/live-feed URL: https://docs.0gkit.com/kits/live-feed ============================================================================ # live-feed > **Reorg-safe live social feed on 0G Storage + 0gkit-indexer.** Posts are > content-addressed blobs in 0G Storage. Ordering and reorg-drop come from > the Indexer tracking on-chain `PostPublished` events when > `OG_FEED_CONTRACT_ADDRESS` is set. Without a contract address the adapter > runs in **storage-only mode** — posts are stored and streamed but reorg-drop > is not active. ## What it does The `live-feed` kit adds a live post feed to your 0G app. On each `POST /api/feed` call the kit: 1. Serialises the post payload (`{ content, author, ts }`) to JSON and uploads it to **0G Storage** via `@foundryprotocol/0gkit-storage`. The immutable content-addressed `root` is the permanent post identifier. 2. Appends a `FeedPost` cursor entry (root + denormalized content + author + block number) to the in-process `FeedCursor`. 3. The `FeedCursor` fires all `subscribe()` listeners with the new post. **Reorg-safety (requires `OG_FEED_CONTRACT_ADDRESS`):** When a deployed `FeedEvents` contract address is provided, the adapter wires `@foundryprotocol/0gkit-indexer` to subscribe to `PostPublished` on-chain events. The Indexer's `onReorg` callback fires the cursor's reorg signal (`isReorg=true`) for any posts whose backing events were rolled back. The UI hook (`useLiveFeed`) and the `FeedStream` component remove those posts from the displayed feed. Without the contract address the adapter omits the Indexer subscription — posts are stored and delivered but reorg-drop is inactive. **Streaming:** `GET /api/feed` (SSE) calls `feed.stream(cb)`, which immediately flushes all existing posts (`isOrphan=false`) then streams new arrivals in real time. The React `useLiveFeed` hook and `FeedStream` component consume this stream. ## Compatible bases `react-app` · `chat` ## Apply ```bash # scaffold-time npm create 0gkit-app -- --kits live-feed # add to an existing project 0g add live-feed ``` ## Environment variables | Variable | Example | Notes | | -------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `OG_PRIVATE_KEY` | `0x...` | Required — signs 0G Storage transactions (post uploads) | | `OG_RPC_URL` | `https://evmrpc-testnet.0g.ai` | Required — 0G chain RPC endpoint for Storage and Indexer | | `OG_FEED_CONTRACT_ADDRESS` | `0xYourDeployedFeedEventsContract` | **Required for reorg-safety.** Deployed `FeedEvents` contract. Without it, adapter runs in storage-only mode. | | `OG_FEED_NAMESPACE` | `live-feed` | Storage namespace prefix for feed blobs (default: `live-feed`) | ## Quick start ```bash 0g add live-feed ``` `createFeed` takes an injected `FeedStorage` (0G Storage) and a `FeedCursor` (ordering / reorg source). `post()` uploads the payload and appends a cursor entry; `stream(cb)` flushes existing posts then streams new ones, flagging reorg-orphaned posts with `isOrphan=true`: ```ts import { Storage } from "@foundryprotocol/0gkit-storage"; import { createFeed, type FeedStorage, type FeedCursor, type FeedPost, } from "./lib/feed.js"; const og = new Storage({ privateKey: process.env.OG_PRIVATE_KEY as `0x${string}`, rpcUrl: process.env.OG_RPC_URL!, }); const storage: FeedStorage = { upload: (data) => og.upload(data), download: (root) => og.download(root), }; // In-process cursor. With OG_FEED_CONTRACT_ADDRESS set, the adapter drives // reorg-drop from the 0gkit-indexer onReorg callback (isReorg=true) instead. const posts: FeedPost[] = []; const listeners: Array<(p: FeedPost[], reorg: boolean) => void> = []; const cursor: FeedCursor = { async append(post) { posts.push(post); listeners.forEach((fn) => fn([post], false)); }, subscribe(onBatch) { listeners.push(onBatch); return () => listeners.splice(listeners.indexOf(onBatch), 1); }, async list() { return [...posts]; }, }; const feed = createFeed({ storage, cursor }); const unsubscribe = feed.stream((post, isOrphan) => { if (isOrphan) console.log("reorg-dropped:", post.root); else console.log("live:", post.author, post.content); }); await feed.post({ content: "gm from 0G", author: "0xYou" }); unsubscribe(); ``` > **Reorg-safety caveat:** the reorg-drop guarantee only fires when the > `FeedCursor` emits `isReorg=true`. In the real adapter that signal comes from > the `0gkit-indexer` tracking on-chain `PostPublished` events, which needs a > deployed `FeedEvents` contract (`OG_FEED_CONTRACT_ADDRESS`). Without it the > feed runs in **storage-only mode** — posts are stored and streamed, no reorg > signal. ## Tiers - **lib** — `lib/feed.ts` (portable `createFeed`, `FeedStorage`, `FeedCursor`, `FeedPost`, `PostInput` interfaces + implementation). No package imports — all deps injected by adapters. - **adapters** — `app/api/feed/route.ts` (Next.js / chat): wires `@foundryprotocol/0gkit-storage` as `FeedStorage` and an in-process cursor as `FeedCursor`; optionally wires `@foundryprotocol/0gkit-indexer` when `OG_FEED_CONTRACT_ADDRESS` is set. - **ui** — `hooks/useLiveFeed.ts` (SSE stream hook), `components/FeedStream.tsx` (live post list with reorg-drop), `app/feed/page.tsx` (demo page). ## Honesty note The portable lib's reorg-drop guarantee is proven by unit tests: when the injected `FeedCursor` fires its `subscribe` callback with `isReorg=true`, those posts are surfaced as orphans and the caller removes them — they are never delivered as live items. In the real adapter, this reorg signal flows from the Indexer's `onReorg` callback. **This reorg-safety is only active when `OG_FEED_CONTRACT_ADDRESS` is set and the `FeedEvents` contract is deployed.** Without it, the adapter is storage-only and no reorg signal is emitted. ============================================================================ # prediction-market kit Route: /kits/prediction-market URL: https://docs.0gkit.com/kits/prediction-market ============================================================================ # prediction-market > **AI-resolved, proof-anchored prediction market.** Lifecycle: open → bet → > resolve (via `ai-oracle`) → settle. Composes `ai-oracle` so applying > `prediction-market` auto-applies `ai-oracle` first. Resolution receipts are > signed by the operator key (✓ signature verified — not TEE-quote) and > anchored to 0G Storage (or on-chain opt-in). ## What it does The `prediction-market` kit adds a full prediction market lifecycle to your 0G app: 1. **Open** — create a market with a question and deadline. 2. **Bet** — participants place YES/NO positions. 3. **Resolve** — at deadline, the kit calls `resolveOracle` (from the composed `ai-oracle` lib) to get a signed, anchored AI verdict. 4. **Settle** — `resolveMarket` marks the market settled and stores the signed resolution receipt in 0G Storage for auditability (immutable content-addressed commitment). Set `OG_ANCHOR_ONCHAIN=1` to also write the root on-chain via `Anchor.sol`. Because `prediction-market` declares `"composes": ["ai-oracle"]`, running `0g add prediction-market` applies `ai-oracle` first, then the market overlay, and merges dependencies from both kits into `package.json`. ## Compatible bases `react-app` · `chat` · `tee-attested-api` ## Apply ```bash # scaffold-time (applies ai-oracle automatically) npm create 0gkit-app -- --kits prediction-market # add to an existing project 0g add prediction-market ``` ## Environment variables Inherits all `ai-oracle` env vars, plus: | Variable | Example | Notes | | ---------------------- | --------------------------------------------- | ----------------------------------------------------------------------- | | `OG_COMPUTE_MODEL` | `neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8` | Model for resolution oracle (optional) | | `OG_PRIVATE_KEY` | `0x...` | Operator key for signing resolution receipts | | `OG_RPC_URL` | `https://evmrpc-testnet.0g.ai` | 0G chain RPC endpoint | | `OG_ANCHOR_ONCHAIN` | `1` | Set to `1` to commit resolution receipts on-chain (default: 0G Storage) | | `OG_ANCHOR_ADDRESS` | `0x...` | Deployed `Anchor.sol` address — required when `OG_ANCHOR_ONCHAIN=1` | | `OG_STORAGE_NAMESPACE` | `prediction-market` | Namespace prefix for market blobs in 0G Storage | ## Quick start ```bash 0g add prediction-market # composes ai-oracle — applied automatically first ``` `openMarket` / `placeBet` / `resolveMarket` drive the lifecycle over an injected `MarketStorage`. `resolveMarket` delegates to the composed [`ai-oracle`](/kits/ai-oracle) `resolveOracle` for the settlement answer: ```ts import { Storage } from "@foundryprotocol/0gkit-storage"; import { createMarketStore, openMarket, placeBet, resolveMarket, type MarketStorage, } from "./lib/market.js"; import { resolveOracle } from "./lib/oracle.js"; // co-located by composition const storage = new Storage({ privateKey: process.env.OG_PRIVATE_KEY as `0x${string}`, rpcUrl: process.env.OG_RPC_URL!, }); // Content-addressed → mutable-namespace bridge (persist `roots` in production). const roots = new Map(); const marketStorage: MarketStorage = { async putBlob(ns, data) { const { root } = await storage.upload(new TextEncoder().encode(data)); roots.set(ns, root); }, async getBlob(ns) { const root = roots.get(ns); if (!root) return undefined; const bytes = await storage.download(root); return bytes ? new TextDecoder().decode(bytes) : undefined; }, }; const store = createMarketStore(marketStorage); const market = await openMarket(store, { question: "Will 0G mainnet launch in 2026?", closesAt: Date.now() + 86_400_000, }); await placeBet(store, { marketId: market.id, bettor: "0xYou", prediction: "YES", amount: 10, }); // `oracleDeps` = { infer, attestor, anchor, model } — see the ai-oracle quick start. const boundOracle = (_deps: unknown, question: string) => resolveOracle(oracleDeps, question); const { market: settled, receipt } = await resolveMarket( { resolveOracle: boundOracle, storage: marketStorage }, market.id ); console.log(settled.state, receipt.answer, receipt.commitment); ``` > **Honesty caveat:** resolution uses the composed `ai-oracle` **signed receipt** > (operator key signs `digestJson(receipt)`, verified via `recoverSigner`) — > **not** a TEE-quote. Settle is folded into `resolveMarket`; there is no separate > `settleMarket`. ## Tiers - **lib** — `lib/market.ts` (portable `Market`, `Bet`, `Resolution` types, `openMarket` / `placeBet` / `resolveMarket` helpers; settle is folded into resolve — there is no separate `settleMarket`). - **adapters** — one per base (`app/api/markets/route.ts` for Next.js, `src/routes/markets.ts` for Express). - **ui** — `app/markets/page.tsx` (market list page), `components/MarketBoard.tsx`, `components/CreateMarketForm.tsx` (React bases only). ## Composition `prediction-market` `composes` `ai-oracle`. The engine resolves the full closure deps-first (deduped, cycle-safe) before writing any files, so both kits' files land in the project and both kits' `package.json` dependencies are merged. ## Honesty note Resolution uses `resolveOracle` from the composed `ai-oracle` lib, which produces an **operator-signed receipt**: `digestJson(receipt)` signed via EIP-191 personal-sign (`signMessage`); the signer is recovered with `recoverSigner`. There is no TEE quote verification. The badge is "✓ signature verified". The `Attestor` interface is injected so a real TEE-quote verifier can slot in later without changing market code. ============================================================================ # sealed-inference kit Route: /kits/sealed-inference URL: https://docs.0gkit.com/kits/sealed-inference ============================================================================ # sealed-inference > **Signed AI inference with a verification badge driven by the real verify > result.** Each response is signed by the operator key (✓ signature verified > — not TEE-quote), and the UI badge reflects the actual verification outcome > — never hardcoded, never always-green. ## What it does The `sealed-inference` kit adds a verifiable chat or completion endpoint with a React badge component. On each request it: 1. Runs inference via `@foundryprotocol/0gkit-compute`. 2. Signs the inference receipt server-side: `digestJson(receipt)` is hashed and signed with `signMessage` (EIP-191 personal-sign); the signer is recovered via `recoverSigner` and compared to the expected operator address. 3. Returns the signed receipt and a `verified` boolean to the client. 4. The `SealedChat` React component renders **✓ signature verified** when `verified` is `true`, or a clear failure badge otherwise. Verification happens server-side inside `sealedInfer`; the component reflects that result — it never calls `verifyEnvelope` directly. ## Compatible bases `react-app` · `chat` · `tee-attested-api` · `mcp-agent` ## Apply ```bash # scaffold-time npm create 0gkit-app -- --kits sealed-inference # add to an existing project 0g add sealed-inference ``` ## Environment variables | Variable | Example | Notes | | --------------------- | --------------------------------------------- | ------------------------------------------------------------ | | `OG_COMPUTE_MODEL` | `neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8` | Model for inference (optional — provider default if omitted) | | `OG_PRIVATE_KEY` | `0x...` | Operator key for signing inference receipts | | `OG_RPC_URL` | `https://evmrpc-testnet.0g.ai` | 0G chain RPC endpoint | | `OG_ATTESTOR_ADDRESS` | `0x...` | Expected signer address that `recoverSigner` checks against | ## Quick start ```bash 0g add sealed-inference ``` `sealedInfer(deps, prompt, expectedSigner)` runs inference, signs the receipt, verifies it against `expectedSigner`, and returns a `verified` boolean the UI badge reflects. The default wiring uses the same signed-receipt attestor as [`ai-oracle`](/kits/ai-oracle) — `OG_ATTESTOR_ADDRESS` is the expected signer: ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; import { digestJson } from "@foundryprotocol/0gkit-core"; import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet"; import { recoverSigner } from "@foundryprotocol/0gkit-attestation"; import { sealedInfer, type Attestor } from "./lib/sealed.js"; const signer = await fromPrivateKey(process.env.OG_PRIVATE_KEY as `0x${string}`); const compute = new Compute({ signer }); const infer = { async infer({ prompt, model }: { prompt: string; model?: string }) { const r = await compute.router({ messages: [{ role: "user" as const, content: prompt }], ...(model ? { model } : {}), }); return { output: r.output }; }, }; const attestor: Attestor = { async sign(receipt) { const digest = digestJson(receipt); return { digest, signature: await signer.signMessage({ raw: digest }) }; }, async verify(receipt, signed, expectedSigner) { const match = digestJson(receipt).toLowerCase() === signed.digest.toLowerCase(); const recovered = await recoverSigner({ digest: signed.digest as `0x${string}`, signature: signed.signature as `0x${string}`, }); return { ok: match && recovered.toLowerCase() === expectedSigner.toLowerCase(), signer: recovered, }; }, }; const result = await sealedInfer( { infer, attestor, model: process.env.OG_COMPUTE_MODEL }, "Summarize the 0G network in one sentence.", process.env.OG_ATTESTOR_ADDRESS! ); console.log(result.text, result.verified); // verified: true | false ``` > **Honesty caveat:** `verified` is a **signed-receipt** check — the expected > operator key signed this exact receipt — **not** a TEE-quote verification. > `sealedInfer` never throws on a tampered signature; it returns > `verified: false` so the badge shows the real outcome (never hardcoded green). ## Tiers - **lib** — `lib/sealed.ts` (portable `SealedInference`, `SealedEnvelope` type, `sealedInfer` helper). - **adapters** — one per base (`app/api/sealed/route.ts` for Next.js, `src/routes/sealed.ts` for Hono, `src/tools/sealed.ts` for MCP). - **ui** — `components/SealedChat.tsx` (badge + chat UI), `hooks/useSealedInference.ts` (React hook for React bases only). ## Honesty note The attestation layer signs `digestJson(receipt)` via EIP-191 personal-sign (`signMessage`); verification recovers the signer via `recoverSigner` and compares it to the expected operator address. There is no TEE quote verification in the current stack. The `Attestor` interface is injected so a real TEE-quote verifier can slot in later. The badge reflects the server's `verified` result — it is never hardcoded green. ============================================================================ # trade-signal kit Route: /kits/trade-signal URL: https://docs.0gkit.com/kits/trade-signal ============================================================================ # trade-signal > ⚠ **Advisory only — not financial advice; no orders executed.** > This kit generates AI buy/sell/hold signals for informational purposes only. > It does **not** place orders, move funds, or execute any transaction on your > behalf. You are solely responsible for any decisions you make. This is a > testnet demo running on the Galileo network — mainnet and automated execution > are intentionally out of scope. ## What it does The `trade-signal` kit adds an **advisory** trading-signal feature to your 0G app. The public API has **no** `execute`, `trade`, `swap`, `send`, or `transfer` function — this is a signal generator with an attested audit trail, not an auto-trader. On each run the kit: 1. Accepts a read-only `SignalInput` (asset, current price, recent price history, optional indicators) from the caller — no on-chain read is performed by the lib. 2. Calls `@foundryprotocol/0gkit-compute` via `compute.router()` (K7 — routed provider selection with a client-side fallback) with a structured prompt asking for an advisory action, a confidence, and a one-sentence rationale. The model is instructed: no order instructions, no profit guarantees, no risk-free claims. 3. Parses the JSON response and returns a validated `Signal` (`action ∈ buy|sell|hold`, `confidence` clamped to `[0,1]`, `rationale`). On malformed output it returns a **safe `hold` default** (confidence `0`) — it never fabricates a buy/sell and never throws. 4. Optionally attests the signal: `logSignal` signs a `SignalReceipt` via EIP-191 personal-sign and uploads the full record to **0G Storage** as an immutable audit entry; `attestSignal` signs + verifies a receipt without storing (used by the MCP tool). **Neither executes anything.** The `AdvisoryBanner` component is **non-removable** — it is rendered unconditionally at the top of the signal page with fixed copy: "Advisory only — not financial advice; no orders executed." ## Compatible bases `react-app` · `chat` · `tee-attested-api` · `mcp-agent` ## Apply ```bash # scaffold-time npm create 0gkit-app -- --kits trade-signal # add to an existing project 0g add trade-signal ``` ## Environment variables | Variable | Example | Notes | | --------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------- | | `OG_PRIVATE_KEY` | `0x...` | Operator key for signing signal receipts and 0G Storage transactions | | `OG_RPC_URL` | `https://evmrpc-testnet.0g.ai` | 0G chain RPC endpoint (Galileo testnet default — mainnet is out of scope) | | `OG_COMPUTE_MODEL` | `neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8` | Model for signal inference (optional — uses provider default if omitted) | | `OG_ATTESTOR_ADDRESS` | `0x...` | Expected signer address the `signal_verify` MCP tool (and the sign+verify path) checks against | ## Quick start ```bash 0g add trade-signal ``` `analyzeSignal(input, deps)` returns an advisory, read-only `Signal`. `logSignal(signal, deps)` records the signal as a signed receipt on 0G Storage — it does **not** execute anything: ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet"; import { analyzeSignal, type SignalInput } from "./lib/signal.js"; const privateKey = process.env.OG_PRIVATE_KEY as `0x${string}`; const signer = await fromPrivateKey(privateKey); const compute = new Compute({ signer }); const input: SignalInput = { asset: "ETH", currentPrice: 3200, history: [3100, 3150, 3180, 3200], indicators: { rsi14: 58, sma20: 3120 }, }; const signal = await analyzeSignal(input, { compute: { async infer({ prompt, model }) { const r = await compute.router({ messages: [{ role: "user" as const, content: prompt }], ...(model ? { model } : {}), }); return { output: r.output }; }, }, model: process.env.OG_COMPUTE_MODEL, }); // { action: "buy"|"sell"|"hold", confidence: 0..1, rationale }; safe "hold" on malformed output console.log(signal); ``` > **Honesty caveat:** `trade-signal` is advisory-only by design — the public API > has **no** `execute` / `trade` / `swap` / `send` / `transfer` (enforced by a > negative test). `logSignal` records the signal with a **signed receipt** (not > a TEE-quote); the user decides and acts manually. Testnet-default; mainnet and > automated execution are intentionally out of scope. ## Tiers - **lib** — `lib/signal.ts` (portable `analyzeSignal`, `Signal`, `SignalInput`, `SignalAction`, `AnalyzeSignalDeps` — the only analysis function; read-only, safe `hold` default); `lib/signalLog.ts` (portable `logSignal` + `attestSignal`, `SignalReceipt`, `SignalRecord`, `SealedSignal` — attest to 0G Storage or sign+verify without storing; neither executes a transaction). - **adapters** — `app/api/signal/route.ts` for `react-app` and `chat` bases (POST dispatcher: `analyze` + `log`); `src/routes/signal.ts` for `tee-attested-api` (Hono `buildSignalRouter`); `src/tools/signal.ts` for `mcp-agent` (`trade_signal` + `signal_verify` MCP tools + `mcpToolPlugin`). - **ui** — `components/AdvisoryBanner.tsx` (**non-removable** disclaimer, leads the page), `components/SignalPanel.tsx` (advisory action badge + attested receipt button), `hooks/useTradeSignal.ts`, `app/signal/page.tsx`. ## Attested signal receipt `logSignal` (`lib/signalLog.ts`) records the advisory signal with an attested receipt: 1. Builds a canonical `SignalReceipt` (`asset`, `action`, `confidence`, `rationale`, `ts`). 2. Signs it via the injected `Attestor` (EIP-191 personal-sign over `digestJson(receipt)`). Badge: **✓ signature verified** — not TEE-quote. 3. Encodes the full `SignalRecord` to JSON and uploads to **0G Storage** (immutable, content-addressed). The `storageRef` is the returned root. 4. Returns the full record including `storageRef` for offline retrieval and independent verification. The `mcp-agent` adapter exposes the same attestation as two tools: `trade_signal` (returns an advisory signal + a signed, verified receipt) and `signal_verify` (recovers the signer from a receipt and checks it against the expected operator address). Neither tool places an order. ## Honesty note `trade-signal` is deliberately advisory-only. The lib test suite contains a **negative assertion** (`PUBLIC API SURFACE — advisory-only, execution-free invariant`) that fails if any export ever contains `execute`, `trade`, `swap`, `send`, or `transfer` — this guard exists for the lifetime of the kit. The attestation is a **signed receipt** (EIP-191 personal-sign via `@foundryprotocol/0gkit-attestation` `recoverSigner`) — not a TEE-quote / enclave attestation. The `Attestor` interface is injected so a real TEE-quote verifier can slot in without changing the lib. `OG_RPC_URL` defaults to the Galileo testnet endpoint. Mainnet usage and automated execution are intentionally out of scope for this kit. ============================================================================ # yield-intel kit Route: /kits/yield-intel URL: https://docs.0gkit.com/kits/yield-intel ============================================================================ # yield-intel > ⚠ **Demo — not financial advice; no automated execution.** > This kit provides AI-generated yield analysis for informational purposes only. > It does **not** execute any transactions on your behalf. You are solely > responsible for any financial decisions you make. This is a testnet demo > running on the Galileo network — mainnet and automated execution are > intentionally out of scope. ## What it does The `yield-intel` kit adds a read-only AI yield analysis dashboard to your 0G app. The public API has **no** `execute`, `trade`, `swap`, `send`, or `transfer` function — this is an analysis and audit tool, not a trading bot. On each analysis run the kit: 1. Accepts a list of `Position` objects (protocol, asset, amount, APY) from the caller — read-only input, no on-chain read is performed by the lib. 2. Calls `@foundryprotocol/0gkit-compute` via `compute.router()` (K7 — routed provider selection with a client-side fallback) with a structured analysis prompt asking for per-position scores (0–100) and one-sentence rationale. The model is instructed: no trading instructions, no profit guarantees, no risk-free claims. 3. Parses the JSON response and returns a ranked `AnalysisItem[]` sorted by descending score. On any parse error returns `[]` — never throws. 4. Optionally logs a decision record: the user describes their **intended** action in free text; `logDecision` signs a `DecisionReceipt` via EIP-191 personal-sign and uploads the full record to **0G Storage** as an immutable audit entry. **This does not execute the action.** The `DemoBanner` component is **non-removable** — it is rendered unconditionally at the top of the yield page with fixed copy: "Demo — not financial advice; no automated execution." ## Compatible bases `react-app` · `chat` · `tee-attested-api` ## Apply ```bash # scaffold-time npm create 0gkit-app -- --kits yield-intel # add to an existing project 0g add yield-intel ``` ## Environment variables | Variable | Example | Notes | | ------------------ | --------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `OG_NETWORK` | `galileo` | 0G network. **Defaults to `galileo` (testnet).** Mainnet and automated execution are out of scope. | | `OG_PRIVATE_KEY` | `0x...` | Operator key for signing decision receipts and 0G Storage transactions | | `OG_RPC_URL` | `https://evmrpc-testnet.0g.ai` | 0G chain RPC endpoint (Galileo testnet default) | | `OG_COMPUTE_MODEL` | `neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8` | Model for yield analysis inference (optional — uses provider default if omitted) | ## Quick start ```bash 0g add yield-intel ``` `analyze(positions, deps)` returns a ranked, read-only `AnalysisItem[]`. `logDecision(decision, deps)` records the user's **intended** action as a signed receipt on 0G Storage — it does **not** execute anything: ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; import { Storage } from "@foundryprotocol/0gkit-storage"; import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet"; import { analyze, type Position } from "./lib/yield.js"; import { logDecision, type Attestor } from "./lib/decisionLog.js"; const privateKey = process.env.OG_PRIVATE_KEY as `0x${string}`; const signer = await fromPrivateKey(privateKey); const compute = new Compute({ signer }); const positions: Position[] = [ { id: "p1", protocol: "Aave", asset: "USDC", amount: 5000, apy: 4.2 }, { id: "p2", protocol: "Uniswap V3", asset: "ETH/USDC", amount: 3000, apy: 11.8 }, ]; const items = await analyze(positions, { compute: { async infer({ prompt, model }) { const r = await compute.router({ messages: [{ role: "user" as const, content: prompt }], ...(model ? { model } : {}), }); return { output: r.output }; }, }, model: process.env.OG_COMPUTE_MODEL, }); // AnalysisItem[] ranked by descending score; [] on malformed output console.log(items); ``` > **Honesty caveat:** `yield-intel` is execution-free by design — the public API > has **no** `execute` / `trade` / `swap` / `send` / `transfer` (enforced by a > negative test). `logDecision` records an intention with a **signed receipt** > (not a TEE-quote); the user acts manually. Testnet-default; mainnet and > automated execution are intentionally out of scope. ## Tiers - **lib** — `lib/yield.ts` (portable `analyze`, `Position`, `AnalysisItem`, `AnalysisDeps` — the only public function; read-only); `lib/decisionLog.ts` (portable `logDecision`, `DecisionInput`, `DecisionRecord`, `DecisionReceipt`, `DecisionLogDeps` — logs a decision record to 0G Storage; does not execute any transaction). - **adapters** — `app/api/yield/route.ts` for `react-app` and `chat` bases (POST, runs `analyze` + optional `logDecision`); `src/routes/yield.ts` for `tee-attested-api` (Hono route). - **ui** — `components/DemoBanner.tsx` (**non-removable** disclaimer, leads the page), `components/YieldTable.tsx` (ranked analysis table, read-only), `components/DecisionLog.tsx` (log-an-intention form — explicitly says "This logs your decision — it does NOT execute it."), `app/yield/page.tsx`. ## Decision log `logDecision` (`lib/decisionLog.ts`) records the user's **intended** action with an attested receipt: 1. Builds a canonical `DecisionReceipt` (`positionId`, `action`, `rationale`, `score`, `ts`). 2. Signs it via the injected `Attestor` (EIP-191 personal-sign over `digestJson(receipt)`). Badge: **✓ signature verified** — not TEE-quote. 3. Encodes the full `DecisionRecord` to JSON and uploads to **0G Storage** (immutable, content-addressed). The `storageRef` is the returned root. 4. Returns the full record including `storageRef` for offline retrieval and independent verification. The `action` field is a free-text description of what the user plans to do **manually**. This system does not execute it. ## Honesty note `yield-intel` is deliberately execution-free. The lib test suite contains a **negative assertion** (`PUBLIC API SURFACE — execution-free invariant`) that fails if any export ever contains `execute`, `trade`, `swap`, `send`, or `transfer` — this guard exists for the lifetime of the kit. The attestation is a **signed receipt** (EIP-191 personal-sign via `@foundryprotocol/0gkit-attestation` `signMessage`/`recoverSigner`) — not a TEE-quote / enclave attestation. The `Attestor` interface is injected so a real TEE-quote verifier can slot in without changing the lib. `OG_NETWORK` defaults to `galileo` (Galileo testnet). Mainnet usage and automated execution are intentionally out of scope for this kit. ============================================================================ # MCP guide Route: /mcp URL: https://docs.0gkit.com/mcp ============================================================================ # MCP guide `@foundryprotocol/0gkit-mcp` is the neutral 0G [Model Context Protocol](https://modelcontextprotocol.io) server. It exposes every 0G primitive as an `og_*` tool so Claude Desktop, Cursor, Cline, or any MCP-capable agent runtime can drive 0G directly — no glue code. It runs over stdio; clients launch it with `npx`. Defaults to the **Galileo testnet** — no real funds needed. Foundry tools are a separate, opt-in plugin (`ZEROG_FOUNDRY=1`), absent by default. ## The tools Nine tools, mirroring the `0g` CLI 1:1. Every error is returned as JSON with `error`, `code`, and an actionable `hint` (and `isError: true`). | Tool | Required args | Optional args | Returns | | ------------------- | --------------------------- | ------------------------------------------------------------------ | -------------------------------------------- | | `og_storage_put` | `data` (UTF-8 text) | `network`, `rpc`, `private_key` | `{ root, txHash, explorerUrl, bytes }` | | `og_storage_get` | `root` | `network`, `rpc` | `{ root, bytes, text }` | | `og_storage_exists` | `root` | `network`, `rpc` | `{ root, exists }` | | `og_infer` | `message` | `provider`, `model`, `temperature`, `network`, `rpc`, `broker_key` | `{ output, provider, txHash, latencyMs }` | | `og_da_publish` | `payload` (UTF-8) | `network` | `{ digest, daRef, blobId, mode, latencyMs }` | | `og_da_verify` | `payload`, `digest` | `network` | `{ digest, verified }` | | `og_chain_faucet` | `address` | `network` | `{ address, network, txHash }` | | `og_chain_balance` | `address` | `network`, `rpc` | `{ address, network, wei, zg }` | | `og_attest_verify` | `signed_envelope`, `signer` | — | `{ verified, checks, signer, report }` | `network` is `aristotle` \| `galileo` \| `local`, defaulting to `ZEROG_NETWORK` or `galileo`. `signed_envelope` is a JSON string `{ envelope, digest, signature }`. `og_attest_verify` never errors for a bad signature — it returns `verified: false` with per-check detail. ## Environment | Var | Purpose | | ------------------- | --------------------------------------------- | | `ZEROG_NETWORK` | `galileo` (default), `aristotle`, or `local`. | | `ZEROG_RPC_URL` | Override the preset RPC. | | `ZEROG_PRIVATE_KEY` | Signer key (funds storage uploads). | | `ZEROG_BROKER_KEY` | Funded broker key for inference. | | `ZEROG_PROVIDER` | Default 0G inference provider address. | | `ZEROG_FOUNDRY` | `1` to enable the opt-in Foundry plugin. | ## Client configuration ### Claude Desktop Edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "0gkit": { "command": "npx", "args": ["-y", "@foundryprotocol/0gkit-mcp"], "env": { "ZEROG_NETWORK": "galileo", "ZEROG_PRIVATE_KEY": "0x...", "ZEROG_PROVIDER": "0x..." } } } } ``` ### Cursor In `~/.cursor/mcp.json` (or the project `.cursor/mcp.json`): ```json { "mcpServers": { "0gkit": { "command": "npx", "args": ["-y", "@foundryprotocol/0gkit-mcp"], "env": { "ZEROG_NETWORK": "galileo", "ZEROG_PRIVATE_KEY": "0x..." } } } } ``` ### Cline In the Cline MCP settings (`cline_mcp_settings.json`): ```json { "mcpServers": { "0gkit": { "command": "npx", "args": ["-y", "@foundryprotocol/0gkit-mcp"], "env": { "ZEROG_NETWORK": "galileo", "ZEROG_PRIVATE_KEY": "0x...", "ZEROG_BROKER_KEY": "0x...", "ZEROG_PROVIDER": "0x..." } } } } ``` After saving, restart the client. The nine `og_*` tools appear in the tool list. Ask the agent things like _"upload this README to 0G storage and give me the root"_ or _"verify this attestation against 0xCoordinator"_. ## Embedding programmatically ```ts import { create0gMcpServer, VERSION } from "@foundryprotocol/0gkit-mcp"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; const server = await create0gMcpServer(); // Pass { foundryPlugin: null } to force-disable the opt-in Foundry load, // or { deps } to inject test doubles. await server.connect(new StdioServerTransport()); console.error(`0gkit-mcp v${VERSION} ready`); ``` ## The Foundry plugin (opt-in) Set `ZEROG_FOUNDRY=1` and install `@foundryprotocol/mcp`. The neutral server then also lists its tools (`list_ingots`, `run_inference`, `get_ingot`, `get_lineage`, `get_attestation`), loaded via a computed specifier so the neutrality boundary (`pnpm boundary:check`) stays green. Without the opt-in, those tools do not appear at all. ## Related [CLI reference](/cli) (the same surface for humans/scripts) · the primitive packages: [storage](/packages/storage), [compute](/packages/compute), [da](/packages/da), [attestation](/packages/attestation), [chain](/packages/chain). Template: `npx degit rajkaria/0gkit/templates/mcp-agent`. ============================================================================ # Migrating from the official 0G SDKs Route: /migrate-from-official-sdks URL: https://docs.0gkit.com/migrate-from-official-sdks ============================================================================ # Migrating from the official 0G SDKs If you are already using `@0gfoundation/0g-storage-ts-sdk`, `@0gfoundation/0g-compute-ts-sdk`, or rolling your own DA HTTP client, **0gkit is a drop-in replacement** that gives you a uniform [`Receipt`](/concepts), a [typed error taxonomy](/errors), and an [escape hatch](/packages/storage#storageraw) to the underlying SDK whenever you need it. You can migrate one primitive at a time. The official SDKs continue to work inside 0gkit as the underlying transport — nothing is wrapped that prevents you from reaching the raw client. ## Why migrate? | Pain point | 0gkit fix | | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | | Each SDK has a different signer shape (`PrivateKey`, `ethers.Wallet`, broker key, …) | Single [`Signer`](/packages/wallet) interface across every primitive. | | Errors are bare `Error`s — no codes, no hints, no docs links | Every failure is a [`ZeroGError`](/errors) with `code` + `hint` + `helpUrl`. | | No cost estimate before broadcasting | [`primitive.estimate(input)`](/concepts) on storage / compute / DA. | | No deterministic preview | `{ dryRun: true }` returns the receipt envelope **without** any tx. | | Untyped contract calls | [Typed contracts](/packages/0gkit-contracts) with wagmi-style read/write. | | No browser-safe client | `Storage.computeRoot` and `Storage.exists` are pure — no SDK import. | | No CLI | `npx @foundryprotocol/0gkit-cli storage put` (or `infer`, `da publish`). | You **do not lose** anything: `storage.raw()` returns the loaded `@0gfoundation/0g-storage-ts-sdk` module so any feature 0gkit does not surface yet is still one call away. ## 1. Storage Replace `@0gfoundation/0g-storage-ts-sdk` with `@foundryprotocol/0gkit-storage`. The SDK is still installed as an optional peer — 0gkit loads it lazily. ### Install ```bash # before npm install @0gfoundation/0g-storage-ts-sdk ethers # after — 0gkit + the same peers (kept lazy) npm install @foundryprotocol/0gkit-storage@latest @foundryprotocol/0gkit-core@latest viem npm install @0gfoundation/0g-storage-ts-sdk ethers # optional peers ``` ### Upload ```ts // before — @0gfoundation/0g-storage-ts-sdk import { Indexer, ZgFile } from "@0gfoundation/0g-storage-ts-sdk"; import { Wallet, JsonRpcProvider } from "ethers"; const provider = new JsonRpcProvider("https://evmrpc-testnet.0g.ai"); const wallet = new Wallet(process.env.PRIVATE_KEY!, provider); const indexer = new Indexer("https://indexer-storage-testnet.0g.ai"); const file = ZgFile.fromBytes(new TextEncoder().encode("hello 0G")); const [tree, err] = await file.merkleTree(); if (err) throw err; const root = tree!.rootHash(); const [tx, uploadErr] = await indexer.upload( file, "https://evmrpc-testnet.0g.ai", wallet ); if (uploadErr) throw uploadErr; console.log("root:", root, "tx:", tx); ``` ```ts // after — @foundryprotocol/0gkit-storage import { Storage } from "@foundryprotocol/0gkit-storage"; import { fromEnv } from "@foundryprotocol/0gkit-wallet"; const storage = new Storage({ network: "galileo", signer: await fromEnv() }); const { root, tx } = await storage.upload(new TextEncoder().encode("hello 0G")); console.log("root:", root, "tx:", tx.txHash, `(${tx.latencyMs} ms)`); ``` What you gain: typed `Signer` from env/file/private key/KMS, uniform `Receipt` (`{ txHash, latencyMs }`), `ConfigError` with `hint` when the SDK is missing, and a `{ dryRun: true }` overload that returns the **predicted** root + the gas estimate without broadcasting. ### Download / probe / hash locally ```ts // after const bytes = await storage.download(root); // download by root const exists = await storage.exists(root); // poll for finality const localRoot = await storage.computeRoot(bytes); // no SDK, no network ``` Need the underlying SDK for something not exposed yet? ```ts const raw = await storage.raw(); // the loaded @0gfoundation module ``` See [`@foundryprotocol/0gkit-storage`](/packages/storage) for the full API and the [estimate + dry-run concept](/concepts) for cost preflight. ## 2. Compute (TEE inference) Replace `@0gfoundation/0g-compute-ts-sdk` (or `@0glabs/0g-serving-broker`) with `@foundryprotocol/0gkit-compute`. ### Install ```bash # before npm install @0gfoundation/0g-compute-ts-sdk ethers # after npm install @foundryprotocol/0gkit-compute@latest @foundryprotocol/0gkit-core@latest viem npm install @0gfoundation/0g-compute-ts-sdk ethers # optional peer ``` ### Inference ```ts // before — @0gfoundation/0g-compute-ts-sdk import { createZGComputeNetworkBroker } from "@0gfoundation/0g-compute-ts-sdk"; import { Wallet, JsonRpcProvider } from "ethers"; const provider = new JsonRpcProvider("https://evmrpc-testnet.0g.ai"); const wallet = new Wallet(process.env.BROKER_KEY!, provider); const broker = await createZGComputeNetworkBroker(wallet); const account = await broker.inference.getAccount(process.env.PROVIDER!); const { endpoint, model } = await broker.inference.getServiceMetadata( process.env.PROVIDER! ); const headers = await broker.inference.getRequestHeaders( process.env.PROVIDER!, "hello" ); const res = await fetch(`${endpoint}/v1/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json", ...headers }, body: JSON.stringify({ messages: [{ role: "user", content: "hello" }], model }), }); const json = await res.json(); const output = json.choices[0].message.content; ``` ```ts // after — @foundryprotocol/0gkit-compute import { Compute } from "@foundryprotocol/0gkit-compute"; import { fromEnv } from "@foundryprotocol/0gkit-wallet"; const compute = new Compute({ network: "galileo", signer: await fromEnv(), provider: process.env.ZEROG_PROVIDER!, }); const { output, receipt } = await compute.inference({ messages: [{ role: "user", content: "hello" }], }); console.log(output, receipt.txHash); ``` What you gain: no manual header dance, no manual broker setup, a typed `ZeroGError` taxonomy (`COMPUTE_NO_PROVIDER`, `COMPUTE_INFERENCE_FAILED`, `COMPUTE_BAD_ATTESTATION`) with one-line fixes, and an `{ dryRun: true }` overload that returns a token-count + cost estimate. See [`@foundryprotocol/0gkit-compute`](/packages/compute). ## 3. DA (Data Availability) Replace direct HTTP calls to the 0G DA encoder with `@foundryprotocol/0gkit-da`. ### Install ```bash # after — no upstream SDK install required; DA is HTTP-only npm install @foundryprotocol/0gkit-da@latest @foundryprotocol/0gkit-core@latest viem ``` ### Publish ```ts // before — hand-rolled HTTP const res = await fetch("https://da-encoder-testnet.0g.ai/v1/publish", { method: "POST", headers: { "Content-Type": "application/octet-stream" }, body: payload, }); if (!res.ok) throw new Error(`DA publish failed: ${res.status}`); const { digest } = await res.json(); ``` ```ts // after — @foundryprotocol/0gkit-da import { DA } from "@foundryprotocol/0gkit-da"; const da = new DA({ network: "galileo" }); const { digest, raw } = await da.publish(payload); ``` What you gain: a network preset (no manual URLs), `NetworkError` with a `hint` when the encoder is unreachable, an `estimate(payload)` for the expected fee, and a `{ dryRun: true }` overload that returns the digest **without** a network call. See [`@foundryprotocol/0gkit-da`](/packages/da). ## What stays the same - **The on-chain receipt.** `tx.txHash` is the same hash you would have seen before; the explorer link works in both worlds. - **The storage root.** A blob uploaded via `Storage.upload` has the same merkle root that `@0gfoundation/0g-storage-ts-sdk` would produce for the same bytes — that's what makes `computeRoot` deterministic. - **The TEE attestation.** Inference receipts carry the provider's attestation unchanged — `@foundryprotocol/0gkit-attestation` parses + verifies it. ## Hybrid usage You can mix 0gkit with the underlying SDK indefinitely. `storage.raw()`, `compute.raw()`, and `da.raw()` return the loaded module so any feature not yet surfaced through 0gkit's API is one call away. This is also how you adopt incrementally — keep the old code paths working while you migrate endpoints one at a time. ## Next steps - [Getting started](/getting-started) — install + a 60-second end-to-end example. - [`create-0gkit-app`](/getting-started/create-0gkit-app) — scaffold a runnable project with the right peers wired in. - [Cookbook](/cookbook) — end-to-end builds (chat, AI agent, NFT minter). - [CLI reference](/cli) — `npx @foundryprotocol/0gkit-cli` for the same operations from a script. ============================================================================ # Packages Route: /packages URL: https://docs.0gkit.com/packages ============================================================================ # Packages 0gkit is eleven independently published packages under the `@foundryprotocol/0gkit-*` scope (version `0.1.0`). Each page below documents the **real** exported API, derived from source, with copy-pasteable examples, the errors it throws and how to handle them, and exactly where each package is meant to run. ## The primitives (build on `@foundryprotocol/0gkit-core`) - [**@foundryprotocol/0gkit-core**](/packages/core) — network presets, the viem client factory, `Receipt`, the `ZeroGError` taxonomy, canonical JSON, and the `Signer` interface. - [**@foundryprotocol/0gkit-chain**](/packages/chain) — explorer URLs, native balance, `waitForReceipt`, the testnet faucet. - [**@foundryprotocol/0gkit-storage**](/packages/storage) — `Storage`: `upload` / `download` / `computeRoot` / `exists`. - [**@foundryprotocol/0gkit-compute**](/packages/compute) — `Compute`: provider discovery, broker inference, an OpenAI-compatible shim. - [**@foundryprotocol/0gkit-da**](/packages/da) — `DA`: deterministic digest, encoder publish, local integrity verify. - [**@foundryprotocol/0gkit-attestation**](/packages/attestation) — parse / sign / recover / verify / report a TEE attestation envelope (pure crypto). ## Wallet - [**@foundryprotocol/0gkit-wallet**](/packages/wallet) — Node wallet loaders: `fromPrivateKey`, `fromFile` (keystore-v3), `fromEnv` (auto-pick), `fromKMS` (AWS KMS), and SIWE (EIP-4361) helpers. Returns a `Signer` every primitive accepts. - [**@foundryprotocol/0gkit-wallet-react**](/packages/wallet-react) — React + wagmi v2 browser wallet: `ZeroGWalletProvider`, `useWallet`, `useConnect`, `useSwitchNetwork`, and `adaptWagmi`. ## The surfaces (build on the primitives) - [**@foundryprotocol/0gkit-cli**](/packages/cli) — the `0g` command line. - [**@foundryprotocol/0gkit-mcp**](/packages/mcp) — every primitive as an MCP `og_*` tool. - [**@foundryprotocol/0gkit-react**](/packages/react) — `useUpload` / `useDownload` / `useInference` / `useAttestation`. Dedicated guides expand the surfaces: [CLI reference](/cli), [MCP guide](/mcp), [React guide](/react). ============================================================================ # 0gkit-contracts Route: /packages/0gkit-contracts URL: https://docs.0gkit.com/packages/0gkit-contracts ============================================================================ # 0gkit-contracts Typed contract clients + Foundry codegen for 0G chain contracts. ## Install ```bash pnpm add @foundryprotocol/0gkit-contracts ``` ## Exports - `AttestationVerifierAbi` - `Erc20Abi` - `Erc721Abi` - `KNOWN_ADDRESSES` - `Multicall3Abi` - `RegistryAbi` - `buildClients` - `createTypedContract` - `fetchExplorerAbi` - `makeContractEstimate` - `standardContracts` - `standardContractsMeta` - `type BuildClientsOptions` - `type BuiltClients` - `type ContractEstimate` - `type ContractEstimateBreakdown` - `type EventOptions` - `type FetchAbiOptions` - `type Network` - `type StandardContractMeta` - `type TypedContract` - `type TypedContractOptions` - `type WriteOptions` - `weiToFee` ## Import a deployed contract `0g contracts import` pulls a **verified** ABI straight from the 0G ChainScan block explorer and runs it through the same codegen as `0g contracts generate`, producing a typed client at `./0gkit/contracts/.ts` (override with `--out`). ```bash # From a verified on-chain address (defaults to --network galileo): 0g contracts import 0xAbC… --name MyToken # → fetches the ABI from chainscan-galileo.0g.ai/open/api → typed client # From an off-chain Foundry artifact (no network needed): 0g contracts import --abi ./out/MyToken.sol/MyToken.json --name MyToken ``` Both paths converge on the existing `generate()` codegen — there is no separate emitter. Under the hood the address path calls `fetchExplorerAbi(address, network)` against the explorer's Etherscan-compatible `/open/api` endpoint. **Honesty:** if the contract is **not verified** on the explorer, `import` fails with a typed `ConfigError` telling you to pass the build artifact via `--abi .json` — it never fabricates an ABI. `--name` is required on the address path because `getabi` returns no contract name. Galileo is the default network; mainnet (`aristotle`) works too, and no behaviour is gated on mainnet being live. Read endpoints are keyless; set `OG_EXPLORER_API_KEY` only if you need to lift a rate limit. Kits like [`inft-studio`](/kits/inft-studio) reference `0g contracts import` as the way to pull a deployed iNFT contract into a typed client. ## Programmatic ABI fetch ```ts import { fetchExplorerAbi } from "@foundryprotocol/0gkit-contracts"; const abi = await fetchExplorerAbi("0xAbC…", "galileo"); ``` See the package README and source in [`packages/0gkit-contracts/`](https://github.com/rajkaria/0gkit/tree/main/packages/0gkit-contracts) for usage examples. ============================================================================ # 0gkit-devnet Route: /packages/0gkit-devnet URL: https://docs.0gkit.com/packages/0gkit-devnet ============================================================================ # 0gkit-devnet Local 0G devnet harness used by `0g dev`. ## Install ```bash pnpm add @foundryprotocol/0gkit-devnet ``` ## Exports - `AnvilNotInstalledError` - `DEFAULT_DEV_MNEMONIC` - `VERSION` - `clearState` - `deriveAccounts` - `detectAnvil` - `isRunning` - `readState` - `spawnAnvil` - `startComputeMock` - `startDaMock` - `startDevnet` - `startStorageMock` - `stopDevnet` - `type AnvilProcess` - `type AnvilSpawnOptions` - `type ComputeMockHandle` - `type ComputeMockMode` - `type DaMockHandle` - `type DevAccount` - `type DevnetChainService` - `type DevnetHandle` - `type DevnetService` - `type DevnetStartOptions` - `type DevnetState` - `type StorageMockHandle` - `writeState` See the package README and source in [`packages/0gkit-devnet/`](https://github.com/rajkaria/0gkit/tree/main/packages/0gkit-devnet) for usage examples. ============================================================================ # 0gkit-indexer Route: /packages/0gkit-indexer URL: https://docs.0gkit.com/packages/0gkit-indexer ============================================================================ # 0gkit-indexer Reorg-safe polling indexer for chain events with persistable cursors. ## Install ```bash pnpm add @foundryprotocol/0gkit-indexer ``` ## Exports - `CursorState` - `CursorStore` - `DecodedEvent` - `FromBlock` - `Indexer` - `IndexerOptions` - `IndexerStatus` - `MemoryCursorStore` - `SubscribeOptions` See the package README and source in [`packages/0gkit-indexer/`](https://github.com/rajkaria/0gkit/tree/main/packages/0gkit-indexer) for usage examples. ============================================================================ # @foundryprotocol/0gkit-kits Route: /packages/0gkit-kits URL: https://docs.0gkit.com/packages/0gkit-kits ============================================================================ # @foundryprotocol/0gkit-kits > The kits engine: a neutral, dependency-light core that resolves, composes, and > applies **kits** — drop-in, multi-framework feature overlays for 0G apps. ## What it does `0gkit-kits` is the engine behind `npm create 0gkit-app -- --kits ` and `0g add `. It reads each kit's `kit.json` manifest, validates it against `KitManifestSchema`, resolves the right files for a given **base** (the target template), follows composition (`composes`) and conflict (`conflicts`) rules, and writes the overlay into a destination project — merging `package.json` and `.env.example` idempotently. The package depends only on `zod` (manifest validation) and `giget` (overlay fetch) plus `node:*`. It never imports another `@foundryprotocol/0gkit-*` package or any Foundry app package, so the CLI can load it without paying a cold-start penalty and the neutrality boundary stays intact. ## Concepts - **Kit** — a versioned feature overlay stored as a git overlay under `templates/_kits//`, described by a `kit.json` manifest. - **Base** — the template a kit is applied onto (e.g. `react-app`, `chat`, `storage-app`, `mcp-agent`, `tee-attested-api`). `detectBase` infers it from a project's `package.json`. - **3-tier model** — every kit ships a portable `lib` tier (always applied), optional per-framework `adapters/` tiers, and an optional React-only `ui` tier. `resolveTiers` computes which files apply for a base. - **Composition** — a kit may `composes` other kits (auto-applied deps-first, deduped, cycle-safe) and declare `conflicts` that make a combination illegal. ## When to use it - Building the scaffolder / CLI surface that offers and installs kits. - Programmatically applying a kit into a project (`applyKit`). - Listing or inspecting the available kit registry (`listKits` / `getKit`). ## Install ```bash npm install @foundryprotocol/0gkit-kits ``` ## Quick start ```ts import { applyKit, listKits, detectBase, KitError } from "@foundryprotocol/0gkit-kits"; const base = detectBase(process.cwd()); // e.g. "react-app" const available = listKits({ base }); // kits that contribute files to this base try { const result = await applyKit({ kit: "agent-memory", dest: process.cwd(), base, }); console.log(result.applied, result.filesWritten, result.envAdded); } catch (err) { if (err instanceof KitError) { // err.code ∈ KIT_NOT_FOUND | KIT_CONFLICT | KIT_MISSING_REQUIRES | KIT_INCOMPATIBLE console.error(err.code, err.message); } } ``` ## API reference ### Manifest - **`KitManifestSchema`** — the zod schema every `kit.json` is parsed through. Materialises defaults for `tiers.lib`, `env`, `dependencies`, `devDependencies`, `requires`, `composes`, and `conflicts`, so the engine always sees a fully-populated manifest. - **`KitManifest`** — the inferred TypeScript type of a validated manifest. - **`KIT_DOMAINS`** — the closed list of capability domains a kit may declare (`verifiable-ai`, `agent-infra`, `markets`, `assets`, `defi`). - **`KitDomain`** — the union type derived from `KIT_DOMAINS`. ### Registry - **`KITS`** — the build-time registry: an array of every kit manifest, generated from `templates/_kits/*/kit.json` by `scripts/gen-registry.mjs`. - **`loadRegistry()`** — returns the embedded `KITS` array. - **`getKit(name, registry?)`** — looks up a single manifest by kebab-case name; returns `undefined` when not found. - **`listKits({ base?, registry? })`** — lists kits, optionally filtered to those compatible with `base` _and_ contributing at least one file for it. - **`resolveTiers(manifest, base)`** — returns the ordered list of overlay files that apply for `base`: the `lib` tier, then the matching `adapters[base]` tier, then the `ui` tier on a React base. ### Bases - **`REACT_BASES`** — the set of bases that receive the React `ui` tier (`react-app`, `chat`). - **`isReactBase(base)`** — whether a base is in `REACT_BASES`. - **`detectBase(dir)`** — infers the base of an existing project from its `package.json` dependencies (falls back to `"node"`). ### Apply + compose - **`applyKit(options)`** — resolves the full composition closure (deps-first, deduped, cycle-safe), checks `conflicts`, then writes every tier's files, merges `package.json`, and appends env vars. Returns an `ApplyResult`. - **`ApplyKitOptions`** — `{ kit, dest, base, pm?, dryRun?, deps? }`. - **`ApplyResult`** — `{ applied, filesWritten, envAdded, notes, token }`; the `token` is the stable success marker `[0gkit:kit-applied]`. - **`ApplyDeps`** — injectable seams (`fetchOverlay`, `registry`) for testing `applyKit` without network or the real registry. - **`KitError`** — thrown on an illegal apply; carries a `code` of type **`KitErrorCode`** (`KIT_NOT_FOUND` | `KIT_CONFLICT` | `KIT_MISSING_REQUIRES` | `KIT_INCOMPATIBLE`). ### Overlay fetch - **`fetchKitOverlay(name, dir, deps?)`** — downloads `templates/_kits/` from the canonical repo at `OGKIT_TEMPLATE_REF` (default `"main"`) via giget. - **`FetchKitOverlayDeps`** — injectable `download` seam for unit tests. ### Idempotent merges - **`mergePackageJson(base, incoming)`** — merges two partial `package.json` objects; existing base keys win on conflict. - **`PartialPackageJson`** — the shape `mergePackageJson` operates on. - **`appendEnv(current, vars)`** — appends `EnvVar` entries to an `.env.example` string, skipping any key already present (idempotent). - **`EnvVar`** — `{ key, example, note? }`. ## Exports - `KITS` - `KIT_DOMAINS` - `KitError` - `KitManifestSchema` - `REACT_BASES` - `appendEnv` - `applyKit` - `detectBase` - `fetchKitOverlay` - `getKit` - `isReactBase` - `listKits` - `loadRegistry` - `mergePackageJson` - `resolveTiers` - `type ApplyDeps` - `type ApplyKitOptions` - `type ApplyResult` - `type EnvVar` - `type FetchKitOverlayDeps` - `type KitDomain` - `type KitErrorCode` - `type KitManifest` - `type PartialPackageJson` ============================================================================ # @foundryprotocol/0gkit-observability Route: /packages/0gkit-observability URL: https://docs.0gkit.com/packages/0gkit-observability ============================================================================ # @foundryprotocol/0gkit-observability > OpenTelemetry instrumentation for 0gkit primitives. One call wires every > Storage / Compute / DA / Attestation operation as an OTel span with `0gkit.*` > semantic attributes. Bundle ≤ 20 KB gzipped. ## What it does `instrument0g()` patches the public methods of `Storage`, `Compute`, and `DA` at runtime so every call emits an OTel span. The span name is `0gkit..` (e.g. `0gkit.storage.upload`). Attributes include `0gkit.network`, `0gkit.size_bytes`, `0gkit.gas_native`, `0gkit.fee_native`, `0gkit.confirm_seconds`, `0gkit.root`, and so on. Failures record the exception, set the span status to ERROR, and attach `0gkit.error_code` (the SCREAMING_SNAKE code from the [error taxonomy](/errors)). ## When to use it - Production apps that need to know what's slow, what's expensive, and what's failing in their 0G calls. - Cost-attribution dashboards: `0gkit.gas_native` + `0gkit.fee_native` attributes feed straight into per-team / per-feature cost rollups. - Anywhere you already have an OTel collector (Honeycomb, Datadog, Tempo, Vercel OTel, Grafana Cloud). ## Install ```bash pnpm add @foundryprotocol/0gkit-observability @opentelemetry/api # Only if you don't already have an OTel SDK configured: pnpm add @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http ``` ## Quickstart — auto SDK setup ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; // At process boot, BEFORE any Storage / Compute / DA call: await instrument0g({ serviceName: "my-app", exporter: { kind: "otlp", endpoint: "https://api.honeycomb.io/v1/traces", headers: { "x-honeycomb-team": process.env.HONEYCOMB_API_KEY! }, }, }); // From here, every Storage / Compute / DA call emits a span. // Nothing else in your app needs to change. ``` ## Bring your own SDK (`mode: "attach"`) If you already configure `@opentelemetry/sdk-node` (or any other OTel SDK) in your app, pass `mode: "attach"` so `instrument0g` skips SDK setup and only patches primitives: ```ts import { instrument0g } from "@foundryprotocol/0gkit-observability"; await instrument0g({ mode: "attach" }); ``` ## Tests — patch fakes with explicit targets Tests can patch fake classes by passing `targets` explicitly. This bypasses the dynamic imports of the real primitives, so it's also synchronous in practice (the `await` resolves immediately): ```ts import { instrument0g, disinstrument0g } from "@foundryprotocol/0gkit-observability"; class FakeStorage { network = "galileo"; async upload() { return { root: "0xabc" }; } } await instrument0g({ mode: "attach", targets: { storage: { class: FakeStorage, methods: ["upload"] } }, }); // Run tests against an in-memory exporter. disinstrument0g(); // restores originals ``` ## Span attribute reference All attribute keys live in a single frozen `ATTR` constant. Every key uses the `0gkit.*` vendor namespace so collectors and cost calculators can filter on the prefix. | Constant | Key | Notes | | ---------------------- | ----------------------- | ----------------------------------------------------- | | `ATTR.NETWORK` | `0gkit.network` | `"galileo"` / `"aristotle"` / `"local"` | | `ATTR.OP` | `0gkit.op` | `"storage.upload"`, `"compute.inference"`, … | | `ATTR.SIZE_BYTES` | `0gkit.size_bytes` | Bytes uploaded / downloaded / published | | `ATTR.SEGMENTS` | `0gkit.segments` | Storage segments (256 KiB chunks) | | `ATTR.GAS_NATIVE` | `0gkit.gas_native` | Gas units (stringified bigint) | | `ATTR.FEE_NATIVE` | `0gkit.fee_native` | Native wei fee (stringified bigint) | | `ATTR.CONFIRM_SECONDS` | `0gkit.confirm_seconds` | Confirmation latency in seconds | | `ATTR.ROOT` | `0gkit.root` | Storage Merkle root | | `ATTR.TX_HASH` | `0gkit.tx_hash` | Transaction hash | | `ATTR.BLOCK_NUMBER` | `0gkit.block_number` | Block number | | `ATTR.MODEL` | `0gkit.model` | Compute model id | | `ATTR.INPUT_TOKENS` | `0gkit.input_tokens` | Input tokens consumed | | `ATTR.OUTPUT_TOKENS` | `0gkit.output_tokens` | Output tokens generated (or max for estimates) | | `ATTR.ERROR_CODE` | `0gkit.error_code` | SCREAMING_SNAKE code from the error taxonomy | | `ATTR.DRY_RUN` | `0gkit.dry_run` | `true` for `.estimate()` and `{ dryRun: true }` calls | Standard OTel `http.*` / `rpc.*` attributes are layered on top by user instrumentation — we don't duplicate them. ## What's wrapped today | Primitive | Methods | | ------------- | ----------------------------------------------- | | `Storage` | `upload`, `download`, `estimate`, `exists` | | `Compute` | `inference`, `estimate` | | `DA` | `publish`, `estimate` | | `Attestation` | (free functions — pass an explicit target only) | `Attestation` is currently a set of free functions, not a class. The default target set skips it; if you wire your own attestation client class, pass `targets.attestation` to instrument it. See [`docs/DECISIONS.md` D32](https://github.com/rajkaria/0gkit/blob/main/docs/DECISIONS.md) for the rationale. ## Local trace mirror (`OGKIT_TRACE_DIR`) Set `OGKIT_TRACE_DIR=` in the process that calls `instrument0g(...)` and every span will be mirrored as one JSON line to `/-.jsonl`, in addition to your configured OTel exporter. Off by default — pure opt-in, no network, no extra infra. ```bash export OGKIT_TRACE_DIR=.0gkit/traces node ./your-handler.js 0g traces list ``` The sink is fire-and-forget: a full disk or permissions error never crashes the handler. The CLI's [`0g traces`](/cli#0g-traces) subcommand reads these files back, and `0g traces inspect --json | 0g cost forecast --from-jaeger -` replays one local trace as a real per-op cost breakdown. ## Bundle size The public entry bundles to ≤ 20 KB gzipped. Asserted by `bundle-size.test.ts` on every CI run. `@opentelemetry/api` is externalised (peer dep); the optional SDK + exporter peers are lazy-imported and never reach the bundle unless the caller asks for them. ## Exporter recipes - [Honeycomb](/concepts/observability/exporters/honeycomb) - [Datadog](/concepts/observability/exporters/datadog) - [Vercel OTel](/concepts/observability/exporters/vercel) ## See also - [Concept: Observability](/concepts/observability) — `0gkit.*` attribute semantics + cost attribution playbook - [Error codes](/errors) — every `0gkit.error_code` value links to a fix page - [Source on GitHub](https://github.com/rajkaria/0gkit/tree/main/packages/0gkit-observability) ## Exports - `type AttrKey` - `type ExporterConfig` - `type InstrumentConfig` - `type InstrumentMode` - `type InstrumentTargets` - `type TraceFileEntry` - `type TraceFileSummary` - `type TraceRecord` - `appendSpanRecord` - `defaultTraceDir` - `isSinkEnabled` - `listTraceFiles` - `pathForTrace` - `readTraceFile` - `summarizeTrace` ============================================================================ # 0gkit-testing Route: /packages/0gkit-testing URL: https://docs.0gkit.com/packages/0gkit-testing ============================================================================ # 0gkit-testing Mocks, fixtures, vitest matchers, and `setupLocalDevnet` for testing on 0G. ## Install ```bash pnpm add @foundryprotocol/0gkit-testing ``` ## Exports - `ChatMessage` - `FIXTURE_ATTESTATION_PRIVATE_KEY` - `FIXTURE_ATTESTATION_SIGNER` - `FixtureEnvelopeOptions` - `FixtureSignedEnvelope` - `MockComputeClient` - `MockComputeEstimate` - `MockComputeEstimateBreakdown` - `MockComputeOptions` - `MockDAClient` - `MockDAOptions` - `MockInferenceArgs` - `MockInferenceResult` - `MockStorageClient` - `MockStorageEstimate` - `MockStorageEstimateBreakdown` - `MockStorageOptions` - `MockUploadResult` - `SUITE_NAMES` - `TEST_MNEMONIC` - `fixtureAttestation` - `fixtureReceipt` - `mockComputeClient` - `mockDAClient` - `mockStorageClient` - `runConformance` - `setupLocalDevnet` - `testWallet` - `type DevnetTestHandle` - `type SetupLocalDevnetOptions` - `type SuiteDeps` - `type SuiteName` - `type SuiteResult` - `type TestWalletOptions` See the package README and source in [`packages/0gkit-testing/`](https://github.com/rajkaria/0gkit/tree/main/packages/0gkit-testing) for usage examples. ## Conformance + `0g test` The `0g test` CLI command runs the built-in conformance suites against a 0G network. It lazy-loads `@foundryprotocol/0gkit-testing` at runtime (D39) so it does not affect CLI cold-start. ### Suites | Suite | What it checks | | --------- | ------------------------------------------------------------------------ | | `storage` | Upload a byte buffer then download it back; asserts round-trip identity. | | `compute` | Send a minimal inference request; asserts a non-empty `output` string. | | `da` | Publish bytes to the DA layer; verifies the returned digest matches. | | `wallet` | Sign a message with `testWallet`; recovers the signer address via viem. | ### Flags ```bash 0g test # run all four suites against galileo (default) 0g test --suite storage,da # run a subset of suites (comma-separated) 0g test --local # target the running `0g dev` stack (127.0.0.1:8545) 0g test --galileo # explicit galileo testnet (default when no flag) 0g test --kits # also run each applied kit's conformance module ``` | Flag | Type | Default | Description | | ---------------- | -------- | ------- | --------------------------------------------------------------- | | `--suite ` | `string` | all | Comma-separated subset: `storage`, `compute`, `da`, `wallet` | | `--local` | boolean | false | Use the local `0g dev` devnet at `http://127.0.0.1:8545` | | `--galileo` | boolean | true | Use the live galileo testnet (explicit; same as the default) | | `--kits` | boolean | false | Run each applied kit's `conformance.ts` in `.0gkit/kits//` | ### `runConformance` ```ts import { runConformance, type SuiteName, type SuiteDeps, type SuiteResult, } from "@foundryprotocol/0gkit-testing"; const results: SuiteResult[] = await runConformance({ suites: ["storage", "da"], // omit for all four suites deps: { makeStorage: () => storageClient, makeCompute: () => computeClient, makeDA: () => daClient, testWallet: () => wallet, }, }); // SuiteResult: { name: string; ok: boolean; detail: string } ``` `SUITE_NAMES` is the tuple `["storage", "compute", "da", "wallet"]` exported for use in downstream validators. ## `0g doctor --fix` repair table `0g doctor --fix` applies safe auto-fixes and prints commands for things it cannot fix automatically. It never installs packages or mutates network state (D85). | Check | What `--fix` does | | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `storage-indexer` unreachable | Calls `genEnvFromConfig` — regenerates `.env.example` from `define0GConfig().envExample()` and seeds `.env.local` if it does not exist. | | `da-encoder` unreachable | Same `.env` regeneration via `genEnvFromConfig`. | | Stale `@foundryprotocol/0gkit-*` pins | Calls `bumpStalePins` — prints `npm install @latest …` for each stale package. **Prints a command; does not run it.** | | `rpc` unreachable | Prints `0g dev # then re-run with --network local` via `rpcFallbackCmd`. | | `signer` missing or unfunded | Prints `export ZEROG_PRIVATE_KEY=` or a faucet `open` command. | The `.env` regeneration is idempotent: `.env.example` is always rewritten with the same bytes; `.env.local` is only created when it does not already exist (never clobbered). ============================================================================ # @foundryprotocol/0gkit-attestation Route: /packages/attestation URL: https://docs.0gkit.com/packages/attestation ============================================================================ # @foundryprotocol/0gkit-attestation > Neutral 0G TEE attestation: parse, sign (EIP-191), recover, verify, and > report a signed envelope. Pure crypto — no network. ## What it does Five pure functions over an `AttestationEnvelope` (the `foundry/eval-result/v1` shape). Validate/narrow an unknown value into an envelope, compute its on-chain digest, EIP-191 personal-sign it, recover the signer, verify digest **and** signer (never throwing), and produce a human-readable report. Signatures verify identically on-chain (ecrecover). ## When to use it - Producing a signed, tamper-evident attestation of a TEE eval result. - Verifying one you received: did the digest match, and did the expected coordinator actually sign it? - Logging / displaying an envelope (`reportEnvelope`). ## Where to use it **Anywhere** — pure `viem` crypto, no network, no Node-only dependency. Browser-safe (the playground runs `verifyEnvelope` live in-browser). ## Install ```bash npm install @foundryprotocol/0gkit-attestation @foundryprotocol/0gkit-core viem ``` ## API reference ### Types ```ts interface AttestationEnvelope { kind: "foundry/eval-result/v1"; forge: Address; scores: number[]; baseline: number; teeAttestation: Hex; daRef?: string; coordinator: Address; timestamp: number; // unix seconds } interface SignedEnvelope { envelope: AttestationEnvelope; digest: Hex; signature: Hex; } interface VerifyResult { ok: boolean; checks: { digest: boolean; signer: boolean }; signer: Address; // the recovered address } ``` ### `parseEnvelope(value)` ```ts function parseEnvelope(value: unknown): AttestationEnvelope; ``` Validates + narrows an unknown value. **Throws** `AttestationError` (with a hint) if any field is missing or the wrong type, or `kind` is not `foundry/eval-result/v1`. ### `digestEnvelope(envelope)` ```ts function digestEnvelope(envelope: AttestationEnvelope): Hex; ``` `keccak256` of the canonical envelope JSON — the on-chain anchor (this is `core`'s `digestJson` applied to the envelope). ### `signEnvelopeWithSigner(envelope, signer)` ```ts function signEnvelopeWithSigner( envelope: AttestationEnvelope, signer: Signer ): Promise; ``` EIP-191 personal-sign over the digest using a `Signer` from `@foundryprotocol/0gkit-wallet`. Use this when the signing key is in AWS KMS, a browser wallet, or another managed store — anywhere that a raw private key string is not available. **Throws** `AttestationError` if signing fails. ### `signEnvelope(envelope, privateKey)` — legacy (deprecated) ```ts function signEnvelope( envelope: AttestationEnvelope, privateKey: Hex | string ): Promise; ``` EIP-191 personal-sign over the digest (matches on-chain ecrecover). Leading `0x` on the key is optional. **Throws** `AttestationError` for an invalid private key. > **Deprecated.** Prefer `signEnvelopeWithSigner` — it accepts the `Signer` > interface and works with KMS / browser wallets. `signEnvelope` continues to > work but emits a deprecation warning and will be removed in v2. ### `recoverSigner(signed)` ```ts function recoverSigner( signed: Pick ): Promise
    ; ``` Recovers the signing address from `{ digest, signature }`. ### `verifyEnvelope(signed, expectedSigner)` ```ts function verifyEnvelope( signed: SignedEnvelope, expectedSigner: Address | string ): Promise; ``` Verifies digest integrity **and** signer identity. **Never throws** — a malformed signature yields `ok:false`. When `checks.digest` is `false`, `checks.signer` is reported `false` without attempting recovery (the signer check is skipped, not "wrong signer"). ### `reportEnvelope(signed)` ```ts function reportEnvelope(signed: SignedEnvelope): string; ``` A human-readable multi-line summary for CLIs / logs (kind, forge, coordinator, scores, timestamp, digest, truncated signature). ## Examples ### Minimal — sign then verify (recommended) ```ts import { signEnvelopeWithSigner, verifyEnvelope, type AttestationEnvelope, } from "@foundryprotocol/0gkit-attestation"; import { fromEnv } from "@foundryprotocol/0gkit-wallet"; const signer = await fromEnv(); // KMS_KEY_ID > KEY_FILE > PRIVATE_KEY const coordinator = signer.address; const envelope: AttestationEnvelope = { kind: "foundry/eval-result/v1", forge: "0x0000000000000000000000000000000000000001", scores: [0.91, 0.88], baseline: 0.8, teeAttestation: "0xdeadbeef", coordinator, timestamp: Math.floor(Date.now() / 1000), }; const signed = await signEnvelopeWithSigner(envelope, signer); const result = await verifyEnvelope(signed, coordinator); console.log(result.ok); // true console.log(result.checks); // { digest: true, signer: true } ``` ### Realistic — verify untrusted input, never throwing ```ts import { parseEnvelope, verifyEnvelope, reportEnvelope, type SignedEnvelope, } from "@foundryprotocol/0gkit-attestation"; import { AttestationError } from "@foundryprotocol/0gkit-core"; function checkUntrusted(raw: unknown, expectedSigner: string) { // 1. Structural validation — this CAN throw AttestationError: let signed: SignedEnvelope; try { const s = raw as SignedEnvelope; parseEnvelope(s.envelope); // throws if the envelope shape is wrong signed = s; } catch (err) { if (err instanceof AttestationError) { return { ok: false, reason: err.message, hint: err.hint }; } throw err; } // 2. Crypto verification — verifyEnvelope NEVER throws: return verifyEnvelope(signed, expectedSigner).then((r) => ({ ok: r.ok, checks: r.checks, // { digest, signer } recovered: r.signer, report: reportEnvelope(signed), })); } ``` ## Common errors | Symptom | Cause | Fix | | ---------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------ | | `AttestationError: Invalid attestation envelope: …` | `parseEnvelope` got a bad/incomplete shape. | Match the `foundry/eval-result/v1` shape exactly. | | `AttestationError: signEnvelope: invalid privateKey` | Key is not 64 hex chars. | Pass a 64-char hex key (with or without `0x`). | | `verify` returns `ok:false`, `checks.digest:false` | The envelope was altered after signing. | The payload is tampered — reject it. (Not an exception by design.) | | `verify` returns `ok:false`, `checks.signer:false` | A different key signed it. | Confirm the expected signer / coordinator address. | ## Related [core](/packages/core) (`digestJson`) · [wallet](/packages/wallet) (`signEnvelopeWithSigner`) · [da](/packages/da) (digest anchor) · CLI: [`0g attest`](/cli#0g-attest) · MCP: `og_attest_verify` ([MCP guide](/mcp)) · React: [`useAttestation`](/react). Template: `npx degit rajkaria/0gkit/templates/attestation-verify`. ## Exports - `type VerifyResult` ============================================================================ # @foundryprotocol/0gkit-chain Route: /packages/chain URL: https://docs.0gkit.com/packages/chain ============================================================================ # @foundryprotocol/0gkit-chain > Neutral 0G native-chain helpers: explorer URLs, native balance, > `waitForReceipt`, and the testnet faucet. ## What it does Four small functions over a `ZeroGClient` / `NetworkPreset`: build a block-explorer URL, read a native 0G balance, wait for a transaction to mine (returning a normalized `Receipt` with an explorer link attached), and request testnet funds. ## When to use it - When you need a human-readable explorer link for a tx or address. - When you want a native-token balance in wei. - When you submitted a tx (via raw viem or another tool) and need to block until it confirms with a uniform `Receipt`. - When onboarding a testnet wallet (the faucet helper). ## Where to use it Node scripts and servers (pure viem + fetch — no Node-only SDK). `faucet` and `balance` use `fetch`, which exists in modern browsers too, but you would not normally call a faucet from client code. ## Install ```bash npm install @foundryprotocol/0gkit-chain @foundryprotocol/0gkit-core viem ``` ## API reference ### `explorerUrl(network, target)` ```ts type ExplorerTarget = { tx: string } | { address: string }; function explorerUrl(network: NetworkPreset, target: ExplorerTarget): string; ``` Builds `/tx/` or `/address/` (trailing slashes on the base are stripped). **Throws** `ConfigError` if the network has no `explorer` (e.g. `local`). ### `attachExplorerUrl(receipt, network)` ```ts function attachExplorerUrl(receipt: Receipt, network: NetworkPreset): Receipt; ``` Returns a copy of `receipt` with `explorerUrl` filled from `receipt.txHash` when the network has an explorer. A no-op (returns the receipt unchanged) when there is no explorer or no `txHash`. **Never throws.** ### `balance(client, address)` ```ts function balance(client: ZeroGClient, address: `0x${string}` | string): Promise; ``` Native 0G balance in **wei**. **Throws** `NetworkError` if the RPC read fails (the hint suggests `0g doctor` / a working `rpcUrl`). ### `waitForReceipt(client, txHash)` ```ts function waitForReceipt( client: ZeroGClient, txHash: `0x${string}` | string ): Promise; ``` Waits for the tx to mine, returns a normalized `Receipt` (`{ txHash, blockNumber, latencyMs, explorerUrl? }`) with the explorer link already attached when the network has one. **Throws** `ChainError` if it does not confirm. ### `faucet(network, address)` ```ts function faucet( network: NetworkPreset, address: `0x${string}` | string ): Promise; ``` If the preset has a programmatic `faucetUrl`, POSTs `{ address }` to it and returns a `Receipt` (`txHash` may be `undefined` — that is fine, it is optional). If there is no programmatic endpoint it **throws** `ConfigError` whose hint points at the human faucet page (for `galileo`, `https://faucet.0g.ai`). A network/HTTP failure **throws** `NetworkError`. ## Examples ### Minimal — balance + explorer link ```ts import { createClient } from "@foundryprotocol/0gkit-core"; import { balance, explorerUrl } from "@foundryprotocol/0gkit-chain"; const client = createClient({ network: "aristotle" }); const wei = await balance(client, "0xYourAddress"); console.log(`${wei} wei`); console.log(explorerUrl(client.network, { address: "0xYourAddress" })); // https://chainscan.0g.ai/address/0xYourAddress ``` ### Realistic — wait for a tx, then faucet onboarding on testnet ```ts import { createClient, getNetwork, ZeroGError } from "@foundryprotocol/0gkit-core"; import { waitForReceipt, faucet } from "@foundryprotocol/0gkit-chain"; const client = createClient({ network: "galileo" }); // Block until a previously-broadcast tx confirms: const receipt = await waitForReceipt(client, "0xYourTxHash"); console.log(`mined in block ${receipt.blockNumber} (${receipt.latencyMs} ms)`); if (receipt.explorerUrl) console.log(receipt.explorerUrl); // Onboard a fresh testnet address. Galileo has no programmatic faucet, so // this throws a ConfigError whose hint is the web faucet URL — handle it: try { await faucet(getNetwork("galileo"), "0xNewAddress"); } catch (err) { if (err instanceof ZeroGError) { console.log(err.hint); // "Visit https://faucet.0g.ai and request funds…" } else { throw err; } } ``` ## Common errors | Symptom | Cause | Fix | | ------------------------------------------------ | -------------------------------------------------- | -------------------------------------------------------------- | | `ConfigError: Network '…' has no block explorer` | `explorerUrl`/explorer link on `local`. | Only call `explorerUrl` for networks with an explorer. | | `NetworkError: Failed to read balance…` | RPC unreachable. | Run `0g doctor`, or pass a working `rpcUrl` to `createClient`. | | `ChainError: Transaction … did not confirm` | Wrong hash, or it was sent to a different network. | Verify the hash and the network it was broadcast to. | | `ConfigError: No programmatic faucet endpoint…` | `faucet()` on `galileo` (web-only faucet). | Visit the URL in `err.hint` (`https://faucet.0g.ai`). | ## Related [core](/packages/core) · CLI: [`0g chain`](/cli#0g-chain). Template: `npx degit rajkaria/0gkit/templates/storage-app`. ## Exports - `signMessageWith` ============================================================================ # @foundryprotocol/0gkit-cli Route: /packages/cli URL: https://docs.0gkit.com/packages/cli ============================================================================ # @foundryprotocol/0gkit-cli > The neutral `0g` command line — `init`, `doctor`, `chain`, `storage`, > `infer`, `da`, `attest`. Language-agnostic; `--json` for scripting. ## What it does Ships a single binary, `0g`, that exposes every 0G primitive as a subcommand. It is a thin, faithful wrapper over the primitive packages — any stack (Python, Go, shell) can shell out and parse `--json`. Foundry is a separate, opt-in, hidden plugin (`0g foundry …`), never required. ## When to use it - You are not in TypeScript (Python, Go, Rust, shell) — the CLI is the universal surface. - CI / scripts — `--json` gives machine-readable output and meaningful exit codes. - Quick preflight (`0g doctor`) or scaffolding (`0g init`). ## Where to use it A terminal, a CI runner, or any process that can shell out. Node `>=20.10`. ## Install ```bash npm install -g @foundryprotocol/0gkit-cli # provides `0g` # or run ad-hoc with no install (note the full scope — `npx 0g` resolves # to an unrelated package on npm): npx @foundryprotocol/0gkit-cli ``` ## Quick taste ```bash npx @foundryprotocol/0gkit-cli init my-app && cd my-app && npm install npx @foundryprotocol/0gkit-cli doctor # preflight every 0G surface npx @foundryprotocol/0gkit-cli chain faucet 0xYourAddress # galileo → points at faucet.0g.ai ROOT=$(npx @foundryprotocol/0gkit-cli storage put model.bin --json | jq -r .root) npx @foundryprotocol/0gkit-cli storage exists "$ROOT" --json ``` The full command set, every flag, exit codes, and the env-var matrix are in the dedicated **[CLI reference](/cli)**. ## API surface This package is primarily a binary. It also exports `buildProgram(deps)`, `runCommand`, `ctxOf`, and `VERSION` for embedding/testing the commander program with injected dependencies (`ProgramDeps`) — see the source if you need to embed the CLI. ## Related Wraps every primitive: [core](/packages/core), [chain](/packages/chain), [storage](/packages/storage), [compute](/packages/compute), [da](/packages/da), [attestation](/packages/attestation). Full guide: **[CLI reference](/cli)**. ============================================================================ # @foundryprotocol/0gkit-compute Route: /packages/compute URL: https://docs.0gkit.com/packages/compute ============================================================================ # @foundryprotocol/0gkit-compute > Neutral 0G Compute: model-first routing, explicit-provider inference, provider > discovery, and an OpenAI-compatible shim. ## What it does Wraps the 0G compute broker SDK behind one class. **`router()`** picks a provider for you — the managed 0G Router endpoint when you supply a `routerApiKey`, otherwise honest client-side selection over the provider list with retry/fallback. **`direct()`/`inference()`** call a provider you name. Either returns the result plus an on-chain fee `Receipt`. A drop-in OpenAI-style shim is included so existing OpenAI code works unchanged. See the concept guide [router() vs direct()](/concepts/compute-router-vs-direct). ## When to use it - Running LLM inference paid for on 0G, against a registered provider. - Migrating OpenAI code to 0G with minimal changes (the `openai()` shim). - Listing available providers / models programmatically. ## Where to use it **Node only** for live inference — the broker SDK (`@0gfoundation/0g-compute-ts-sdk`, fallback `@0glabs/0g-serving-broker`) and `ethers` are dynamically imported. The broker import is intentionally non-analyzable + ignore-commented so it never breaks a bundler — but the SDK itself is Node-only, so run inference on a server / script / via the CLI. ## Install ```bash npm install @foundryprotocol/0gkit-compute @foundryprotocol/0gkit-core viem npm install @0gfoundation/0g-compute-ts-sdk ethers ``` ## API reference ### `class Compute` ```ts interface ChatMessage { role: "system" | "user" | "assistant"; content: string; } interface ComputeConfig { network?: "aristotle" | "galileo"; brokerRpc?: string; // default https://evmrpc.0g.ai signer?: Signer; // preferred — use fromEnv() / fromPrivateKey() / etc. brokerKey?: string; // legacy — deprecated, use signer instead provider?: string; // on-chain inference provider address (default prefer for router()) model?: string; // default model id routerApiKey?: string; // 0G Router API key (pc.0g.ai) — enables the managed Router routerUrl?: string; // override the Router base URL (default by network) fetch?: typeof fetch; // inject fetch (testing) loadBroker?: (name: string) => Promise; // inject the broker SDK loadEthers?: () => Promise; } interface InferenceResult { output: string; // the assistant message content receipt: Receipt; // { txHash?, latencyMs } — the on-chain fee receipt raw: unknown; // the raw provider JSON } new Compute(config: ComputeConfig); ``` Pass a `Signer` (from `@foundryprotocol/0gkit-wallet`) to `Compute` so key management is independent of the inference client. The legacy `brokerKey` string continues to work but emits a deprecation warning — see [Legacy (deprecated)](#legacy-deprecated) below. #### `compute.router(args)` — model-first (recommended) ```ts router(args: { model?: string; // required for the managed Router; optional client-side messages: ChatMessage[]; temperature?: number; prefer?: string; // pin a provider (defaults to config.provider) sort?: "price"; // managed-Router routing knob maxAttempts?: number; // client-side retry cap }): Promise; ``` Picks a provider for you. With `routerApiKey` set, calls the real 0G Router endpoint (`router-api.0g.ai/v1`, server-side selection + failover). Without one, selects client-side over `listProviders()` and retries the next candidate on failure (logged once). **Throws** a typed `NetworkError` when no provider is reachable, and `ConfigError` if the managed Router is used without a model. See [router() vs direct()](/concepts/compute-router-vs-direct). #### `compute.direct(args)` / `compute.inference(args)` ```ts inference(args: { provider?: string; // per-call provider (overrides config.provider) model?: string; messages: ChatMessage[]; temperature?: number; }): Promise; direct: typeof inference; // explicit-provider alias (no behaviour change) ``` The explicit-provider path: acknowledges the provider signer (non-fatal if already done), fetches service metadata, signs request headers, POSTs to `/v1/chat/completions`, and best-effort settles the fee (non-fatal). `direct()` is a thin alias; the optional per-call `{ provider }` is additive. **Throws** `ConfigError` for a missing `brokerKey`/`provider` or unloadable SDK/`ethers`; **throws** `NetworkError` for metadata/header/HTTP failures. #### `compute.listProviders()` ```ts listProviders(): Promise; ``` Lists registered providers via the broker. **Throws** `ConfigError` if `brokerKey` is missing, `NetworkError` if the listing fails. #### `compute.openai()` ```ts openai(): { chat: { completions: { create(params: { model?: string; messages: ChatMessage[]; temperature?: number; }): Promise<{ id: string; object: "chat.completion"; model: string; choices: Array<{ index: number; message: { role: "assistant"; content: string }; finish_reason: "stop"; }>; _0gReceipt: Receipt; // the 0G fee receipt, attached for you }>; }; }; }; ``` An OpenAI-shaped `chat.completions.create`. The 0G fee `Receipt` is attached as `_0gReceipt` on the response. #### `compute.raw()` ```ts raw(): Promise<{ inference: BrokerInference }>; // the underlying broker ``` The escape hatch — the underlying broker object. ## Examples ### Route by model — no provider address (recommended) ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; // Managed 0G Router: server-side selection + failover, single balance. const compute = new Compute({ network: "galileo", routerApiKey: process.env.ROUTER_API_KEY!, // from pc.0g.ai }); const { output } = await compute.router({ model: "llama-3.1-8b", messages: [{ role: "user", content: "Say hello to 0G in five words." }], }); console.log(output); ``` Drop `routerApiKey` and pass a wallet `signer`/`brokerKey` instead to get the same `router()` call backed by **client-side** selection over the on-chain provider list — no dashboard key required. ### With `signer` — recommended ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; import { fromEnv } from "@foundryprotocol/0gkit-wallet"; const signer = await fromEnv(); // KMS_KEY_ID > KEY_FILE > PRIVATE_KEY const compute = new Compute({ network: "galileo", signer, provider: process.env.ZEROG_PROVIDER!, }); const { output, receipt } = await compute.inference({ messages: [{ role: "user", content: "Say hello to 0G in five words." }], }); console.log(output); ``` ### Minimal — one completion ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet"; const signer = await fromPrivateKey(process.env.ZEROG_BROKER_KEY!); const compute = new Compute({ network: "galileo", signer, provider: process.env.ZEROG_PROVIDER!, // on-chain provider address }); const { output, receipt } = await compute.inference({ messages: [{ role: "user", content: "Say hello to 0G in five words." }], }); console.log(output); console.log(`fee tx ${receipt.txHash ?? "(none)"} — ${receipt.latencyMs} ms`); ``` ### Realistic — OpenAI drop-in with error handling ```ts import { Compute } from "@foundryprotocol/0gkit-compute"; import { ZeroGError } from "@foundryprotocol/0gkit-core"; import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet"; const signer = await fromPrivateKey(process.env.ZEROG_BROKER_KEY!); const compute = new Compute({ network: "galileo", signer, provider: process.env.ZEROG_PROVIDER!, model: "llama-3.3-70b-instruct", }); const oa = compute.openai(); // existing OpenAI code path try { const res = await oa.chat.completions.create({ messages: [ { role: "system", content: "You are terse." }, { role: "user", content: "What is 0G Data Availability?" }, ], temperature: 0.2, }); console.log(res.choices[0].message.content); console.log("0G fee receipt:", res._0gReceipt); // attached by 0gkit } catch (err) { if (err instanceof ZeroGError) { console.error(`[${err.code}] ${err.message}\n→ ${err.hint}`); } else { throw err; } } ``` ### Legacy (deprecated) Passing `brokerKey` directly is still supported but deprecated. It emits a `DeprecationWarning` and will be removed in v2. ```ts // ⚠ deprecated — works today, removed in v2 const compute = new Compute({ network: "galileo", brokerKey: process.env.ZEROG_BROKER_KEY, provider: process.env.ZEROG_PROVIDER!, }); ``` Migrate by wrapping with `fromPrivateKey`: ```ts import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet"; const signer = await fromPrivateKey(process.env.ZEROG_BROKER_KEY!); const compute = new Compute({ network: "galileo", signer, provider: "0x…" }); ``` ## Common errors | Symptom | Cause | Fix | | ----------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------ | | `ConfigError: Compute requires a signer or brokerKey` | No credentials in config. | Pass `{ signer }` (from `fromEnv()`) to the `Compute` constructor. | | `ConfigError: Compute requires a provider address` | No `provider` in config. | Pass `{ provider }`, or call `router()` to select one for you. | | `ConfigError: 0G compute SDK not found…` | Optional broker peer not installed. | `npm install @0gfoundation/0g-compute-ts-sdk ethers`. | | `NetworkError: Failed to fetch service metadata…` | Provider not registered / broker unfunded. | Verify the provider address and fund the broker ledger. | | `NetworkError: 0G compute provider returned HTTP …` | Provider rejected the request. | Verify the provider address and broker ledger balance. | ## Related [core](/packages/core) · [wallet](/packages/wallet) (loaders: `fromEnv`, `fromKMS`, …) · CLI: [`0g infer`](/cli#0g-infer) · MCP: `og_infer` ([MCP guide](/mcp)) · React: [`useInference`](/react). Template: `npx degit rajkaria/0gkit/templates/inference-app`. ## Exports - `DEFAULT_FEE_WEI_PER_TOKEN` - `DEFAULT_MAX_OUTPUT_TOKENS` - `__resetDeprecationWarning` - `countTokens` - `makeComputeEstimate` - `pickProviderAddress` - `selectProviders` - `toProviderInfo` - `type ChatMessage` - `type ComputeConfig` - `type ComputeEstimate` - `type ComputeEstimateBreakdown` - `type InferenceArgs` - `type InferenceResult` - `type ProviderInfo` - `type RouterArgs` - `type RouterResult` ============================================================================ # @foundryprotocol/0gkit-core Route: /packages/core URL: https://docs.0gkit.com/packages/core ============================================================================ # @foundryprotocol/0gkit-core > The neutral 0G foundation: network presets, a viem client factory, the > `Receipt` envelope, the `ZeroGError` taxonomy, and canonical JSON. ## What it does `core` is the shared base every other primitive builds on. It has zero runtime dependencies beyond `viem`. It owns four things: the network presets (`aristotle`, `galileo`, `local`), the `createClient` viem factory, the uniform `Receipt` shape, and the `ZeroGError` error taxonomy. It also provides the deterministic JSON digest used by DA and Attestation. ## When to use it - Always — it is a transitive dependency of every primitive. You use it directly when you need a viem client, want to read a network preset, or want to catch `ZeroGError`. - When you want raw viem access to 0G: `createClient(...).public` is a full viem `PublicClient`. ## Where to use it Node scripts, servers, edge runtimes, the browser — it is pure TypeScript + viem with no Node-only dependency. (The Node-only 0G SDK peers live in `storage`/`compute`, not here.) ## Install ```bash npm install @foundryprotocol/0gkit-core viem ``` ## API reference ### `networks` / `aristotle` / `galileo` / `local` ```ts const aristotle: NetworkPreset; // mainnet, chainId 16661 const galileo: NetworkPreset; // testnet, chainId 16602 (the default everywhere) const local: NetworkPreset; // local Anvil, chainId 31337 const networks: Record; ``` `NetworkName` is `"aristotle" | "galileo" | "local"`. See [Getting started → networks](/getting-started#supported-networks) for the resolved field values. ### `getNetwork(name)` ```ts function getNetwork(name: NetworkName): NetworkPreset; ``` Returns the preset for `name`. **Throws** `ConfigError` if `name` is not a known network (the hint lists the valid names). ### `type NetworkPreset` ```ts interface NetworkPreset { readonly name: NetworkName; readonly chainId?: number; // undefined ⇒ createClient throws ConfigError readonly rpcUrl?: string; // undefined ⇒ createClient throws ConfigError readonly explorer?: string; // undefined ⇒ explorerUrl() throws readonly faucetUrl?: string; // programmatic faucet endpoint (testnet) readonly faucetWebUrl?: string; // human faucet page, used in faucet()'s hint readonly testnet: boolean; } ``` ### `createClient(options)` ```ts interface CreateClientOptions { network: NetworkName; rpcUrl?: string; // overrides the preset RPC (required if preset has none) chainId?: number; // overrides the preset chain id privateKey?: string; // leading 0x optional; when set, a wallet is returned } interface ZeroGClient { network: NetworkPreset; public: PublicClient; // viem wallet?: WalletClient; // viem, only if privateKey was passed } function createClient(opts: CreateClientOptions): ZeroGClient; ``` **Throws** `ConfigError` when the preset has no `rpcUrl`/`chainId` and none was passed, or when `privateKey` is not a valid 32-byte hex string. ### `buildChain(preset, rpcUrl?, chainId?)` ```ts function buildChain(preset: NetworkPreset, rpcUrl?: string, chainId?: number): Chain; // a viem Chain ``` The lower-level helper `createClient` uses internally. **Throws** `ConfigError` if neither the preset nor the override supplies a `rpcUrl` / `chainId`. ### `type Receipt` The uniform result envelope. See [Concepts → Receipt](/concepts#the-receipt-envelope) for the full shape and why `txHash` is a `0x`-hex-or-`string` union. ### Errors: `ZeroGError`, `ConfigError`, `NetworkError`, `ChainError`, `AttestationError` ```ts type ZeroGErrorCode = "CONFIG" | "NETWORK" | "CHAIN" | "ATTESTATION"; class ZeroGError extends Error { readonly code: ZeroGErrorCode; readonly hint: string; constructor(code: ZeroGErrorCode, message: string, hint: string); } class ConfigError extends ZeroGError {} // code "CONFIG" class NetworkError extends ZeroGError {} // code "NETWORK" class ChainError extends ZeroGError {} // code "CHAIN" class AttestationError extends ZeroGError {} // code "ATTESTATION" ``` Full taxonomy and "thrown when" table: [Concepts → the ZeroGError taxonomy](/concepts#the-zerogerror-taxonomy). ### `canonicalJsonStringify(value)` / `digestJson(value)` ```ts function canonicalJsonStringify(value: unknown): string; // deterministic JSON function digestJson(value: unknown): Hex; // keccak256 of the canonical JSON ``` `digestJson` is the cross-package, on-chain digest anchor used by DA and Attestation. ### `define0GConfig({ server?, client?, edge? })` ```ts import { define0GConfig } from "@foundryprotocol/0gkit-core"; import { z } from "zod"; export const config = define0GConfig({ server: { ZEROG_NETWORK: z.enum(["galileo", "aristotle", "local"]).default("galileo"), PRIVATE_KEY: z.string().min(64).describe("Funds 0G transactions."), }, client: { NEXT_PUBLIC_ZEROG_NETWORK: z.string().default("galileo"), }, }); const env = config.server(); // throws ConfigError on missing / malformed env console.log(config.envExample()); // emits a stringified .env.example ``` Typed env reader with three slots — `server` (Node-only, full `process.env`), `client` (browser-safe, prefix-enforced `NEXT_PUBLIC_*`), `edge` (no process.env fallback). Validation failures throw `ConfigError`. Every 0gkit template ships a `0g.config.ts` that calls this. ### `detectLocalDevnet({ rpcUrl?, timeoutMs? })` ```ts import { detectLocalDevnet } from "@foundryprotocol/0gkit-core"; if (await detectLocalDevnet()) { // local Anvil/0G devnet is reachable — switch to network="local" } ``` Pure viem `getChainId` probe; default `rpcUrl="http://127.0.0.1:8545"`, default `timeoutMs=1000`. Returns `true` only when the chainId matches the `local` preset (31337). No shell-out to `0g doctor` (D72). ### `printFirstSuccess({ op, id, note? })` / `FIRST_SUCCESS_MARKER` ```ts import { printFirstSuccess, FIRST_SUCCESS_MARKER } from "@foundryprotocol/0gkit-core"; printFirstSuccess({ op: "storage.upload", id: result.root, note: "network=galileo", }); // → boxed banner that includes "[0gkit:first-success]" ``` Emits a unicode-boxed success banner. `FIRST_SUCCESS_MARKER` is the public contract token (`"[0gkit:first-success]"`) that log scrapers / CI gates can pin to. Callers gate idempotency themselves (the helper does not). ### `buildDefectReport(input)` / `suggestOwnership(code)` / `suggestSeverity(code)` ```ts import { buildDefectReport, ZeroGError } from "@foundryprotocol/0gkit-core"; try { await storage.upload(bytes); } catch (err) { if (err instanceof ZeroGError) { console.error( buildDefectReport({ error: err, product: "Foundry Protocol", env: { wallet: "MetaMask", chainId: 16602, network: "galileo" }, }) ); } } ``` Renders a ready-to-file QA defect report in the bilingual template used by the [0G ecosystem app-test program](https://github.com/lvxuan149/0g-apac-app-test). Every `ZeroGError` already carries a `code` + `helpUrl`, so the report auto-fills **ownership** (infra-class codes → `0G Infra`, integration/config codes → `Hackathon项目`), a **suggested severity**, the **environment** line, the **actual result**, and a **root-cause** hint — leaving only repro steps, expected result, and screenshot for the tester. `suggestOwnership` / `suggestSeverity` expose the routing + severity heuristics on their own. Framework-agnostic: call it from a browser dApp's error boundary or a CLI catch block. The `0g` CLI wires it behind `--defect-report`. ## Examples ### Minimal — resolve a network and read the chain id ```ts import { createClient, getNetwork } from "@foundryprotocol/0gkit-core"; const preset = getNetwork("aristotle"); console.log(preset.chainId); // 16661 const client = createClient({ network: "aristotle" }); console.log(client.public.chain?.id); // 16661 ``` ### Realistic — a signing client + actionable error handling ```ts import { createClient, ZeroGError, ConfigError } from "@foundryprotocol/0gkit-core"; try { const client = createClient({ network: "galileo", privateKey: process.env.ZEROG_PRIVATE_KEY, // adds client.wallet }); const address = client.wallet?.account?.address; const block = await client.public.getBlockNumber(); // raw viem escape hatch console.log(`signer ${address} — head block ${block}`); } catch (err) { if (err instanceof ConfigError) { // e.g. malformed private key, or galileo preset edge case console.error(`config problem: ${err.message}\n→ ${err.hint}`); } else if (err instanceof ZeroGError) { console.error(`[${err.code}] ${err.message}\n→ ${err.hint}`); } else { throw err; } } ``` ## Common errors | Symptom | Cause | Fix | | --------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------ | | `ConfigError: Unknown network '…'` | Bad `NetworkName` passed to `getNetwork`. | Use `aristotle`, `galileo`, or `local`. | | `ConfigError: Network '…' has no rpcUrl/chainId` | An unresolved preset (or a custom network). | Pass `{ rpcUrl, chainId }` to `createClient`. | | `ConfigError: Invalid privateKey: must be a 32-byte hex…` | `privateKey` is not 64 hex chars. | Pass a 64-char hex key (with or without `0x`), e.g. the output of `cast wallet new`. | ## Related Built on by every package. Start with [Getting started](/getting-started), then explore [chain](/packages/chain) / [storage](/packages/storage) / [compute](/packages/compute) / [da](/packages/da) / [attestation](/packages/attestation). Template: `npx degit rajkaria/0gkit/templates/storage-app`. ## Exports - `buildDefectReport` - `define0GConfig` - `detectLocalDevnet` - `ERROR_CODES` - `ERROR_HELP_BASE` - `errorNamespace` - `FIRST_SUCCESS_MARKER` - `formatEstimate` - `formatNative` - `helpUrlFor` - `isErrorCode` - `printFirstSuccess` - `suggestOwnership` - `suggestSeverity` - `type CreateClientOptions` - `type DefectOwnership` - `type DefectReportEnv` - `type DefectReportError` - `type DefectReportInput` - `type DefectSeverity` - `type DefineConfigOptions` - `type DefinedConfig` - `type DetectLocalDevnetOptions` - `type DryRunResult` - `type ErrorCode` - `type Estimate` - `type FirstSuccessArgs` - `type NetworkName` - `type SignTypedDataArgs` - `type SignableTx` - `type Signer` - `type ZeroGClient` ============================================================================ # @foundryprotocol/0gkit-da Route: /packages/da URL: https://docs.0gkit.com/packages/da ============================================================================ # @foundryprotocol/0gkit-da > Neutral 0G Data Availability: deterministic digest, encoder publish, and a > local integrity verify. ## What it does Computes a deterministic `keccak256` digest of a payload (objects via canonical JSON, strings and bytes raw), optionally publishes the bytes to a 0G DA encoder, and verifies a payload against an expected digest **locally** (no network). With no encoder configured it runs in `"local"` digest mode — useful offline and in tests. ## When to use it - Anchoring a payload's digest on-chain (the digest matches `@foundryprotocol/0gkit-core`'s `digestJson` and on-chain keccak). - Publishing a blob to the 0G DA layer for availability. - Tamper-checking a payload you received against a digest you trust — `verify()` is a pure local integrity check. ## Where to use it Anywhere — pure `viem` + `fetch`. `digest()`/`verify()` are fully local (browser-safe). `publish()` needs network access to the encoder (or runs in local mode with no encoder). ## Install ```bash npm install @foundryprotocol/0gkit-da @foundryprotocol/0gkit-core viem ``` ## API reference ### `class DA` ```ts interface DAConfig { network?: "aristotle" | "galileo"; // selects the preset encoder URL encoderUrl?: string; // overrides the preset encoder apiKey?: string; // Bearer token for the encoder signer?: Signer; // accepted for symmetry with other primitives (reserved) fetch?: typeof fetch; // inject fetch (testing) } interface DAPublishResult { digest: Hex; // 0x… keccak256 daRef?: string; // encoder ref (live mode) blobId?: string; // encoder blob id (live mode) mode: "live" | "local"; // "local" when no encoder is configured latencyMs: number; raw?: unknown; } new DA(config: DAConfig); ``` With no `network` and no `encoderUrl`, `publish` runs in `"local"` mode (digest only, no network). With a `network`, the encoder defaults to `https://da-encoder.0g.network` (aristotle) or `https://da-encoder-testnet.0g.ai` (galileo). > **`signer` is accepted but reserved.** The current DA encoder API uses bearer > `apiKey` authentication, not a wallet signature. The `signer` field is > accepted for constructor symmetry with other primitives and will be wired when > DA introduces on-chain fee settlement. Passing one today has no effect. #### `da.digest(payload)` ```ts digest(payload: unknown): Hex; ``` `keccak256` of the exact wire bytes: an object → keccak of the canonical-JSON encoding (identical to `core`'s `digestJson`); a string → keccak of raw UTF-8; a `Uint8Array` → keccak of the raw bytes. Pure, local, never throws. #### `da.publish(payload)` ```ts publish(payload: unknown): Promise; ``` Computes the digest, then (if an encoder is configured) POSTs the bytes to `/blob`. Returns `mode: "local"` with just the digest when no encoder is set. **Throws** `NetworkError` if the encoder request fails or returns non-2xx. #### `da.verify(payload, expectedDigest)` ```ts verify(payload: unknown, expectedDigest: string): boolean; ``` Recomputes the digest and compares (case-insensitive). A **local** integrity check — no network. **Throws** `ConfigError` if `expectedDigest` is not a 32-byte `0x` hex string. ## Examples ### Minimal — local digest + verify (no network, no keys) ```ts import { DA } from "@foundryprotocol/0gkit-da"; const da = new DA({}); // no network ⇒ local mode const payload = { model: "m1", score: 0.92 }; const { digest, mode } = await da.publish(payload); console.log(mode); // "local" console.log(da.verify(payload, digest)); // true console.log(da.verify({ model: "m1", score: 0.93 }, digest)); // false ``` ### Realistic — publish to the testnet encoder with error handling ```ts import { DA } from "@foundryprotocol/0gkit-da"; import { ZeroGError } from "@foundryprotocol/0gkit-core"; const da = new DA({ network: "galileo" }); // ⇒ testnet encoder, live mode try { const res = await da.publish({ event: "eval", at: Date.now() }); console.log(`mode ${res.mode} — digest ${res.digest}`); if (res.daRef) console.log(`daRef ${res.daRef} (${res.latencyMs} ms)`); // Later, anywhere, verify a copy of the payload against the digest: const ok = da.verify({ event: "eval", at: res.raw }, res.digest); console.log("integrity:", ok); } catch (err) { if (err instanceof ZeroGError) { // NetworkError ⇒ encoder unreachable; ConfigError ⇒ bad digest format console.error(`[${err.code}] ${err.message}\n→ ${err.hint}`); } else { throw err; } } ``` ## Common errors | Symptom | Cause | Fix | | ------------------------------------------------------ | ----------------------------- | -------------------------------------------------------------- | | `ConfigError: expectedDigest is not a 32-byte 0x hex…` | Bad value passed to `verify`. | Pass the value returned by `da.digest()` / `publish().digest`. | | `NetworkError: DA encoder request failed…` | Encoder unreachable. | Check the encoder URL, or omit it for local-digest mode. | | `NetworkError: DA encoder returned HTTP …` | Encoder rejected the request. | Verify the encoder URL / API key, or use local mode. | ## Related [core](/packages/core) (`digestJson` matches `da.digest`) · CLI: [`0g da`](/cli#0g-da) · MCP: `og_da_publish` / `og_da_verify` ([MCP guide](/mcp)). Template: `npx degit rajkaria/0gkit/templates/attestation-verify`. ## Exports - `DEFAULT_DA_RATE_WEI_PER_BYTE` - `estimateBytes` - `type DAConfig` - `type DAEstimate` - `type DAEstimateBreakdown` - `type DAPublishResult` ============================================================================ # @foundryprotocol/0gkit-jobs Route: /packages/jobs URL: https://docs.0gkit.com/packages/jobs ============================================================================ # @foundryprotocol/0gkit-jobs > Durable async job runner for long-running 0G workflows (inference, agents, > batched uploads, DA publishes). Three swappable backends; HMAC-signed > webhooks; graceful shutdown for serverless. ## What it does Wraps a worker loop, a typed job registry, and a pluggable persistence layer behind a single `JobRunner`. Enqueue with a zod-typed input, get an opaque id; the runner claims, runs, validates the output, persists, and (optionally) fires a signed webhook on each state change. ## When to use it - Long-running inference where a synchronous HTTP timeout isn't survivable. - Multi-step agents whose orchestration outlives a single request. - Webhook-driven workflows where the receiver must trust the sender. - Anywhere "at-least-once retry with bounded backoff" is the right delivery semantic. ## Install ```bash pnpm add @foundryprotocol/0gkit-jobs @foundryprotocol/0gkit-core zod # Redis backend only pnpm add ioredis ``` ## Define + run a job ```ts import { JobRunner, jobs } from "@foundryprotocol/0gkit-jobs"; import { MemoryBackend } from "@foundryprotocol/0gkit-jobs/backends/memory"; import { z } from "zod"; const InferenceJob = jobs.define({ name: "inference", input: z.object({ prompt: z.string(), model: z.string() }), output: z.object({ text: z.string() }), handler: async ({ input, signer, signal }) => { if (signal.aborted) throw new Error("shutting down"); // ...call Compute / Storage / DA here. return { text: "..." }; }, }); const runner = new JobRunner({ backend: new MemoryBackend(), signer, webhook: { url: process.env.WEBHOOK_URL!, secret: process.env.WEBHOOK_SECRET! }, }); runner.register(InferenceJob); await runner.start({ concurrency: 4 }); const id = await runner.enqueue(InferenceJob, { prompt: "hi", model: "..." }); const final = await runner.waitFor(id); ``` ## Backends | Backend | Install | When to use | | ------- | ------------------------------- | -------------------------------- | | memory | (built-in) | dev, tests, ephemeral workflows | | sqlite | (built-in via `better-sqlite3`) | single-node prod, no extra infra | | redis | optional peer `ioredis` | multi-node prod, fan-out | Backends share a single conformance contract — same behaviour across all three, the only differences are durability and concurrency reach. ## Webhook verification (Express) ```ts import { jobs } from "@foundryprotocol/0gkit-jobs"; app.post("/api/jobs/webhook", express.text({ type: "*/*" }), (req, res) => { const ok = jobs.verifyWebhook({ body: req.body, signature: req.header("x-0gkit-signature") ?? "", secret: process.env.JOBS_SECRET!, }); if (!ok) return res.status(401).send("bad signature"); // ... dedupe on (jobId, newState) }); ``` The signed payload is the **exact request body** (not a re-serialised JSON), so consumers must read raw bytes before verifying. ## Graceful shutdown (Vercel Fluid Compute) ```ts process.on("beforeExit", async () => { await runner.stop({ drain: true, timeoutMs: 25_000 }); }); ``` `stop({ drain: true })` lets in-flight handlers finish; `stop({ drain: false })` aborts them via the `AbortSignal` passed into the handler ctx. ## CLI ```bash 0g jobs status [--backend memory|sqlite] [--path ./.jobs.db] [--json] ``` Read-only inspector against a backend you point it at — useful in CI logs and post-mortems. The production runner lives in your app, not in the CLI. ## Error codes - [`JOBS_BACKEND_UNREACHABLE`](/errors/JOBS_BACKEND_UNREACHABLE) - [`JOBS_JOB_NOT_FOUND`](/errors/JOBS_JOB_NOT_FOUND) - [`JOBS_HANDLER_THREW`](/errors/JOBS_HANDLER_THREW) - [`JOBS_WEBHOOK_BAD_SIGNATURE`](/errors/JOBS_WEBHOOK_BAD_SIGNATURE) See also the [durable jobs concept page](/concepts/durable-jobs) for the delivery model and idempotency requirements. ## Exports - `ClaimOpts` - `JobBackend` - `JobDefinition` - `JobHandlerContext` - `JobMetadata` - `JobRecord` - `JobState` - `RunnerConfig` - `WebhookConfig` ============================================================================ # @foundryprotocol/0gkit-mcp Route: /packages/mcp URL: https://docs.0gkit.com/packages/mcp ============================================================================ # @foundryprotocol/0gkit-mcp > The neutral 0G MCP server — every 0G primitive as an `og_*` MCP tool for > Claude / Cursor / Cline / any agent runtime. ## What it does Runs a stdio MCP server exposing nine `og_*` tools (storage put/get/exists, infer, DA publish/verify, chain faucet/balance, attest verify) that mirror the `0g` CLI 1:1. Every error is a `ZeroGError` with an actionable `hint`. Foundry tools are a separate, opt-in plugin (`ZEROG_FOUNDRY=1`), absent by default. ## When to use it - You want an LLM agent to drive 0G directly — no glue code. - You use Claude Desktop, Cursor, Cline, or any MCP-capable runtime. ## Where to use it As a subprocess of an MCP client (stdio transport), or embedded programmatically via `create0gMcpServer()`. ## Install No install needed — MCP clients launch it with `npx`: ```bash npx -y @foundryprotocol/0gkit-mcp ``` ## API surface ```ts import { create0gMcpServer, VERSION } from "@foundryprotocol/0gkit-mcp"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; const server = await create0gMcpServer(); // attempts opt-in Foundry load await server.connect(new StdioServerTransport()); ``` Also exported: `TOOLS`, `makeHandlers`, `defaultDeps`, `loadFoundryPlugin`, and the `McpDeps` / `ZeroGMcpOptions` / `FoundryPlugin` / `ToolCallResult` types for embedding and testing. ### Plugin seam `create0gMcpServer({ plugins })` accepts extra `McpToolPlugin`s (`{ name, tools, call }`) that are merged into the tool list alongside the nine neutral `og_*` tools — the same seam the Foundry plugin rides. `collectToolPlugin(name, register, opts?)` adapts any `register(server: McpServerLike, opts?)` function (the high-level `server.tool(...)` shape a kit adapter uses) into an `McpToolPlugin`, so a project can expose its kit tools without the neutral server ever importing a kit. See [Concepts → MCP](/concepts/mcp). ### `0g mcp init` config writer `buildMcpConfig({ agent, scope, home, cwd, applied? })` returns the `{ path, json, mode, kits }` an editor config should be written to — `AGENTS` (`"cursor" | "claude" | "windsurf" | "codex"`, the `AgentName` union) drives the per-agent paths, `McpScope` is `"project" | "global"`, and the result carries a `McpServerEntry` under `mcpServers["0gkit"]`. `readAppliedKits(cwd, fs)` reads a project's `.0gkit/kits.json` (returning `AppliedKits | null`) so `mode` flips to `"local"` for a kitted `mcp-agent` project. These power the `0g mcp init ` CLI command — full walkthrough in [Concepts → MCP](/concepts/mcp). The complete tool list, JSON arg schemas, and copy-paste client config for Claude Desktop / Cursor / Cline are in the dedicated **[MCP guide](/mcp)**. ## Related Wraps every primitive. Full guide: **[MCP guide](/mcp)**. Template: `npx degit rajkaria/0gkit/templates/mcp-agent`. ## Exports - `type FoundryPlugin` - `type LoadFoundryOptions` - `type McpDeps` - `type ToolCallResult` - `type ZeroGMcpOptions` - `collectToolPlugin` - `type McpToolPlugin` - `type McpServerLike` - `buildMcpConfig` - `readAppliedKits` - `AGENTS` - `type AgentName` - `type McpScope` - `type AppliedKits` - `type McpServerEntry` - `type BuiltMcpConfig` - `type KitsReader` ============================================================================ # @foundryprotocol/0gkit-react Route: /packages/react URL: https://docs.0gkit.com/packages/react ============================================================================ # @foundryprotocol/0gkit-react > Neutral React hooks for 0G: `useUpload`, `useDownload`, `useInference`, > `useAttestation`. ## What it does Thin reactive wrappers over `storage`, `compute`, and `attestation`. Every hook returns the same shape — `{ data, error, loading, reset }` plus a named runner. The runner resolves with the value **and** rejects, so a component can read state reactively or `await` the call directly. `react` is a peer dependency. ## When to use it - A React UI that uploads to / downloads from 0G Storage, runs inference, or verifies an attestation. - You want loading/error state managed for you with a consistent shape. ## Where to use it A React 18/19 client component. Note the runtime boundary: `useUpload` / `useDownload` / `useInference` ultimately call the Node-only storage/compute SDKs, so they only work where those run (a server action, an Electron/Node host, or behind your own API). `useAttestation` is **pure crypto and works fully in the browser**. ## Install ```bash npm install @foundryprotocol/0gkit-react react # plus whichever primitive you use: npm install @foundryprotocol/0gkit-storage @0gfoundation/0g-storage-ts-sdk ethers # uploads npm install @foundryprotocol/0gkit-compute ethers # inference npm install @foundryprotocol/0gkit-attestation # verify ``` ## API surface ```ts function useUpload(config: StorageConfig): UseUploadResult; // .upload(bytes) function useDownload(config: StorageConfig): UseDownloadResult; // .download(root) function useInference(config: ComputeConfig): UseInferenceResult; // .infer(args) function useAttestation(): UseAttestationResult; // .verify(signed, expectedSigner) ``` Each result extends `AsyncState` (`{ data, error, loading }`) and adds the named runner plus `reset()`. `config` is read per-call through a ref, so you may recompute it each render without the hook going stale. Full signatures, return shapes, and a complete Next.js example are in the dedicated **[React guide](/react)**. ## Related Wraps [storage](/packages/storage), [compute](/packages/compute), [attestation](/packages/attestation). Full guide: **[React guide](/react)**. Template: `npx degit rajkaria/0gkit/templates/react-app`. ## Exports - `ZeroGErrorBoundary` - `ZeroGIndexerProvider` - `type AsyncAction` - `type AsyncState` - `type InferenceArgs` - `type UseAttestationResult` - `type UseDownloadResult` - `type UseEventOptions` - `type UseEventResult` - `type UseInferenceResult` - `type UseLogsOptions` - `type UseLogsResult` - `type UseUploadResult` - `type ZeroGErrorBoundaryProps` - `type ZeroGIndexerProviderProps` - `useEvent` - `useIndexer` - `useLogs` ============================================================================ # @foundryprotocol/0gkit-storage Route: /packages/storage URL: https://docs.0gkit.com/packages/storage ============================================================================ # @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 ```bash 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` ```ts 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; // inject the SDK (testing) } interface UploadOptions { signer?: unknown; // ready ethers Wallet/Signer — bypasses config signer/privateKey uploadOptions?: Record; // SDK UploadOption (fee, finality, tags) txOptions?: Record; // ethers-style TransactionOptions } interface UploadResult { root: string; // 0x-normalized merkle root tx: Receipt; // { txHash, latencyMs } txSeq?: number; // 0G Storage sequence number, when the SDK reports one 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)](#legacy-deprecated) below. #### `storage.upload(data, opts?)` ```ts upload(data: Uint8Array, opts?: UploadOptions): Promise; ``` Uploads the bytes; returns `{ root, tx, txSeq, raw }`. Funds the tx from the constructor `signer`/`privateKey` by default. **Throws** `ConfigError` if no key is available or the SDK / `ethers` cannot be loaded; **throws** `NetworkError` if the upload fails or returns an unrecognized shape. Pass `opts.signer` to upload with a **ready ethers `Wallet`/`Signer`** — it takes precedence over the constructor signer, so browser-, remote-, or KMS-backed signers that never expose a plaintext key can upload. `opts.uploadOptions` and `opts.txOptions` are forwarded straight to the underlying SDK. #### `storage.download(root)` ```ts download(root: string): Promise; ``` 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)` ```ts computeRoot(data: Uint8Array): Promise; ``` Computes the merkle root **locally** — no upload, no keys, no network. **Throws** `NetworkError` if the computation fails (e.g. empty input). #### `storage.exists(root)` ```ts exists(root: string): Promise; ``` `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()` ```ts raw(): Promise; // the loaded @0gfoundation/0g-storage-ts-sdk module ``` The escape hatch — the underlying SDK module. ## Examples ### With `signer` — recommended ```ts 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 ```ts 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 ```ts 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 v2. ```ts // ⚠ deprecated — works today, removed in v2 const storage = new Storage({ network: "galileo", privateKey: process.env.ZEROG_PRIVATE_KEY, }); ``` Migrate by wrapping with `fromPrivateKey`: ```ts import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet"; const signer = await fromPrivateKey(process.env.ZEROG_PRIVATE_KEY!); const storage = new Storage({ network: "galileo", signer }); ``` ## Common errors | Symptom | Cause | Fix | | ------------------------------------------------------------------ | ------------------------------------------- | ------------------------------------------------------------------ | | `ConfigError: Storage.upload requires a signer or privateKey` | `upload` with no credentials in config. | Pass `{ signer }` (from `fromEnv()`) to the `Storage` constructor. | | `ConfigError: @0gfoundation/0g-storage-ts-sdk could not be loaded` | Optional peer not installed. | `npm install @0gfoundation/0g-storage-ts-sdk ethers`. | | `ConfigError: ethers could not be loaded` | `ethers` 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](/packages/core) · [wallet](/packages/wallet) (loaders: `fromEnv`, `fromKMS`, …) · CLI: [`0g storage`](/cli#0g-storage) · MCP: `og_storage_*` ([MCP guide](/mcp)) · React: [`useUpload` / `useDownload`](/react). Template: `npx degit rajkaria/0gkit/templates/storage-app`. ## Exports - `SEGMENT_SIZE_BYTES` - `estimateBytes` - `makeStorageEstimate` - `type StorageConfig` - `type StorageEstimate` - `type StorageEstimateBreakdown` - `type StorageSdk` - `type UploadOptions` - `type UploadResult` ============================================================================ # @foundryprotocol/0gkit-wallet Route: /packages/wallet URL: https://docs.0gkit.com/packages/wallet ============================================================================ # @foundryprotocol/0gkit-wallet > Node wallet loaders for 0G: `fromPrivateKey`, `fromFile`, `fromEnv`, > `fromKMS`, and SIWE (EIP-4361) helpers. ## What it does Provides a uniform `Signer` interface that every 0G primitive accepts. Four loaders create a `Signer` from different key sources: a raw hex private key, an eth-keystore-v3 file, environment-variable auto-detection, or an AWS KMS key. A `siwe` namespace ships EIP-4361 message building, parsing, and verification for server-side sign-in flows. ## When to use it - Server scripts or API routes that need to sign storage uploads, inference requests, or attestations. - Production deployments where the private key is in AWS KMS (`fromKMS`). - Sign-in with Ethereum flows (`siwe.*`). ## Where to use it **Node only.** `fromFile` uses `fs`, `fromKMS` uses `@aws-sdk/client-kms`, and `fromEnv` delegates to whichever loader applies. None of these run in the browser — use [`@foundryprotocol/0gkit-wallet-react`](/packages/wallet-react) for browser wallet integration. ## Install ```bash npm install @foundryprotocol/0gkit-wallet @foundryprotocol/0gkit-core # KMS support — install the AWS SDK only if you use fromKMS / fromEnv with KMS_KEY_ID: npm install @aws-sdk/client-kms ``` ## API reference ### Loaders ```ts function fromPrivateKey(privateKey: string): Promise; function fromFile(path: string, opts: { password: string }): Promise; function fromEnv(opts?: { env?: NodeJS.ProcessEnv }): Promise; function fromKMS(opts: { keyId: string; region?: string }): Promise; ``` All loaders return a `Signer`. All throw `ConfigError` (from `@foundryprotocol/0gkit-core`) when credentials are invalid or unavailable. ### `type Signer` Re-exported from `@foundryprotocol/0gkit-core`. Every 0G primitive accepts a `Signer` in place of the legacy `privateKey` string. ```ts interface Signer { readonly address: `0x${string}`; readonly source: "local" | "file" | "env" | "kms" | "wagmi" | string; signMessage( input: string | Uint8Array | { raw: string | Uint8Array } ): Promise<`0x${string}`>; signTypedData(args: SignTypedDataArgs): Promise<`0x${string}`>; sendTransaction(tx: SignableTx): Promise<`0x${string}`>; } ``` ### `siwe` namespace ```ts import * as siwe from "@foundryprotocol/0gkit-wallet"; // or: import { siwe } from "@foundryprotocol/0gkit-wallet"; siwe.generateNonce(): string; siwe.buildMessage(args: BuildMessageArgs): string; siwe.verify(args: VerifyArgs): Promise; // VerifyResult = { ok: true; address: `0x${string}`; fields: ParsedSiwe } // | { ok: false; reason: string } ``` `buildMessage` follows the EIP-4361 grammar exactly. `verify` recovers the signer from the signature and checks the nonce + expiration — it **never throws** for bad signatures, returning `{ ok: false, reason }` instead. ### Type re-exports `Signer`, `SignTypedDataArgs`, `SignableTx`, `FromFileOptions`, `FromKMSOptions`, `FromEnvOptions` are all re-exported from this package. ## Examples ### `fromPrivateKey` ```ts import { fromPrivateKey } from "@foundryprotocol/0gkit-wallet"; import { Storage } from "@foundryprotocol/0gkit-storage"; const signer = await fromPrivateKey(process.env.PRIVATE_KEY!); const storage = new Storage({ network: "galileo", signer }); const { root, tx } = await storage.upload(new TextEncoder().encode("hello")); console.log(`root ${root} — tx ${tx.txHash}`); ``` ### `fromFile` (eth-keystore-v3) ```ts import { fromFile } from "@foundryprotocol/0gkit-wallet"; const signer = await fromFile("./secrets/keystore.json", { password: process.env.KEY_PASSWORD!, }); console.log(signer.address); // 0x… ``` ### `fromEnv` (environment auto-pick) `fromEnv` inspects env in this order: 1. `KMS_KEY_ID` — delegates to `fromKMS` (uses `AWS_REGION` / `KMS_REGION`) 2. `KEY_FILE` + `KEY_PASSWORD` — delegates to `fromFile` 3. `PRIVATE_KEY` — delegates to `fromPrivateKey` ```ts import { fromEnv } from "@foundryprotocol/0gkit-wallet"; // In production, set KMS_KEY_ID. In dev, set PRIVATE_KEY. // The caller doesn't need to know which is active. const signer = await fromEnv(); console.log(signer.address, signer.source); // "kms" | "env" | … ``` ### `fromKMS` (AWS KMS secp256k1) ```ts import { fromKMS } from "@foundryprotocol/0gkit-wallet"; const signer = await fromKMS({ keyId: "arn:aws:kms:us-east-1:123456789012:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", region: "us-east-1", }); console.log(signer.address); // derived from the KMS public key // signer.privateKey — does NOT exist; key material never leaves KMS ``` ### SIWE — server-side sign-in ```ts import { siwe } from "@foundryprotocol/0gkit-wallet"; // 1. Issue a nonce challenge (server): const nonce = siwe.generateNonce(); // store in session // 2. Build the message the client will sign: const message = siwe.buildMessage({ domain: "example.com", address: "0xUserWalletAddress", uri: "https://example.com/login", nonce, chainId: 16602, // 0G Galileo statement: "Sign in to Example", expirationTime: new Date(Date.now() + 5 * 60_000), }); // 3. Client signs with their wallet, POSTs { message, signature } back. // 4. Server verifies: const result = await siwe.verify({ message, signature: "0xClientSignatureHex", expectedNonce: nonce, // prevents replay }); if (result.ok) { console.log("authenticated as", result.address); console.log("chain", result.fields.chainId); } else { console.error("SIWE failed:", result.reason); } ``` ## Gotchas - **KMS key spec must be `ECC_SECG_P256K1`.** The Ethereum curve is secp256k1, not the NIST P-256 (`ECC_NIST_P256`) used by TLS. Creating a KMS key with the wrong spec results in an `InvalidSignatureException` at sign time. - **KMS signers have no `signer.privateKey`.** The key material never leaves KMS — that is the point. Any code that expects `signer.privateKey` needs to be updated to use `signer.signMessage` / `signer.signTypedData` instead. - **`fromEnv` precedence is `KMS_KEY_ID > KEY_FILE+KEY_PASSWORD > PRIVATE_KEY`.** If `KMS_KEY_ID` is set _and_ the KMS call fails, `fromEnv` throws immediately rather than falling through to the next loader. ## Related [core](/packages/core) (the `Signer` interface lives here) · [wallet-react](/packages/wallet-react) (browser wallet for React/Next.js) · [storage](/packages/storage) · [compute](/packages/compute) · [attestation](/packages/attestation) ## Exports - `parse` - `type FromEnvOptions` - `type FromFileOptions` - `type FromKMSOptions` ============================================================================ # @foundryprotocol/0gkit-wallet-react Route: /packages/wallet-react URL: https://docs.0gkit.com/packages/wallet-react ============================================================================ # @foundryprotocol/0gkit-wallet-react > React + wagmi v2 wallet integration for 0G: `ZeroGWalletProvider`, > `useWallet`, `useConnect`, `useSwitchNetwork`, and `adaptWagmi`. ## What it does Wraps [wagmi v2](https://wagmi.sh) (injected + WalletConnect connectors) behind a 0G-aware provider and three hooks. Every hook returns a `Signer` — the same interface the 0G primitives accept — so you can pass `useWallet().signer` directly to `new Storage({ signer })` without any adapter code. ## When to use it - A Next.js / React app where the user connects their browser wallet (MetaMask, Rainbow, WalletConnect, etc.) and signs storage uploads or attestations. - Anywhere you need a reactive `isConnected` / `address` alongside a 0G-typed `Signer`. ## Where to use it **Client components only.** All exports carry `"use client"` at the top of their module — they use React hooks internally and cannot be imported in React Server Components. The provider must live in a client component somewhere above the tree; hooks only work below it. ## Install ```bash npm install @foundryprotocol/0gkit-wallet-react @foundryprotocol/0gkit-core # Peer dependencies — must be installed separately: npm install react viem wagmi @tanstack/react-query ``` ## API reference ### `ZeroGWalletProvider` ```ts interface ZeroGWalletConfig { network: "galileo" | "aristotle" | "local"; connectors?: Array<"injected" | "walletConnect">; // default ["injected"] walletConnectProjectId?: string; // required when "walletConnect" is listed } function ZeroGWalletProvider(props: { config: ZeroGWalletConfig; children: ReactNode; queryClient?: QueryClient; // optional — a fresh one is created if omitted }): JSX.Element; ``` Sets up a `WagmiProvider` + `QueryClientProvider` for the chosen 0G network. The chain RPC is preset per network (`galileo` → `https://evmrpc-testnet.0g.ai`, `aristotle` → `https://evmrpc.0g.ai`, `local` → `http://127.0.0.1:8545`). ### `useWallet()` ```ts interface UseWalletResult { address: `0x${string}` | undefined; isConnected: boolean; signer: Signer | null; // null when no wallet is connected disconnect: () => void; } function useWallet(): UseWalletResult; ``` The primary hook. Returns the connected address, connection state, a `Signer` wrapping the wagmi account (ready to pass to 0G primitives), and a `disconnect` callback. ### `useConnect()` ```ts interface UseConnectResult { connect: (connectorId?: string) => Promise; connectors: readonly Connector[]; isPending: boolean; error: Error | null; reset: () => void; } function useConnect(): UseConnectResult; ``` Calls `connect(connectorId)` to trigger the connection flow for a specific connector (`"injected"`, `"walletConnect"`, or a wagmi connector `id`). With no argument, picks the first registered connector. ### `useSwitchNetwork()` ```ts interface UseSwitchNetworkResult { switchNetwork: (chainId: number) => Promise; isPending: boolean; error: Error | null; } function useSwitchNetwork(): UseSwitchNetworkResult; ``` Asks the wallet to switch to `chainId`. Useful when the user is on the wrong network (e.g. they are on mainnet Ethereum but the app is on Galileo). ### `adaptWagmi(adapter)` — low-level escape hatch ```ts interface WagmiAccountAdapter { address: `0x${string}` | undefined; signMessageAsync: (args: { message: string }) => Promise<`0x${string}`>; signTypedDataAsync: (args: SignTypedDataArgs) => Promise<`0x${string}`>; sendTransactionAsync: (tx: SignableTx) => Promise<`0x${string}`>; } function adaptWagmi(adapter: WagmiAccountAdapter): Signer | null; ``` Converts raw wagmi hook results into a `Signer`. `useWallet` uses this internally. Call it directly only when you need to compose your own wagmi setup and want to reuse the `Signer` contract. ## Examples ### Root layout — add the provider ```tsx // app/layout.tsx (or pages/_app.tsx) "use client"; import { ZeroGWalletProvider } from "@foundryprotocol/0gkit-wallet-react"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` > **Next.js App Router:** `RootLayout` is a Server Component by default. If you > add `"use client"` to it you lose RSC streaming for the whole tree. Instead, > extract the provider into its own file: ```tsx // app/providers.tsx "use client"; import { ZeroGWalletProvider } from "@foundryprotocol/0gkit-wallet-react"; export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ```tsx // app/layout.tsx — stays a Server Component import { Providers } from "./providers"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### `useWallet` — read address and sign ```tsx "use client"; import { useWallet } from "@foundryprotocol/0gkit-wallet-react"; import { Storage } from "@foundryprotocol/0gkit-storage"; export function UploadButton({ data }: { data: Uint8Array }) { const { address, isConnected, signer } = useWallet(); async function handleUpload() { if (!signer) return; // signer is a Signer — pass it directly to any 0G primitive const storage = new Storage({ network: "galileo", signer }); const { root } = await storage.upload(data); console.log("uploaded:", root); } if (!isConnected) return

    Connect your wallet first.

    ; return ; } ``` ### `useConnect` — connect button ```tsx "use client"; import { useConnect } from "@foundryprotocol/0gkit-wallet-react"; export function ConnectButton() { const { connect, connectors, isPending, error } = useConnect(); return ( <> {connectors.map((c) => ( ))} {error &&

    {error.message}

    } ); } ``` ### `useSwitchNetwork` — wrong-network banner ```tsx "use client"; import { useSwitchNetwork } from "@foundryprotocol/0gkit-wallet-react"; import { useChainId } from "wagmi"; const GALILEO_CHAIN_ID = 16602; export function WrongNetworkBanner() { const chainId = useChainId(); const { switchNetwork, isPending } = useSwitchNetwork(); if (chainId === GALILEO_CHAIN_ID) return null; return (
    Wrong network — please switch to 0G Galileo.
    ); } ``` ## RSC note The `"use client"` boundary is baked into every module of this package. Importing any export from `@foundryprotocol/0gkit-wallet-react` in a React Server Component will cause a build error. Keep all wallet UI in client components, and fetch data server-side (e.g. `new Storage({ signer: await fromEnv() })`) from server actions or API routes using [`@foundryprotocol/0gkit-wallet`](/packages/wallet). ## Related [wallet](/packages/wallet) (Node loaders — use in server actions / scripts) · [storage](/packages/storage) · [compute](/packages/compute) · [core](/packages/core) (the `Signer` interface) ## Exports - `type UseConnectResult` - `type UseSwitchNetworkResult` - `type UseWalletResult` - `type WagmiAccountAdapter` - `type ZeroGConnectorId` - `type ZeroGNetwork` - `type ZeroGWalletConfig` ============================================================================ # React guide Route: /react URL: https://docs.0gkit.com/react ============================================================================ # React guide `@foundryprotocol/0gkit-react` is four thin reactive hooks over the 0G primitives. `react` is a peer dependency. ```bash npm install @foundryprotocol/0gkit-react react ``` ## The shared shape Every hook returns the same `AsyncState` envelope plus a named runner and a `reset`: ```ts interface AsyncState { data: T | undefined; error: Error | undefined; loading: boolean; } // each hook adds: (...args) => Promise and reset(): void ``` `data` and `error` are mutually exclusive — a new run clears both until it settles. The runner **resolves with the value and also rejects**, so you can either read `data`/`error` reactively or `await` the call directly and `try/catch`. `config` is read per-call through a ref, so you may recompute it each render (e.g. on a network/key change) without the hook going stale. ## The runtime boundary (read this) `useUpload` / `useDownload` / `useInference` ultimately call the **Node-only** 0G storage/compute SDKs. They will surface a clean `ConfigError` in a pure browser bundle. Run them where Node runs — a server action, a Node/Electron host, or behind your own API route. **`useAttestation` is pure crypto and works fully in the browser** (the 0gkit playground verifies attestations live, client-side). ## Hooks ### `useUpload(config)` ```ts function useUpload(config: StorageConfig): { data: UploadResult | undefined; // { root, tx, raw } error: Error | undefined; loading: boolean; upload: (data: Uint8Array) => Promise; reset: () => void; }; ``` ### `useDownload(config)` ```ts function useDownload(config: StorageConfig): { data: Uint8Array | undefined; error: Error | undefined; loading: boolean; download: (root: string) => Promise; reset: () => void; }; ``` ### `useInference(config)` ```ts interface InferenceArgs { messages: ChatMessage[]; // { role, content } model?: string; temperature?: number; } function useInference(config: ComputeConfig): { data: InferenceResult | undefined; // { output, receipt, raw } error: Error | undefined; loading: boolean; infer: (args: InferenceArgs) => Promise; reset: () => void; }; ``` ### `useAttestation()` Takes no config (pure, no network, no keys). ```ts function useAttestation(): { data: VerifyResult | undefined; // { ok, checks, signer } error: Error | undefined; loading: boolean; verify: (signed: SignedEnvelope, expectedSigner: string) => Promise; reset: () => void; }; ``` `verify` never throws for a bad signature — it resolves `{ ok: false }` with per-check detail in `data.checks`. ## A complete Next.js example A client component. Upload runs through a server route (Node) while attestation verifies right in the browser. ```tsx "use client"; import { useUpload, useAttestation } from "@foundryprotocol/0gkit-react"; import type { SignedEnvelope } from "@foundryprotocol/0gkit-attestation"; export default function Console({ signed }: { signed: SignedEnvelope }) { // Config is read per-call via a ref — safe to recompute each render. const up = useUpload({ network: "galileo", privateKey: process.env.NEXT_PUBLIC_DEMO_KEY, // demo only — see safety note }); const at = useAttestation(); // pure crypto, browser-safe return (
    {up.data && root: {up.data.root}} {up.error && {up.error.message}} {at.data && (

    {at.data.ok ? "VERIFIED" : "NOT VERIFIED"} — digest{" "} {String(at.data.checks.digest)}, signer {String(at.data.checks.signer)}

    )}
    ); } ``` For a real app, do uploads/inference in a server action or API route (Node) and call it from the client, since the storage/compute SDKs are Node-only. ## Awaiting directly vs. reading state ```tsx const ai = useInference({ network: "galileo", brokerKey, provider }); async function onAsk() { try { const r = await ai.infer({ messages: [{ role: "user", content: "Hello 0G" }], }); console.log(r.output, r.receipt.latencyMs); } catch (err) { // same Error you'd see in ai.error } } // or just render ai.loading / ai.data / ai.error — both are kept in sync. ``` ## Related Wraps [storage](/packages/storage), [compute](/packages/compute), [attestation](/packages/attestation). See [Troubleshooting → key handling](/troubleshooting#key-handling--safety) before putting any key near the browser. Template: `npx degit rajkaria/0gkit/templates/react-app`. ============================================================================ # Templates Route: /templates URL: https://docs.0gkit.com/templates ============================================================================ # Templates 0gkit ships **nine** starter projects so you can go from zero to a running 0G app in one command. Each is a minimal, correct, copy-and-go example focused on one surface. Scaffold any of them with `create-0gkit-app`: ```bash npm create 0gkit-app@latest my-app -- --template cd my-app && pnpm install && pnpm dev ``` …or with `degit` if you want just the files (no git history): ```bash npx degit rajkaria/0gkit/templates/ my-app cd my-app && pnpm install ``` > The CLI also scaffolds a minimal read-only project with no template needed: > `npx @foundryprotocol/0gkit-cli init my-app` (writes `package.json`, `.env.example`, `index.mjs`, > `README.md`, `.gitignore`). Use that for a bare start; use a template below > for a focused, surface-specific example. ## Under 5 minutes — time to first success Every template ships a `0g.config.ts` (typed env via [`define0GConfig`](/packages/core)) and prints the first-success banner `[0gkit:first-success]` on its first 0G op so you know it worked. Approximate local time from `npm create` to that banner: | Template | Primary primitive | Time-to-first-success | | -------------------- | --------------------------- | --------------------- | | `storage-app` | Storage upload + dedup | ~45s | | `inference-app` | Compute inference | ~45s | | `attestation-verify` | Attestation (pure crypto) | ~30s | | `mcp-agent` | MCP server (stdio) | ~30s | | `nft-with-storage` | Storage + contract mint | ~60s | | `ai-agent` | Compute + durable jobs | ~60s | | `tee-attested-api` | Hono + TEE attestation | ~60s | | `chat` | Next.js + Storage + indexer | ~90s | | `react-app` | Next.js + 0gkit-react hooks | ~90s | --- ## The five canonical archetypes (SP8) ### `chat` Real-time chat where messages live on **0G Storage** and the on-chain `MessagePosted` event log is the source of truth for the message list. Uses the SP6 reorg-safe `useEvent` hook so rolled-back messages disappear automatically. ```bash npm create 0gkit-app@latest my-chat -- --template chat ``` Surfaces demoed: wallet + storage + indexer + react + typed contracts. Best when: building anything where users post → the chain remembers. ### `storage-app` Upload + retrieve a file with **SP7 dry-run preflight** (predict cost + Merkle root before broadcasting), **dedup** (skip the funding tx if the root already exists), and round-trip verify. Uses [`@foundryprotocol/0gkit-storage`](/packages/storage) + [`@foundryprotocol/0gkit-core`](/packages/core). ```bash npm create 0gkit-app@latest my-store -- --template storage-app ``` Surfaces demoed: wallet + storage + SP7 estimator. Best when: persisting datasets/artifacts to 0G. ### `ai-agent` Multi-step **LangChain-style ReAct agent** on 0G Compute, where every inference step's TEE attestation is verified before its decision is acted on. In-process loop today; documented hand-off path for SP10 (`0gkit-jobs`). ```bash npm create 0gkit-app@latest my-agent -- --template ai-agent ``` Surfaces demoed: wallet + compute + attestation. Best when: chained model reasoning with auditable enclave provenance. ### `tee-attested-api` A **Hono** HTTP API where every response carries an `X-0G-Attestation` header. Clients can verify cryptographically that the response originated inside genuine enclave hardware. Plain `console.log` access logging today; documented hand-off for SP11 (`0gkit-observability`). ```bash npm create 0gkit-app@latest my-api -- --template tee-attested-api ``` Surfaces demoed: wallet + attestation + compute. Best when: shipping a public API whose payloads need provenance. ### `nft-with-storage` A Foundry-deployed ERC-721 where both the metadata JSON **and** the media file live on 0G Storage. `tokenURI(id)` returns `0g-storage://`. Uses SP4 typed-contract codegen. ```bash npm create 0gkit-app@latest my-nft -- --template nft-with-storage ``` Surfaces demoed: wallet + storage + SP4 typed contracts. Best when: minting collectibles with on-chain provenance + off-chain payloads. --- ## The four Phase-1 starters ### `inference-app` A Node script that runs a chat completion against a 0G compute provider and prints the on-chain fee receipt, including the OpenAI drop-in shim. Uses [`@foundryprotocol/0gkit-compute`](/packages/compute). ```bash npm create 0gkit-app@latest my-infer -- --template inference-app ``` Best when: running paid LLM inference on 0G, or migrating OpenAI code. ### `attestation-verify` A pure, no-network, no-keys example that signs and verifies a TEE attestation envelope and prints a report. Uses [`@foundryprotocol/0gkit-attestation`](/packages/attestation). ```bash npm create 0gkit-app@latest my-attest -- --template attestation-verify ``` Best when: producing or checking signed eval-result attestations. ### `mcp-agent` A ready-to-wire MCP setup so Claude / Cursor / Cline can drive 0G. Includes the client config blocks from the [MCP guide](/mcp) and an embedding example with [`@foundryprotocol/0gkit-mcp`](/packages/mcp). ```bash npm create 0gkit-app@latest my-mcp -- --template mcp-agent ``` Best when: giving an LLM agent direct, glue-free 0G access. ### `react-app` A Next.js client component using the [`@foundryprotocol/0gkit-react`](/packages/react) hooks — browser-side attestation verify plus a server route for the Node-only upload/inference path. ```bash npm create 0gkit-app@latest my-ui -- --template react-app ``` Best when: building a 0G-powered React UI. ## Deploy on Vercel Every template ships with a one-click Vercel deploy button in its README. The button forks the template into a new repo under your GitHub account, prompts for the env vars below, and deploys on **Fluid Compute** in under 60 seconds. | Template | Required env vars | Deploy | | -------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chat` | `NETWORK`, `PRIVATE_KEY` | [![Deploy](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frajkaria%2F0gkit%2Ftree%2Fmain%2Ftemplates%2Fchat&project-name=0gkit-chat&env=NETWORK%2CPRIVATE_KEY&envDescription=See%200gkit.com%20env%20vars&envLink=https%3A%2F%2F0gkit.com%2Fgetting-started%2Fenv-vars) | | `storage-app` | `NETWORK`, `PRIVATE_KEY` | [![Deploy](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frajkaria%2F0gkit%2Ftree%2Fmain%2Ftemplates%2Fstorage-app&project-name=0gkit-storage-app&env=NETWORK%2CPRIVATE_KEY&envDescription=See%200gkit.com%20env%20vars&envLink=https%3A%2F%2F0gkit.com%2Fgetting-started%2Fenv-vars) | | `ai-agent` | `NETWORK`, `PRIVATE_KEY` | [![Deploy](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frajkaria%2F0gkit%2Ftree%2Fmain%2Ftemplates%2Fai-agent&project-name=0gkit-ai-agent&env=NETWORK%2CPRIVATE_KEY&envDescription=See%200gkit.com%20env%20vars&envLink=https%3A%2F%2F0gkit.com%2Fgetting-started%2Fenv-vars) | | `tee-attested-api` | `NETWORK`, `PRIVATE_KEY`, `OTEL_EXPORTER_OTLP_ENDPOINT` (opt) | [![Deploy](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frajkaria%2F0gkit%2Ftree%2Fmain%2Ftemplates%2Ftee-attested-api&project-name=0gkit-tee-attested-api&env=NETWORK%2CPRIVATE_KEY%2COTEL_EXPORTER_OTLP_ENDPOINT&envDescription=See%200gkit.com%20env%20vars&envLink=https%3A%2F%2F0gkit.com%2Fgetting-started%2Fenv-vars) | | `nft-with-storage` | `NETWORK`, `PRIVATE_KEY`, `RPC_URL` | [![Deploy](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frajkaria%2F0gkit%2Ftree%2Fmain%2Ftemplates%2Fnft-with-storage&project-name=0gkit-nft-with-storage&env=NETWORK%2CPRIVATE_KEY%2CRPC_URL&envDescription=See%200gkit.com%20env%20vars&envLink=https%3A%2F%2F0gkit.com%2Fgetting-started%2Fenv-vars) | | `inference-app` | `NETWORK`, `PRIVATE_KEY` | [![Deploy](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frajkaria%2F0gkit%2Ftree%2Fmain%2Ftemplates%2Finference-app&project-name=0gkit-inference-app&env=NETWORK%2CPRIVATE_KEY&envDescription=See%200gkit.com%20env%20vars&envLink=https%3A%2F%2F0gkit.com%2Fgetting-started%2Fenv-vars) | | `react-app` | `NETWORK`, `WALLETCONNECT_PROJECT_ID` (opt) | [![Deploy](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frajkaria%2F0gkit%2Ftree%2Fmain%2Ftemplates%2Freact-app&project-name=0gkit-react-app&env=NETWORK%2CWALLETCONNECT_PROJECT_ID&envDescription=See%200gkit.com%20env%20vars&envLink=https%3A%2F%2F0gkit.com%2Fgetting-started%2Fenv-vars) | | `mcp-agent` | `NETWORK`, `PRIVATE_KEY` | [![Deploy](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frajkaria%2F0gkit%2Ftree%2Fmain%2Ftemplates%2Fmcp-agent&project-name=0gkit-mcp-agent&env=NETWORK%2CPRIVATE_KEY&envDescription=See%200gkit.com%20env%20vars&envLink=https%3A%2F%2F0gkit.com%2Fgetting-started%2Fenv-vars) | | `attestation-verify` | `NETWORK` | [![Deploy](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frajkaria%2F0gkit%2Ftree%2Fmain%2Ftemplates%2Fattestation-verify&project-name=0gkit-attestation-verify&env=NETWORK&envDescription=See%200gkit.com%20env%20vars&envLink=https%3A%2F%2F0gkit.com%2Fgetting-started%2Fenv-vars) | ## After scaffolding Every template is testnet-first (Galileo) — no real funds needed. Run `npx @foundryprotocol/0gkit-cli doctor` to preflight, then follow the template's `README`. See [Getting started](/getting-started) for environment variables and [Troubleshooting](/troubleshooting) if a peer dependency complains. ## Add a kit after scaffolding Kits are drop-in feature overlays you can apply to any template after scaffolding. To see which kits are compatible with your project's base: ```bash 0g kits list ``` Then apply one or more: ```bash 0g add agent-memory 0g add ai-oracle sealed-inference ``` See the [Kits catalog](/kits) for a full list of available kits and what each one adds. ============================================================================ # Troubleshooting & FAQ Route: /troubleshooting URL: https://docs.0gkit.com/troubleshooting ============================================================================ # Troubleshooting & FAQ ## First: run `0g doctor` Almost every "it doesn't work" has a one-line answer from the preflight: ```bash npx @foundryprotocol/0gkit-cli doctor --network galileo npx @foundryprotocol/0gkit-cli doctor --json | jq '.checks[] | select(.ok==false)' ``` It checks RPC reachability + chain id, signer presence/funding, the storage indexer, the DA encoder, and faucet guidance. And remember: **every `ZeroGError` carries a `.hint`** — read it, it is the exact fix. ## Peer dependencies | Package | Required peers | Optional peers (when needed) | | ------------------------------------ | -------------- | -------------------------------------------------------------------------------------------------- | | `@foundryprotocol/0gkit-core` | `viem ^2.21` | — | | `@foundryprotocol/0gkit-chain` | `viem ^2.21` | — | | `@foundryprotocol/0gkit-storage` | `viem ^2.21` | `@0gfoundation/0g-storage-ts-sdk ^1.2.9`, `ethers ^6.16` — for upload/download/computeRoot/exists | | `@foundryprotocol/0gkit-compute` | `viem ^2.21` | `@0gfoundation/0g-compute-ts-sdk` (or `@0glabs/0g-serving-broker`), `ethers ^6.16` — for inference | | `@foundryprotocol/0gkit-da` | `viem ^2.21` | — | | `@foundryprotocol/0gkit-attestation` | `viem ^2.21` | — | | `@foundryprotocol/0gkit-react` | `react` | the primitive you use (+ its peers) | The `@0gfoundation/*` SDKs and `ethers` are marked **optional** so the packages install and type-check without them. They are lazily, dynamically imported only when you call an operation that needs them. If you see: > `ConfigError: @0gfoundation/0g-storage-ts-sdk could not be loaded …` > `ConfigError: 0G compute SDK not found …` > `ConfigError: ethers could not be loaded …` …install the peer the hint names, e.g.: ```bash npm install @0gfoundation/0g-storage-ts-sdk ethers # storage npm install @0gfoundation/0g-compute-ts-sdk ethers # compute ``` ## Browser limitations - **Node-only:** `Storage` (upload/download/computeRoot/exists) and `Compute` (inference) dynamically import the Node-only 0G SDKs and `ethers`. They do not run in a pure browser bundle — you will get a clean `ConfigError`. Run them in a Node script, a server action, an API route, or via the `0g` CLI. - **Browser-safe:** `@foundryprotocol/0gkit-core` (presets, client factory, canonical JSON), `@foundryprotocol/0gkit-da`'s `digest`/`verify`, and **all of `@foundryprotocol/0gkit-attestation`** (pure `viem` crypto). The 0gkit playground verifies attestations live in-browser. - In a Next.js/Turbopack app you can alias the Node-only SDKs to a stub for the client bundle (the playground does this in `next.config.ts` via `turbopack.resolveAlias`) so live upload/infer surface an honest "needs a server/CLI" error instead of a bundler crash. ## Key handling & safety - **Use a testnet key.** `galileo` is the default everywhere and needs no real funds. Get testnet funds at `https://faucet.0g.ai` (or `0g chain faucet `). - **Never ship a private key to the browser.** `useUpload`/`useInference` need Node anyway — do the signing server-side and call it from the client. Any `NEXT_PUBLIC_*` key is public; treat the React example's demo key as a demo only. - Keys can be passed with or without the leading `0x` — 0gkit normalizes them. A malformed key throws a `ConfigError` with the exact remedy. - 0gkit never logs or transmits your key anywhere except the RPC/SDK calls you invoke. ## FAQ **Which network is the default?** `galileo` (testnet) — everywhere: the CLI, the MCP server, and the primitives' network resolution. **`0g storage` rejects `--network local`.** Storage and DA only support `aristotle` | `galileo` (they need an indexer/encoder). Use `galileo`. **`waitForReceipt` throws `ChainError`.** The hash is wrong or was broadcast to a different network. Verify both. **`faucet()` throws on galileo.** Galileo has no programmatic faucet — that is expected. The `ConfigError.hint` is the web faucet URL (`https://faucet.0g.ai`). **`verifyEnvelope` returned `ok:false` but did not throw.** By design — attestation verification never throws for a bad signature/digest. Inspect `data.checks` (`{ digest, signer }`) to see which check failed. **Is Foundry required?** No. 0gkit is vendor-neutral and `pnpm boundary:check` enforces it in CI. Foundry is a separate, opt-in plugin (`ZEROG_FOUNDRY=1`), absent by default. **Where do I drop to the raw SDK?** `Storage#raw()`, `Compute#raw()`, `Compute#openai()`, or the viem clients from `createClient(...)`. See [Concepts → the escape hatch](/concepts#the-escape-hatch-to-the-raw-sdk). ## Still stuck? Open an issue at [github.com/rajkaria/0gkit/issues](https://github.com/rajkaria/0gkit/issues) with the full `ZeroGError` (`code`, `message`, `hint`) and `0g doctor --json` output.