> For the complete documentation index, see [llms.txt](https://whitepaper.litho.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://whitepaper.litho.ai/docs/phases/phase-8-completion.md).

# Phase 8 — SDKs & Developer Experience Portal

> **Status:** **100%** (2026-05-12). Layered SDK + tag-gated npm publish with provenance + GraphQL schema artifact + OpenAPI REST artifact + OpenAPI → SDK type codegen + typed REST runtime client + runnable examples — all live, all drift-gated where applicable.
>
> First written at \~65% on 2026-05-11. See [§ Updates](#updates-since-first-writing-2026-05-11) below for what shipped after.

## Updates since first writing (2026-05-11)

Both items the original write-up listed as deferred ("OpenAPI / GraphQL schema auto-generation") shipped, plus a typed runtime REST client and runnable examples.

### GraphQL schema artifact + drift gate (2026-05-11)

`Makalu/api/scripts/print-schema.ts` re-prints `typeDefs` via `graphql.print()`. Committed at `docs/api-reference/schema.graphql` (210 lines, 18 types). CI's `schema-sync-check` job runs `verify-schema-in-sync.ts` and fails on drift (cross-platform Node diff, no shell redirects). A third-party integrator can fork the SDL and codegen against it without running our server.

### OpenAPI REST artifact + drift gate (2026-05-12)

`Makalu/api/scripts/list-routes.ts` introspects both Express routers (`explorerRouter` + `lithoRouter`) without starting a server (db pool is lazy). `scripts/verify-openapi-in-sync.ts` compares the `(METHOD, path)` set against `paths:` in `docs/api-reference/openapi.yaml`, normalising Express `:param` ↔ OpenAPI `{param}` and stripping the `/api` server prefix. CI job `openapi-sync-check` fails PRs that add/rename/remove a route without updating the YAML. 24 endpoints documented (21 explorer + 3 demo `/api/litho/*`).

Response-body shapes intentionally NOT gated — that surface is test-owned. The route SET is the contract; the shapes are the implementation.

Hand-parsed YAML for the `paths:` keys (same pattern as `scripts/check-licenses.mjs`), no extra deps.

### OpenAPI → SDK type codegen + drift gate (2026-05-12)

`Makalu/packages/sdk/scripts/codegen-openapi.mjs` reads `docs/api-reference/openapi.yaml` via `openapi-typescript@^7` (devDep, no runtime cost) and emits a `paths` / `components` / `operations` type tree at `src/generated/openapi.ts` (\~1016 LoC). Re-exported from `@lithosphere/sdk`. Drift-gated by a new `openapi-codegen-check` CI job that re-runs the codegen and fails on `git diff --exit-code` — symmetric with the producer-side `openapi-sync-check`. The route contract is now drift-protected on both sides.

```ts
import type { paths } from '@lithosphere/sdk';
type BlocksResp = paths['/blocks']['get']['responses']['200']['content']['application/json'];
```

### Typed REST runtime client (2026-05-12)

`createLithoRestClient({ baseUrl })` returns a `Client<paths>` from `openapi-fetch` typed against the generated paths interface. End-to- end type safety: path strings autocomplete, query params validate, response bodies type-narrow against the OpenAPI schema. Factory pattern (not singleton) so tests + multi-network setups can have independent clients. 4 tests cover URL composition, path-param substitution, non-2xx surfaced as `{ error }` (not thrown), and custom-header forwarding.

```ts
import { createLithoRestClient } from '@lithosphere/sdk';
const api = createLithoRestClient({ baseUrl: 'https://makalu.litho.ai/api' });
const { data, error } = await api.GET('/blocks', { params: { query: { limit: 10 } } });
```

### Runnable examples (2026-05-12)

`Makalu/packages/sdk/examples/`:

| Script                 | Exercises                                                                                  |
| ---------------------- | ------------------------------------------------------------------------------------------ |
| `01-balances.ts`       | `LithoClient` against EVM JSON-RPC (chain head + native balance)                           |
| `02-rest-blocks.ts`    | `createLithoRestClient` end-to-end with paginated `/blocks` + per-height detail            |
| `03-contracts-viem.ts` | Pair `LEP100_ABI` from `@lithosphere/blockchain-core` with viem for a typed `readContract` |

Each script is self-contained, hits the live Makalu testnet, and doubles as a smoke check that the chain + indexer are healthy. `examples/README.md` covers both the monorepo-internal and standalone- npm-install invocation paths.

### sdk-template scaffold refresh (2026-05-11)

`create-litho-app`'s SDK template no longer copies `client.ts` (484 LoC) + `types.ts` (275 LoC) verbatim. It now `pnpm add @lithosphere/sdk` (pinned to `workspace:*` inside the monorepo; rewritten to `latest` by the CLI at scaffold time) and demonstrates the extension pattern via a `LithosphereExtensions` class. Bundle 1.87 KB (was an order of magnitude bigger). 5 tests cover invalid-address rejection, pending/success/failure paths, mixed batches.

### Bundle size

* SDK `dist/index.js`: 1.87 KB → **9.02 KB** (+7.15 KB) — the openapi- fetch runtime addition.
* SDK `dist/index.d.ts`: \~24 KB → **34.9 KB** (+11 KB) — the paths / components / operations type tree.

Both reasonable for what they buy.

The point-in-time content below describes the 2026-05-11 snapshot.

***

## What this phase covers

The phase goal from the work plan: **"Dev can call a contract on devnet in under 10 minutes using the portal guide."** Coming into this phase that wasn't possible — `@lithosphere/blockchain-core` was 100% empty stubs (`export {}` + a `VERSION` constant), and the only working client code was buried inside the `create-litho-app` template, not a published runtime package.

Leaving the phase: two layered npm packages, four runnable example apps in the docs, and a release workflow that can publish to npm with provenance attestations the moment an `NPM_TOKEN` secret is set.

## What we built

### Two layered packages

| Package                        | Role                                                                                                                                                       | Path                               | Tests |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ----: |
| `@lithosphere/blockchain-core` | Low-level primitives — network registry, typed ABIs, `LithoError` + `ErrorCode`, shared types. Zero runtime deps, browser-safe.                            | `Makalu/packages/blockchain-core/` |    12 |
| `@lithosphere/sdk`             | High-level client — `LithoClient` with retry/backoff, balance helpers, transaction polling. Re-exports `blockchain-core` so consumers install one package. | `Makalu/packages/sdk/`             |    17 |

The split is deliberate: anyone building tooling for Lithosphere (e.g. a block explorer, a wallet, an indexer) depends on **blockchain-core** for ABIs and network constants without pulling in a runtime client. Application code depends on **sdk** and gets everything in one install.

Both ship dual ESM + CJS (`tsup`) with `.d.ts` for ESM and CJS contexts, work in Node 18+ and modern browsers, and use raw `fetch` — no transitive viem/ethers/web3 dependency on the runtime path. For contract **writes** and signing, pair the SDK with viem or ethers; the typed ABIs (`LEP100_ABI`, `WLITHO_ABI`, `LITHONATIVE_ABI`) plug into both libraries directly.

### `LithoClient` — what it gives you

```ts
import { LithoClient, LithoError, ErrorCode } from '@lithosphere/sdk';

const client = new LithoClient('mainnet');
const { formatted, symbol } = await client.getBalance('0x22d...387');
console.log(`${formatted} ${symbol}`);
```

| Method                                                                  | Returns                                | Notes                                                       |
| ----------------------------------------------------------------------- | -------------------------------------- | ----------------------------------------------------------- |
| `getChainId()`                                                          | `Promise<number>`                      | Avoids an RPC call when chainId is known from the registry. |
| `getBlockNumber()`                                                      | `Promise<number>`                      | Current head, decimal.                                      |
| `getBalance(addr, opts?)`                                               | `Promise<AccountBalance>`              | Native LITHO, `formatted` + `balance` (bigint wei).         |
| `getTransaction(hash)`                                                  | `Promise<TransactionResponse \| null>` | `null` for unknown hash.                                    |
| `getTransactionReceipt(hash)`                                           | `Promise<TransactionReceipt \| null>`  | `null` while pending.                                       |
| `waitForTransaction(hash, confirmations?, timeoutMs?, pollIntervalMs?)` | `Promise<TransactionReceipt>`          | Throws `LithoError(TIMEOUT)` past budget.                   |
| `getNetworkConfig()`                                                    | `NetworkConfig \| null`                | The registry entry matching the client's RPC URL.           |

**Retry behaviour:** every RPC call goes through exponential backoff (`delay * 2^attempt`, default `{count: 3, delay: 250}`), and retries only on transient failures (`NETWORK_ERROR`, `RPC_TIMEOUT`, `RATE_LIMITED`). Bad params, invalid addresses, and contract reverts fail immediately — retrying won't help.

**Errors:** every failure path throws `LithoError` with a typed `code`. `instanceof LithoError` + `switch (err.code)` is the supported pattern. The underlying cause is preserved on `err.cause` (ES2022 `Error.cause`).

### `NETWORKS` registry

```ts
import { NETWORKS } from '@lithosphere/sdk';

NETWORKS.mainnet;
// { name: 'mainnet', chainId: 700777, rpcUrl: 'https://rpc.litho.ai',
//   cosmosChainId: 'lithosphere_700777-2', bech32Prefix: 'litho',
//   currency: { name: 'Lithosphere', symbol: 'LITHO', decimals: 18, denom: 'ulitho' }, ... }
```

Four profiles ship: `mainnet`, `staging`, `devnet`, `local`. Today the first three all resolve to the live Makalu testnet (there's no separate mainnet chain yet — see [`docs/network/chain-parameters.md`](/docs/network/chain-parameters.md)), so choose the profile based on **intent** of the code path (production vs CI staging vs local hardhat fork). `local` targets `http://localhost:8545`.

### Documentation portal updates

| Doc                                                                                         | Status                                                                                                                                                                                        |
| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`docs/api-reference/sdk-reference.md`](/docs/api-reference/sdk-reference.md)               | Rewrote from "Work in Progress" to a full reference: install, three runnable quickstarts, complete method table, errors enum, ABI exports, scaffolding pointer, versioning policy.            |
| [`docs/developers/examples/wagmi-example.md`](/docs/developers/examples/wagmi-example.md)   | New. Next.js 14 + wagmi + viem setup using `NETWORKS` to derive the wagmi chain config, with a connect button, balance display, and LEP-100 `transfer` call wired through `useWriteContract`. |
| [`docs/developers/examples/ethers-example.md`](/docs/developers/examples/ethers-example.md) | New. Node + ethers v6 reading native + token balances, signing a native transfer, and using `LithoClient.waitForTransaction` for confirmation polling.                                        |
| [`docs/governance/release-process.md`](/docs/governance/release-process.md)                 | New. `NPM_TOKEN` setup, semver policy for the SDK, dry-run procedure, rollback.                                                                                                               |
| [`_sidebar.md`](/_sidebar.md)                                                               | Surfaces the new examples + release process.                                                                                                                                                  |

The wagmi and ethers samples are deliberately end-to-end runnable — copy file, `pnpm install`, set env, run. That's the "<10 min to call a contract" promise from the phase spec.

### Tag-gated npm publish workflow

`.github/workflows/release.yaml` now syncs both packages' `package.json` versions from the tag, builds + tests both, packs tarballs for the GitHub Release, **and** publishes both to npm with `--provenance` attestations:

```yaml
publish_npm:
  needs: [validate, publish_github_release]
  if: startsWith(github.ref, 'refs/tags/v')
  permissions:
    contents: read
    id-token: write   # for provenance
  # ... publishes blockchain-core first, then sdk
```

The job is **`NPM_TOKEN`-aware**: if the secret isn't set, it emits a non-fatal warning and skips publish — so the first release won't fail just because the secret hasn't been added yet. The GitHub Release tarball publication still goes through. Provenance attestations show up as a "verified" badge on the npm package page linking back to the Actions run.

### `@lithosphere/sdk-template` rename

`Makalu/templates/sdk-template/` was previously also named `@lithosphere/sdk`, which collided with the new package. Renamed to `@lithosphere/sdk-template` (`private: true`). It remains the source-of-truth for the `create-litho-app` scaffold — not published to npm, copied at scaffold time.

## How to use what was built

**Install (once published):**

```bash
pnpm add @lithosphere/sdk
# pulls @lithosphere/blockchain-core transitively
```

**Quickstart — read the head block:**

```ts
import { LithoClient } from '@lithosphere/sdk';
const client = new LithoClient('mainnet');
console.log(await client.getBlockNumber());
```

**Quickstart — read a LEP-100 balance via viem:**

```ts
import { LEP100_ABI, NETWORKS } from '@lithosphere/sdk';
import { createPublicClient, http } from 'viem';

const viemClient = createPublicClient({ transport: http(NETWORKS.mainnet.rpcUrl) });
const balance = await viemClient.readContract({
  address: '0xtoken...',
  abi: LEP100_ABI,
  functionName: 'balanceOf',
  args: ['0xholder...'],
});
```

**Cutting a release:**

```bash
git tag -a v0.1.0 -m "chore(release): v0.1.0"
git push origin v0.1.0
# CI builds, packs tarballs to the GitHub Release, and publishes to npm with provenance
```

Full walkthrough in [release-process.md](/docs/governance/release-process.md).

## Why it matters

* **Closes the "<10 min to call a contract" acceptance criterion.** Before this phase the only path was "clone the monorepo, find the template, copy client.ts out of it." Now: `pnpm add @lithosphere/sdk`, copy a 5-line snippet from sdk-reference.md, done.
* **Decouples downstream from the live chain endpoints.** Anyone who hard-codes `rpc.litho.ai` in their code ships breakage on the next endpoint move. The `NETWORKS` registry is the one place to update.
* **Typed errors are recoverable errors.** A wallet UI can distinguish "RPC is slow, show a spinner" (`RPC_TIMEOUT`) from "user typed a bad address" (`INVALID_ADDRESS`) from "back off, you're being rate-limited" (`RATE_LIMITED`). Generic `Error` doesn't carry that information.
* **Browser-safe by construction.** `blockchain-core` has zero runtime deps, `sdk` only uses `fetch`. Bundling either into a frontend is one tree-shake away from the smallest possible footprint — no Node polyfills, no Buffer-shim drama.
* **Provenance attestations.** The published packages carry a cryptographically verifiable link back to the Actions run that built them. Supply-chain hardening at zero ongoing cost.

## Files & commits

| Path                                         | What                                                                                                                                                                                 |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Makalu/packages/blockchain-core/`           | Rebuilt from stubs: `src/networks.ts`, `src/errors.ts`, `src/types.ts`, `src/abis/{LEP100,WLITHO,LITHONative}.json`, `src/index.ts`, `tsup.config.ts`, `__tests__/networks.test.ts`. |
| `Makalu/packages/sdk/`                       | New package: `src/client.ts`, `src/index.ts`, `__tests__/client.test.ts`, `tsup.config.ts`, `README.md`, `CHANGELOG.md`.                                                             |
| `Makalu/templates/sdk-template/package.json` | Renamed to `@lithosphere/sdk-template`, `private: true`.                                                                                                                             |
| `.github/workflows/release.yaml`             | Added `publish_npm` job, updated smoke test to assert canonical SDK exports.                                                                                                         |
| `docs/api-reference/sdk-reference.md`        | Rewritten from placeholder.                                                                                                                                                          |
| `docs/developers/examples/wagmi-example.md`  | New.                                                                                                                                                                                 |
| `docs/developers/examples/ethers-example.md` | New.                                                                                                                                                                                 |
| `docs/governance/release-process.md`         | New.                                                                                                                                                                                 |
| `_sidebar.md`                                | Added new example + release-process entries.                                                                                                                                         |

Commit: `4701d55` (the entire layered SDK + docs + publish wiring landed together).

## Deferred work

* **OpenAPI / GraphQL schema auto-generation.** The api service exposes both REST and GraphQL today but the schemas aren't auto-published. Tracked separately — it's a Phase 9-ish observability concern more than an SDK one.
* **`create-litho-app` template refresh.** The template still pre-dates the published SDK; updating its scaffold to `pnpm add @lithosphere/sdk` instead of copying `client.ts` is a small follow-up.
* **Cosmos LCD / REST helpers.** v0.1 of the SDK is EVM-only. Cosmos-side helpers (delegations, governance, validators) are a Cosmos-aware add-on package, not a v0.1 deliverable.
* **First actual npm release.** The workflow is ready; `NPM_TOKEN` needs to land in repo secrets and someone needs to push the first `v0.1.0` tag.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://whitepaper.litho.ai/docs/phases/phase-8-completion.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
