Skip to content

Publish OpenAPI 3.x spec for the /api/* surface #155

Description

@TaprootFreak

Problem

External wallet integrators (Cake Wallet, Layerz Wallet, and any third-party self-hoster) currently have no machine-readable description of the /api/* surface. They either hand-mirror the Zod schemas from zk-coins/app/src/lib/api/schemas.ts (Drift risk — caught only by a live contract test on the consumer side) or read the Rust source directly (slow, error-prone, couples integrators to internal naming).

A published OpenAPI 3.x spec fixes all three:

  • Typed client generation in any language (TS via openapi-typescript, Dart via openapi_generator, Rust via progenitor, etc.).
  • Single source of truth for response shapes — @zkcoins/sdk becomes a thin wrapper instead of a hand-maintained schema mirror.
  • The Layerz repo already has a generated-client pattern (shared/openapi/layerzme.json) — they would import ours analogously.

This is the second of the three-step wallet-integration enablement track (after #153 /api/history, before the @zkcoins/sdk npm package).

Why this comes after #153

The Zod schemas (InfoResponseSchema, BalanceResponseSchema, SendResponseSchema, etc.) describe the current surface. They do not yet describe /api/history, which is needed for getTransactions() in every wallet adapter. Publishing the OpenAPI spec before #153 lands would produce an incomplete spec that integrators would have to re-pin within days.

Sequencing: merge #153 → add Zod schema for history in the app → generate OpenAPI from the now-complete Zod set → publish.

Proposed approach

Generator: zod-to-openapi (or @hono/zod-openapi if Hono is preferred). The existing Zod schemas in zk-coins/app/src/lib/api/schemas.ts already validate every await res.json() at the boundary — promoting them to OpenAPI removes one mirror.

Why not utoipa on the Rust side: would create a triangle (Rust handlers → utoipa annotations → spec, parallel to Zod). Today the Zod schemas serve as the boundary contract for the in-tree app; the contract test catches Rust ↔ Zod drift. Adding a third source (utoipa) would mean two specs to keep in sync. Single source of truth is better than two-out-of-three.

Publish locations:

  1. Static file in repozk-coins/app/openapi/zkcoins.yaml (or .json), checked in, regenerated by a script npm run generate:openapi that runs in CI as a check (fails if regenerated output ≠ committed).
  2. Live endpoint on the nodeGET /openapi.json served from zk-coins/node at runtime. Cake / Layerz adapters can fetch this against the configured apiUrl to detect server-version mismatches at SDK init.

The static file is the canonical artifact; the live endpoint is a convenience for runtime SDK init.

Acceptance criteria

  • zk-coins/app: zod-to-openapi integration in a scripts/generate-openapi.ts (or similar). Run once, produces openapi/zkcoins.yaml. CI job generate-openapi:check regenerates and diffs — fails on drift.
  • Every endpoint in router.rs is represented (info, balance, mint, send, commit, receive, proof, username/claim, username/resolve, history once Add GET /api/history?address= endpoint for wallet transaction history #153 lands).
  • Error envelope shape ({success: false, error: "<string>"}) modelled as a reusable components.schemas.ErrorResponse.
  • zk-coins/node: a small Rust handler serves the same YAML/JSON at GET /openapi.json. Sourced from the static file shipped in the binary (read at startup, served from memory — no per-request file I/O).
  • README §Features table entry for the new /openapi.json endpoint.
  • At least one end-to-end smoke test that fetches /openapi.json and validates it parses as a valid OpenAPI 3.x document.

Out of scope

  • Generated client code (TS, Dart, Rust) — consumers generate from the spec on demand. The repo ships only the spec, not the generated artifacts.
  • Versioning header (Accept: application/vnd.zkcoins.v1+json) — the API has no v1/v2 split today; adding the indirection now is premature. Revisit when the first breaking change ships.
  • Live-vs-static reconciliation logic in the SDK — the SDK fetches /openapi.json once at init for the runtime check; offline behaviour falls back to the SDK's bundled spec snapshot.

Open questions for the PR author

  1. YAML vs JSON as the canonical artifact. YAML is more diff-friendly in the repo; JSON is what most generator tools expect. Recommendation: YAML in the repo, JSON via the runtime endpoint (/openapi.json standard).
  2. Where the YAML lives. Options: zk-coins/app/openapi/zkcoins.yaml (next to the source-of-truth Zod schemas) or a dedicated zk-coins/spec repo (overkill until there are multiple consumers wanting to pin a release). Recommendation: in the app repo.
  3. Whether capabilities (feature flags) belong in the spec. Currently /api/info returns them as runtime values. The spec can either document the field as Record<string, bool> (capability set unknown ahead of time) or enumerate the known capabilities. Recommendation: enumerate — drift becomes a spec change, which is the desired signal.

References

  • Issue Add GET /api/history?address= endpoint for wallet transaction history #153/api/history (prerequisite).
  • zk-coins/app/src/lib/api/schemas.ts — current Zod source of truth.
  • zk-coins/app/src/__tests__/lib/api/contract.live.test.ts — existing live contract test (the precedent for "fail loudly on drift").
  • Memory project_zkcoins_api_info_normalization — defers the bitcoin_network enum addition until the SDK work picks up; the OpenAPI publish is where that enum would land server-side.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions