0gkitdocsGitHub

React guide

@foundryprotocol/0gkit-react is four thin reactive hooks over the 0G primitives. react is a peer dependency.

npm install @foundryprotocol/0gkit-react react

The shared shape

Every hook returns the same AsyncState envelope plus a named runner and a reset:

interface AsyncState<T> {
  data: T | undefined;
  error: Error | undefined;
  loading: boolean;
}
// each hook adds: <runner>(...args) => Promise<T>  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)

function useUpload(config: StorageConfig): {
  data: UploadResult | undefined; // { root, tx, raw }
  error: Error | undefined;
  loading: boolean;
  upload: (data: Uint8Array) => Promise<UploadResult>;
  reset: () => void;
};

useDownload(config)

function useDownload(config: StorageConfig): {
  data: Uint8Array | undefined;
  error: Error | undefined;
  loading: boolean;
  download: (root: string) => Promise<Uint8Array>;
  reset: () => void;
};

useInference(config)

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<InferenceResult>;
  reset: () => void;
};

useAttestation()

Takes no config (pure, no network, no keys).

function useAttestation(): {
  data: VerifyResult | undefined; // { ok, checks, signer }
  error: Error | undefined;
  loading: boolean;
  verify: (signed: SignedEnvelope, expectedSigner: string) => Promise<VerifyResult>;
  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.

"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 (
    <div>
      <button
        disabled={up.loading}
        onClick={() => {
          // The runner rejects too — handle it or read up.error reactively.
          up.upload(new TextEncoder().encode("hello 0G")).catch(() => {});
        }}
      >
        {up.loading ? "uploading…" : "upload"}
      </button>
      {up.data && <code>root: {up.data.root}</code>}
      {up.error && <span role="alert">{up.error.message}</span>}

      <button
        disabled={at.loading}
        onClick={() => at.verify(signed, signed.envelope.coordinator)}
      >
        verify attestation
      </button>
      {at.data && (
        <p>
          {at.data.ok ? "VERIFIED" : "NOT VERIFIED"} — digest{" "}
          {String(at.data.checks.digest)}, signer {String(at.data.checks.signer)}
        </p>
      )}
    </div>
  );
}

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

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, compute, attestation. See Troubleshooting → key handling before putting any key near the browser. Template: npx degit rajkaria/0g-ai-kit/templates/react-app.