You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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).
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:
Static file in repo — zk-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).
Live endpoint on the node — GET /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.
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
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).
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.
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.
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.
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 fromzk-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:
openapi-typescript, Dart viaopenapi_generator, Rust viaprogenitor, etc.).@zkcoins/sdkbecomes a thin wrapper instead of a hand-maintained schema mirror.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/sdknpm 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 forgetTransactions()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-openapiif Hono is preferred). The existing Zod schemas inzk-coins/app/src/lib/api/schemas.tsalready validate everyawait 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:
zk-coins/app/openapi/zkcoins.yaml(or.json), checked in, regenerated by a scriptnpm run generate:openapithat runs in CI as a check (fails if regenerated output ≠ committed).GET /openapi.jsonserved fromzk-coins/nodeat runtime. Cake / Layerz adapters can fetch this against the configuredapiUrlto 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-openapiintegration in ascripts/generate-openapi.ts(or similar). Run once, producesopenapi/zkcoins.yaml. CI jobgenerate-openapi:checkregenerates and diffs — fails on drift.router.rsis 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).{success: false, error: "<string>"}) modelled as a reusablecomponents.schemas.ErrorResponse.zk-coins/node: a small Rust handler serves the same YAML/JSON atGET /openapi.json. Sourced from the static file shipped in the binary (read at startup, served from memory — no per-request file I/O)./openapi.jsonendpoint./openapi.jsonand validates it parses as a valid OpenAPI 3.x document.Out of scope
Accept: application/vnd.zkcoins.v1+json) — the API has nov1/v2split today; adding the indirection now is premature. Revisit when the first breaking change ships./openapi.jsononce at init for the runtime check; offline behaviour falls back to the SDK's bundled spec snapshot.Open questions for the PR author
/openapi.jsonstandard).zk-coins/app/openapi/zkcoins.yaml(next to the source-of-truth Zod schemas) or a dedicatedzk-coins/specrepo (overkill until there are multiple consumers wanting to pin a release). Recommendation: in the app repo.capabilities(feature flags) belong in the spec. Currently/api/inforeturns them as runtime values. The spec can either document the field asRecord<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
/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").project_zkcoins_api_info_normalization— defers thebitcoin_networkenum addition until the SDK work picks up; the OpenAPI publish is where that enum would land server-side.