From 6b646725f3486d636439cdcdf44bdcc70b2b4320 Mon Sep 17 00:00:00 2001 From: tmm Date: Wed, 1 Jul 2026 13:21:22 -0400 Subject: [PATCH 1/4] docs(api): typed client --- src/pages/docs/api/json-rpc.mdx | 19 +++ src/pages/docs/api/typed-client.mdx | 161 ++++++++++++++++++++++++ src/pages/docs/sdk/typescript/index.mdx | 6 + vocs.config.ts | 4 + 4 files changed, 190 insertions(+) create mode 100644 src/pages/docs/api/typed-client.mdx diff --git a/src/pages/docs/api/json-rpc.mdx b/src/pages/docs/api/json-rpc.mdx index 9cb0f52e..b32509e2 100644 --- a/src/pages/docs/api/json-rpc.mdx +++ b/src/pages/docs/api/json-rpc.mdx @@ -47,6 +47,25 @@ Use the Tempo JSON-RPC API with command-line tools, TypeScript clients, React ho ``` + + The [Tempo API Typed Client](/docs/api/typed-client) is Tempo's typed API client for TypeScript. Use it when you want one client for Tempo REST endpoints and the JSON-RPC passthrough, with typed route parameters, status narrowing, and Tempo API error envelopes. + +
+ + ```ts [example.ts] + import { Client } from 'tapimo' + + const client = Client.create({ apiKey: process.env.TEMPO_API_KEY }) + + const response = await client.rpc[':chain{mainnet|testnet|[0-9]+}?'].$post({ + param: { chain: 'testnet' }, // [!code focus] + json: { jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] }, // [!code focus] + }) + + const body = await response.json() // [!code focus] + ``` + + [Wagmi](https://wagmi.sh/react/api/createConfig) provides React hooks and configuration helpers on top of Viem clients. diff --git a/src/pages/docs/api/typed-client.mdx b/src/pages/docs/api/typed-client.mdx new file mode 100644 index 00000000..5e31dc87 --- /dev/null +++ b/src/pages/docs/api/typed-client.mdx @@ -0,0 +1,161 @@ +--- +title: Tempo API Typed Client +description: Use the Tempo API Typed Client to call REST and JSON-RPC endpoints with autocomplete, API key headers, status narrowing, and error handling in TypeScript. +--- + +# Tempo API Typed Client + +Use the Tempo API Typed Client for endpoint autocomplete and API-key/header configuration across Tempo API HTTPS endpoints, including the JSON-RPC passthrough at `https://api.tempo.xyz/rpc`. + +The client is generated from the same Hono app that serves the API. That gives TypeScript autocomplete for routes, parameter names, request bodies, HTTP status codes, response bodies, and error envelopes. + +## Install the Tempo API package + +```bash [Terminal] +pnpm add tapimo +``` + +## Create a typed Tempo API client + +```ts twoslash [client.ts] +// @noErrors +import { Client } from 'tapimo' + +const client = Client.create({ + // API key sugar for the canonical `tempo-api-key` header. + apiKey: 'tempo_api_key', + // Override the API URL. Defaults to `Client.defaultUrl`. + url: 'https://api.tempo.xyz', + // Or pass custom headers directly. + headers: { authorization: 'Bearer tempo_api_key' }, +}) +``` + +`Client.create()` also accepts Hono client options such as a custom `fetch` implementation. If you pass `apiKey`, the client merges it into the request headers as `tempo-api-key`. + +## Make type-safe Tempo API requests + +Requests are end-to-end type-safe across params, statuses, response bodies, and errors. For example, `GET /v1/tokens/:token` narrows the response body from the HTTP status: + +```ts twoslash [tokens.ts] +// @noErrors +import { Client } from 'tapimo' + +const client = Client.create() + +const response = await client.v1.tokens[':token'].$get({ + param: { token: '0x20c0000000000000000000000000000000000000' }, +}) + +// Narrow responses by status. +if (response.status !== 200) { + response.status + // ^? (property) status: 400 | 401 | 402 | 403 | 404 | 429 | 502 + + if (response.status === 404) { + const json = await response.json() + // ^? const json: { error: { code: "token_not_found"; message: string; ... }; requestId: string } + json.error.code + // ^? (property) code: "token_not_found" + } else if (response.status === 402) { + const challenge = await response.text() + // ^? const challenge: string + // Handle the payment challenge from `WWW-Authenticate`. + } else { + const json = await response.json() + // ^? const json: { error: { code: "query_invalid" | "api_key_invalid" | "upstream_error" | ...; ... }; requestId: string } + // Handle validation, auth, rate-limit, or upstream errors. + } + + throw new Error('Request failed') +} + +response.status +// ^? (property) status: 200 +const token = await response.json() +// ^? const token: { address: Hex.Hex; currency: string; decimals: number; name: string; ... } +token.symbol +// ^? (property) symbol: string +``` + +## Call Tempo JSON-RPC through the Typed Client + +The Typed Client exposes the raw JSON-RPC passthrough as a typed route under `client.rpc`. The route key includes Hono's path pattern because the chain selector is optional: + +```ts twoslash [rpc.ts] +// @noErrors +import { Client } from 'tapimo' + +const client = Client.create({ apiKey: 'tempo_api_key' }) + +const response = await client.rpc[':chain{mainnet|testnet|[0-9]+}?'].$post({ + // Use `undefined` for `/rpc`, or pass `mainnet`, `testnet`, or a numeric chain id. + param: { chain: 'testnet' }, + json: { + jsonrpc: '2.0', + id: 1, + method: 'eth_blockNumber', + params: [], + }, +}) + +if (response.status !== 200) { + const error = await response.json() + throw new Error(error.error.message) +} + +const body = await response.json() + +if (!Array.isArray(body) && body.error) { + throw new Error(body.error.message) +} + +const blockNumber = Array.isArray(body) ? body[0]?.result : body.result +// ^? const blockNumber: unknown +``` + +Chain selectors map to these hosted endpoints: + +| `chain` value | Hosted RPC path | +| --- | --- | +| `undefined` | `https://api.tempo.xyz/rpc` | +| `'mainnet'` | `https://api.tempo.xyz/rpc/mainnet` | +| `'testnet'` | `https://api.tempo.xyz/rpc/testnet` | +| `'4217'` | `https://api.tempo.xyz/rpc/4217` | + +## Send batch JSON-RPC requests through the Typed Client + +The JSON-RPC body type accepts one request or an array of requests. The client narrows the success body to the same single-or-batch response union: + +```ts twoslash [batch-rpc.ts] +// @noErrors +import { Client } from 'tapimo' + +const client = Client.create() + +const response = await client.rpc[':chain{mainnet|testnet|[0-9]+}?'].$post({ + param: { chain: undefined }, + json: [ + { jsonrpc: '2.0', id: 1, method: 'eth_chainId', params: [] }, + { jsonrpc: '2.0', id: 2, method: 'eth_blockNumber', params: [] }, + ], +}) + +if (response.status === 200) { + const results = await response.json() + // results is typed as one JSON-RPC response or an array of JSON-RPC responses. +} +``` + +## Understand Typed Client RPC boundaries + +The Typed Client types the HTTP and JSON-RPC envelope around raw RPC calls: + +- the `/rpc`, `/rpc/mainnet`, `/rpc/testnet`, and `/rpc/:chainId` route shape +- the JSON-RPC request envelope (`jsonrpc`, `id`, `method`, and `params`) +- single-request and batch-request response envelopes +- non-200 Tempo API error envelopes and status-code narrowing + +The Typed Client does not type every JSON-RPC method's `params` and `result` fields. Those stay `unknown` because the passthrough accepts the full Ethereum JSON-RPC surface and Tempo-specific methods. Use [Viem](/docs/api/json-rpc#connect-to-tempo-json-rpc-with-tools) when you want method-level TypeScript types for common chain reads and writes. + +Continue with the [Tempo API reference](/docs/api) for endpoint-specific fields and the [JSON-RPC API](/docs/api/json-rpc) for RPC endpoint details. diff --git a/src/pages/docs/sdk/typescript/index.mdx b/src/pages/docs/sdk/typescript/index.mdx index 0bc82355..4092f646 100644 --- a/src/pages/docs/sdk/typescript/index.mdx +++ b/src/pages/docs/sdk/typescript/index.mdx @@ -14,6 +14,12 @@ Tempo distributes TypeScript SDKs for: The Tempo extensions cover common chain operations such as querying state, sending Tempo Transactions, and managing tokens and AMM pools. + Date: Wed, 1 Jul 2026 18:05:15 -0400 Subject: [PATCH 2/4] docs(api): add tapimo dependency --- package.json | 1 + pnpm-lock.yaml | 337 ++++++++++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 1 + 3 files changed, 339 insertions(+) diff --git a/package.json b/package.json index e25ecf16..ababda07 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "sql-formatter": "^15.7.3", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.2", + "tapimo": "0.0.1", "unified": "^11.0.5", "unplugin-auto-import": "^21.0.0", "unplugin-icons": "^23.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8aebba96..22783056 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,6 +120,9 @@ importers: tailwindcss: specifier: ^4.2.2 version: 4.2.2 + tapimo: + specifier: 0.0.1 + version: 0.0.1(04687d79abcc92c1f3b5219c6c82df03) unified: specifier: ^11.0.5 version: 11.0.5 @@ -645,6 +648,12 @@ packages: peerDependencies: hono: ^4 + '@hono/standard-validator@0.2.3': + resolution: {integrity: sha512-bp9vHu6Va6SfMHC3D4ZLBbT/woi+AZ9CRdTXQu3kLJuLh2W/Gb9UO4hijS+BQAGFXi4EGpXdetxpzwTAawSVeg==} + peerDependencies: + '@standard-schema/spec': ^1.0.0 + hono: '>=3.9.0' + '@iconify-json/lucide@1.2.102': resolution: {integrity: sha512-Dm3EEqu5NrmzyDMB2U1+8yroEj2/dB9V4KlH0m/szwwF/ofSf0cPaGTZqkd1aExXjCor+vU53ttRMCGuXf+/cg==} @@ -1208,6 +1217,10 @@ packages: resolution: {integrity: sha512-E6beEdTsJxUStxOmY1knQvSNJq6LTiXOsRX2WTrfmU6d/kiATn6IKkAU0kXtAZkaYCGU4UCEmBFHCMmNKn0JLA==} engines: {node: '>=22'} + '@scalar/openapi-types@0.8.0': + resolution: {integrity: sha512-WmaxVSfvY5K/TwcG2B2TU1WOe1As1uc2s7myswtP6dBlcjU3hM08SApxv/jmyGaCE8t4gO5BBhmHY4pDUfmr2g==} + engines: {node: '>=22'} + '@scalar/openapi-types@0.9.1': resolution: {integrity: sha512-gkGhSkxSzADaBiNg+ZAbJuwj+ZUmzP2Pg9CWZ7ZP+0fck2WjPeDDM7aAbouAm0aQQMF9xBjSPXSA9a/qTHYaTw==} engines: {node: '>=22'} @@ -1307,6 +1320,67 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@standard-community/standard-json@0.3.5': + resolution: {integrity: sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA==} + peerDependencies: + '@standard-schema/spec': ^1.0.0 + '@types/json-schema': ^7.0.15 + '@valibot/to-json-schema': ^1.3.0 + arktype: ^2.1.20 + effect: ^3.16.8 + quansync: ^0.2.11 + sury: ^10.0.0 + typebox: ^1.0.17 + valibot: ^1.1.0 + zod: ^3.25.0 || ^4.0.0 + zod-to-json-schema: ^3.24.5 + peerDependenciesMeta: + '@valibot/to-json-schema': + optional: true + arktype: + optional: true + effect: + optional: true + sury: + optional: true + typebox: + optional: true + valibot: + optional: true + zod: + optional: true + zod-to-json-schema: + optional: true + + '@standard-community/standard-openapi@0.2.9': + resolution: {integrity: sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg==} + peerDependencies: + '@standard-community/standard-json': ^0.3.5 + '@standard-schema/spec': ^1.0.0 + arktype: ^2.1.20 + effect: ^3.17.14 + openapi-types: ^12.1.3 + sury: ^10.0.0 + typebox: ^1.0.0 + valibot: ^1.1.0 + zod: ^3.25.0 || ^4.0.0 + zod-openapi: ^4 + peerDependenciesMeta: + arktype: + optional: true + effect: + optional: true + sury: + optional: true + typebox: + optional: true + valibot: + optional: true + zod: + optional: true + zod-openapi: + optional: true + '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} @@ -3015,6 +3089,21 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} + hono-openapi@1.3.0: + resolution: {integrity: sha512-xDvCWpWEIv0weEmnl3EjRQzqbHIO8LnfzMuYOCmbuyE5aes6aXxLg4vM3ybnoZD5TiTUkA6PuRQPJs3R7WRBig==} + peerDependencies: + '@hono/standard-validator': ^0.2.0 + '@standard-community/standard-json': ^0.3.5 + '@standard-community/standard-openapi': ^0.2.9 + '@types/json-schema': ^7.0.15 + hono: ^4.8.3 + openapi-types: ^12.1.3 + peerDependenciesMeta: + '@hono/standard-validator': + optional: true + hono: + optional: true + hono@4.12.26: resolution: {integrity: sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==} engines: {node: '>=16.9.0'} @@ -3067,6 +3156,11 @@ packages: import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + incur@0.4.10: + resolution: {integrity: sha512-u35+Ba0oJOPQnAYxYPo5j91P/rI9kTtpDf/iJe6kOs335iguz8mYnVguex4NP6GmIP6uc7iqUWY5cmdc/0MwTw==} + engines: {node: '>=22'} + hasBin: true + incur@0.4.5: resolution: {integrity: sha512-4WFlqm5e+IKNoX+lDIjjpebO8ResPx3vPUBChxNfnNo3oGp0ooo6piiiTspOMub2dq2mcG/AmlUq0ejuGfKobg==} engines: {node: '>=22'} @@ -3208,6 +3302,10 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + kysely@0.28.17: + resolution: {integrity: sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==} + engines: {node: '>=20.0.0'} + langium@4.2.2: resolution: {integrity: sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==} engines: {node: '>=20.10.0', npm: '>=10.2.3'} @@ -3575,6 +3673,9 @@ packages: typescript: optional: true + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} @@ -3603,6 +3704,25 @@ packages: hono: optional: true + mppx@0.6.31: + resolution: {integrity: sha512-euHHDLjNa9DQdPSzpayRBhUb3YVGSNZhVAzoOPmjzqxcEb77ry59uaeSBsWr3IjSxqmpR45cB6aR0uiNSgO6kA==} + hasBin: true + peerDependencies: + '@modelcontextprotocol/sdk': '>=1.25.0' + elysia: '>=1' + express: '>=5' + hono: '>=4.12.18' + viem: '>=2.51.0' + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true + elysia: + optional: true + express: + optional: true + hono: + optional: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3779,6 +3899,9 @@ packages: oniguruma-to-es@4.3.6: resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + outvariant@1.4.0: resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==} @@ -3790,6 +3913,22 @@ packages: typescript: optional: true + ox@0.14.25: + resolution: {integrity: sha512-8DoibKtxE8yw63Y2jjMhlbjaURev6WCx4QR4MWLusl2/qIaeTzMJMBIYIDl1KOF45+8H1Ur6eLTdPlUoO8PlRw==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + ox@0.14.27: + resolution: {integrity: sha512-+xhLHo/f+f4BH121/1Pomm/1vgBBda1wYiFpTvjSo8o5OcEj76Pf1hGPJiepoYMTQoTm2SKdSBvWkFWk5l07PA==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + ox@0.14.29: resolution: {integrity: sha512-M5j87Ec4V99MQdRct/g09eWXW60g6zhHTUs1lr4deUtrPDnezBdCJTgKd7pxqTpSZBFveV0ALi9jMMuT1qKyNg==} peerDependencies: @@ -4316,6 +4455,9 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + tapimo@0.0.1: + resolution: {integrity: sha512-JjBmtWEGjEnVyrObc9bsG0qnTeswSPLMaYexRKL7EpWwxP3QWkqHxDo16RLnBHeSdSv4xosSNIfbl24ik26EZw==} + tar@7.5.13: resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==} engines: {node: '>=18'} @@ -4375,6 +4517,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tidx.ts@0.1.1: + resolution: {integrity: sha512-XFoDuwCQwkERGRYB+QGQvzH//Y6kDknttWSts3durreALgkrKOCM9mUDfgRVg3zVisuQgx5RfvoJmO8x8oXm+g==} + time-span@5.1.0: resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} engines: {node: '>=12'} @@ -4603,6 +4748,14 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + viem@2.51.3: + resolution: {integrity: sha512-DA4EbrsvatzzLo6MwcWWiv6kI6dIr3I9HH9B6qsJaClN/s0AjIDUz5RIxl+VmGrovIUCcIvG8744yuGH7d37zw==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + viem@2.53.1: resolution: {integrity: sha512-FhfJ/SW73CVosiyVLmIMVgKDRKYV1AGCLzZoHYvmNayyVff63Qi1ocPCk59LqC/cNw244RbBJjHnmxqXkE7NpA==} peerDependencies: @@ -5405,6 +5558,11 @@ snapshots: dependencies: hono: 4.12.26 + '@hono/standard-validator@0.2.3(@standard-schema/spec@1.1.0)(hono@4.12.26)': + dependencies: + '@standard-schema/spec': 1.1.0 + hono: 4.12.26 + '@iconify-json/lucide@1.2.102': dependencies: '@iconify/types': 2.0.0 @@ -6046,6 +6204,8 @@ snapshots: leven: 4.1.0 yaml: 2.9.0 + '@scalar/openapi-types@0.8.0': {} + '@scalar/openapi-types@0.9.1': {} '@scalar/openapi-upgrader@0.2.9': @@ -6222,6 +6382,23 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/json-schema': 7.0.15 + quansync: 0.2.11 + optionalDependencies: + zod: 4.3.6 + zod-to-json-schema: 3.25.2(zod@4.3.6) + + '@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@4.3.6)': + dependencies: + '@standard-community/standard-json': 0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6) + '@standard-schema/spec': 1.1.0 + openapi-types: 12.1.3 + optionalDependencies: + zod: 4.3.6 + '@standard-schema/spec@1.0.0': {} '@standard-schema/spec@1.1.0': {} @@ -8013,6 +8190,16 @@ snapshots: highlight.js@11.11.1: {} + hono-openapi@1.3.0(@hono/standard-validator@0.2.3(@standard-schema/spec@1.1.0)(hono@4.12.26))(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@4.3.6))(@types/json-schema@7.0.15)(hono@4.12.26)(openapi-types@12.1.3): + dependencies: + '@standard-community/standard-json': 0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6) + '@standard-community/standard-openapi': 0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@4.3.6) + '@types/json-schema': 7.0.15 + openapi-types: 12.1.3 + optionalDependencies: + '@hono/standard-validator': 0.2.3(@standard-schema/spec@1.1.0)(hono@4.12.26) + hono: 4.12.26 + hono@4.12.26: {} html-void-elements@3.0.0: {} @@ -8062,6 +8249,16 @@ snapshots: import-meta-resolve@4.2.0: {} + incur@0.4.10: + dependencies: + '@cfworker/json-schema': 4.1.1 + '@modelcontextprotocol/server': 2.0.0-alpha.2(@cfworker/json-schema@4.1.1) + '@scalar/openapi-types': 0.8.0 + '@toon-format/toon': 2.1.0 + tokenx: 1.3.0 + yaml: 2.9.0 + zod: 4.4.3 + incur@0.4.5: dependencies: '@cfworker/json-schema': 4.1.1 @@ -8163,6 +8360,8 @@ snapshots: khroma@2.1.0: {} + kysely@0.28.17: {} + langium@4.2.2: dependencies: '@chevrotain/regexp-to-ast': 12.0.0 @@ -8808,6 +9007,8 @@ snapshots: optionalDependencies: typescript: 5.9.3 + mitt@3.0.1: {} + mlly@1.8.2: dependencies: acorn: 8.17.0 @@ -8835,6 +9036,19 @@ snapshots: transitivePeerDependencies: - typescript + mppx@0.6.31(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.26)(typescript@5.9.3)(viem@2.51.3(typescript@5.9.3)(zod@4.4.3)): + dependencies: + incur: 0.4.10 + ox: 0.14.27(typescript@5.9.3)(zod@4.4.3) + viem: 2.51.3(typescript@5.9.3)(zod@4.4.3) + zod: 4.4.3 + optionalDependencies: + '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + express: 5.2.1 + hono: 4.12.26 + transitivePeerDependencies: + - typescript + ms@2.1.3: {} mz@2.7.0: @@ -8901,6 +9115,8 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 + openapi-types@12.1.3: {} + outvariant@1.4.0: {} ox@0.14.20(typescript@5.9.3)(zod@4.3.6): @@ -8933,6 +9149,36 @@ snapshots: transitivePeerDependencies: - zod + ox@0.14.25(typescript@5.9.3)(zod@4.4.3): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.9.3)(zod@4.4.3) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - zod + + ox@0.14.27(typescript@5.9.3)(zod@4.4.3): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.9.3)(zod@4.4.3) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - zod + ox@0.14.29(typescript@5.9.3)(zod@4.3.6): dependencies: '@adraffy/ens-normalize': 1.11.1 @@ -9640,6 +9886,70 @@ snapshots: tapable@2.3.3: {} + tapimo@0.0.1(04687d79abcc92c1f3b5219c6c82df03): + dependencies: + '@hono/node-server': 2.0.5(hono@4.12.26) + '@hono/standard-validator': 0.2.3(@standard-schema/spec@1.1.0)(hono@4.12.26) + hono: 4.12.26 + hono-openapi: 1.3.0(@hono/standard-validator@0.2.3(@standard-schema/spec@1.1.0)(hono@4.12.26))(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@0.2.11)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@4.3.6))(@types/json-schema@7.0.15)(hono@4.12.26)(openapi-types@12.1.3) + incur: 0.4.10 + jose: 6.2.3 + mppx: 0.6.31(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.26)(typescript@5.9.3)(viem@2.51.3(typescript@5.9.3)(zod@4.4.3)) + nanoid: 5.1.15 + ox: 0.14.27(typescript@5.9.3)(zod@4.4.3) + tidx.ts: 0.1.1(typescript@5.9.3) + viem: 2.51.3(typescript@5.9.3)(zod@4.4.3) + vocs: 2.2.5(@cfworker/json-schema@4.1.1)(@types/react@19.2.14)(@vue/compiler-sfc@3.5.38)(change-case@5.4.4)(idb-keyval@6.2.2)(mermaid@11.14.0)(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.104.1(esbuild@0.27.7)))(react@19.2.6)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0))(waku@1.0.0-beta.0(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(react-dom@19.2.6(react@19.2.6))(react-server-dom-webpack@19.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(webpack@5.104.1(esbuild@0.27.7)))(react@19.2.6)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0)) + zod: 4.4.3 + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@date-fns/tz' + - '@modelcontextprotocol/sdk' + - '@remix-run/react' + - '@rolldown/plugin-babel' + - '@standard-community/standard-json' + - '@standard-community/standard-openapi' + - '@standard-schema/spec' + - '@svgx/core' + - '@tanstack/react-router' + - '@types/json-schema' + - '@types/react' + - '@vocs/twoslash-rust' + - '@vue/compiler-sfc' + - '@vue/composition-api' + - async-validator + - axios + - babel-plugin-react-compiler + - bufferutil + - change-case + - date-fns + - drauu + - elysia + - express + - idb-keyval + - jwt-decode + - mermaid + - next + - nprogress + - openapi-types + - qrcode + - react + - react-dom + - react-router + - react-router-dom + - react-server-dom-webpack + - rollup + - sortablejs + - supports-color + - svelte + - typescript + - universal-cookie + - utf-8-validate + - vite + - vue-template-compiler + - vue-template-es2015-compiler + - waku + tar@7.5.13: dependencies: '@isaacs/fs-minipass': 4.0.1 @@ -9673,6 +9983,16 @@ snapshots: dependencies: any-promise: 1.3.0 + tidx.ts@0.1.1(typescript@5.9.3): + dependencies: + abitype: 1.2.3(typescript@5.9.3)(zod@4.4.3) + kysely: 0.28.17 + mitt: 3.0.1 + ox: 0.14.20(typescript@5.9.3)(zod@4.4.3) + zod: 4.4.3 + transitivePeerDependencies: + - typescript + time-span@5.1.0: dependencies: convert-hrtime: 5.0.0 @@ -9902,6 +10222,23 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + viem@2.51.3(typescript@5.9.3)(zod@4.4.3): + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.9.3)(zod@4.4.3) + isows: 1.0.7(ws@8.20.1) + ox: 0.14.25(typescript@5.9.3)(zod@4.4.3) + ws: 8.20.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + viem@2.53.1(typescript@5.9.3)(zod@4.3.6): dependencies: '@noble/curves': 1.9.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 722470f2..060611d2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,6 +13,7 @@ minimumReleaseAgeExclude: - viem - vocs - vite + - tapimo - '@vocs/twoslash-rust' - '@vocs/twoslash-rust-darwin-arm64' - '@vocs/twoslash-rust-linux-x64-gnu' From f37750de5d2265321e93250d7684e2ad326efafc Mon Sep 17 00:00:00 2001 From: tmm Date: Thu, 2 Jul 2026 11:45:55 -0400 Subject: [PATCH 3/4] chore: tweaks --- src/pages/docs/api/typed-client.mdx | 93 ++++++++++++++++------------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/src/pages/docs/api/typed-client.mdx b/src/pages/docs/api/typed-client.mdx index 5e31dc87..79ea1fb0 100644 --- a/src/pages/docs/api/typed-client.mdx +++ b/src/pages/docs/api/typed-client.mdx @@ -5,20 +5,29 @@ description: Use the Tempo API Typed Client to call REST and JSON-RPC endpoints # Tempo API Typed Client -Use the Tempo API Typed Client for endpoint autocomplete and API-key/header configuration across Tempo API HTTPS endpoints, including the JSON-RPC passthrough at `https://api.tempo.xyz/rpc`. +Use the Tempo API Typed Client for endpoint autocomplete and full end-to-end type-safety across API parameters, response bodies, statuses, and errors. -The client is generated from the same Hono app that serves the API. That gives TypeScript autocomplete for routes, parameter names, request bodies, HTTP status codes, response bodies, and error envelopes. +## Install Tempo API package -## Install the Tempo API package +Install the [`tapimo`](https://www.npmjs.com/package/tapimo) package with your package manager. -```bash [Terminal] +:::code-group +```bash [npm] +npm install tapimo +``` +```bash [pnpm] pnpm add tapimo ``` +```bash [bun] +bun add tapimo +``` +::: + +## Create typed Tempo API client -## Create a typed Tempo API client +Create a Tempo API client with `Client.create`. ```ts twoslash [client.ts] -// @noErrors import { Client } from 'tapimo' const client = Client.create({ @@ -35,10 +44,9 @@ const client = Client.create({ ## Make type-safe Tempo API requests -Requests are end-to-end type-safe across params, statuses, response bodies, and errors. For example, `GET /v1/tokens/:token` narrows the response body from the HTTP status: +Requests are end-to-end type-safe across parameters, response bodies, statuses, and errors. For example, `GET /v1/tokens/:token` narrows the response body from the HTTP status: ```ts twoslash [tokens.ts] -// @noErrors import { Client } from 'tapimo' const client = Client.create() @@ -49,36 +57,54 @@ const response = await client.v1.tokens[':token'].$get({ // Narrow responses by status. if (response.status !== 200) { - response.status - // ^? (property) status: 400 | 401 | 402 | 403 | 404 | 429 | 502 - + response.status // status is now typed to non-200 series + // ^? if (response.status === 404) { + // Handle 404 status specifically const json = await response.json() - // ^? const json: { error: { code: "token_not_found"; message: string; ... }; requestId: string } - json.error.code - // ^? (property) code: "token_not_found" + // ^? + json.error.code // which narrows to "token_not_found" + // ^? } else if (response.status === 402) { + // Handle the payment challenge from `WWW-Authenticate` const challenge = await response.text() - // ^? const challenge: string - // Handle the payment challenge from `WWW-Authenticate`. + // ^? } else { + // Handle validation, auth, rate-limit, or upstream errors const json = await response.json() - // ^? const json: { error: { code: "query_invalid" | "api_key_invalid" | "upstream_error" | ...; ... }; requestId: string } - // Handle validation, auth, rate-limit, or upstream errors. + // ^? } throw new Error('Request failed') } -response.status -// ^? (property) status: 200 +// Request is successfull +response.status // Response is narrowed to 200 +// ^? +// Get typed response body const token = await response.json() -// ^? const token: { address: Hex.Hex; currency: string; decimals: number; name: string; ... } +// ^? token.symbol -// ^? (property) symbol: string +// ^? +``` + +## Tempo API endpoint autocomplete + +Routes autocomplete directly on the client, so you can discover endpoints without leaving your editor: + +```ts twoslash [endpoints.ts] +// @noErrors +import { Client } from 'tapimo' + +const client = Client.create() + +const response1 = await client.v1. +// ^| +const response2 = await client.v1.transactions. +// ^| ``` -## Call Tempo JSON-RPC through the Typed Client +## Call Tempo JSON-RPC through Typed Client The Typed Client exposes the raw JSON-RPC passthrough as a typed route under `client.rpc`. The route key includes Hono's path pattern because the chain selector is optional: @@ -86,7 +112,7 @@ The Typed Client exposes the raw JSON-RPC passthrough as a typed route under `cl // @noErrors import { Client } from 'tapimo' -const client = Client.create({ apiKey: 'tempo_api_key' }) +const client = Client.create() const response = await client.rpc[':chain{mainnet|testnet|[0-9]+}?'].$post({ // Use `undefined` for `/rpc`, or pass `mainnet`, `testnet`, or a numeric chain id. @@ -105,13 +131,10 @@ if (response.status !== 200) { } const body = await response.json() - -if (!Array.isArray(body) && body.error) { +if (!Array.isArray(body) && body.error) throw new Error(body.error.message) -} const blockNumber = Array.isArray(body) ? body[0]?.result : body.result -// ^? const blockNumber: unknown ``` Chain selectors map to these hosted endpoints: @@ -123,7 +146,7 @@ Chain selectors map to these hosted endpoints: | `'testnet'` | `https://api.tempo.xyz/rpc/testnet` | | `'4217'` | `https://api.tempo.xyz/rpc/4217` | -## Send batch JSON-RPC requests through the Typed Client +### Send batch JSON-RPC requests The JSON-RPC body type accepts one request or an array of requests. The client narrows the success body to the same single-or-batch response union: @@ -147,15 +170,3 @@ if (response.status === 200) { } ``` -## Understand Typed Client RPC boundaries - -The Typed Client types the HTTP and JSON-RPC envelope around raw RPC calls: - -- the `/rpc`, `/rpc/mainnet`, `/rpc/testnet`, and `/rpc/:chainId` route shape -- the JSON-RPC request envelope (`jsonrpc`, `id`, `method`, and `params`) -- single-request and batch-request response envelopes -- non-200 Tempo API error envelopes and status-code narrowing - -The Typed Client does not type every JSON-RPC method's `params` and `result` fields. Those stay `unknown` because the passthrough accepts the full Ethereum JSON-RPC surface and Tempo-specific methods. Use [Viem](/docs/api/json-rpc#connect-to-tempo-json-rpc-with-tools) when you want method-level TypeScript types for common chain reads and writes. - -Continue with the [Tempo API reference](/docs/api) for endpoint-specific fields and the [JSON-RPC API](/docs/api/json-rpc) for RPC endpoint details. From a9e34c8f0c78093509c712e4899795c7e067b391 Mon Sep 17 00:00:00 2001 From: tmm Date: Thu, 2 Jul 2026 11:48:54 -0400 Subject: [PATCH 4/4] chore: up --- src/pages/docs/sdk/typescript/index.mdx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/pages/docs/sdk/typescript/index.mdx b/src/pages/docs/sdk/typescript/index.mdx index 4092f646..0bc82355 100644 --- a/src/pages/docs/sdk/typescript/index.mdx +++ b/src/pages/docs/sdk/typescript/index.mdx @@ -14,12 +14,6 @@ Tempo distributes TypeScript SDKs for: The Tempo extensions cover common chain operations such as querying state, sending Tempo Transactions, and managing tokens and AMM pools. -