Tempo API
Overview
The Tempo API is Tempo's next-generation data API. It is designed for
clients that need a high-level surface for exploring the Tempo chain.
Usage
The Tempo API is a composable Hono app. You can run your own
instance (Self-hosted) or call the hosted API directly (Hosted).
Hosted
Call the hosted Tempo API directly at https://api.tempo.xyz:
curl https://api.tempo.xyz/v1/tokens
const response = await fetch('https://api.tempo.xyz/v1/tokens/0x…')
const token = await response.json()
Explore the API interactively at the root /.
Self-hosted
Install the package:
pnpm i tapimo
Stateful features (webhook subscriptions, verified tokens) live in Postgres.
Provision a database and apply migrations with the CLI:
DATABASE_URL=postgresql://… tapimo admin migrate
The API is composed from route groups (apps) imported from tapimo/apps.
Create a base app with App.create and mount the apps you want:
import { data, routes } from 'tapimo/apps'
import { App, Db } from 'tapimo/server'
const app = App.create({
db: Db.postgres({ connectionString: process.env.DATABASE_URL }),
})
.route('/', data())
.route('/', routes())
data() | Chain and indexed data reads | Blocks, transactions, tokens, webhooks |
routes() | Asset routing | Quotes, chains, providers, deposits, transfers |
relay() | Wallet relay & fee sponsorship | Relay, sponsor |
management() | Tenancy and access management | Orgs, projects, members, API keys |
The composed app exposes a Web fetch method, and App.listener adapts it
to a Node.js request listener, so you can serve it from any runtime or
framework:
createServer(App.listener(app))
Bun.serve({ fetch: app.fetch })
Deno.serve({ fetch: app.fetch })
server.all('*', (c) => app.fetch(c.request))
server.use(App.listener(app))
server.use((c) => app.fetch(c.req.raw))
export const GET = app.fetch
export const POST = app.fetch
Typed Client
Use the typed client for endpoint autocomplete and API-key/header configuration:
import { Client } from 'tapimo'
const client = Client.create({
apiKey: 'tempo_api_key',
url: 'https://api.tempo.xyz',
headers: { authorization: 'Bearer tempo_api_key' },
})
Requests are end-to-end type-safe across params, statuses, response bodies, and errors. For example, GET /v1/tokens/:token:
const response = await client.v1.tokens[':token'].$get({
param: { token: '0x20c0000000000000000000000000000000000000' },
})
if (response.status !== 200) {
response.status
if (response.status === 404) {
const json = await response.json()
json.error.code
} else if (response.status === 402) {
const challenge = await response.text()
} else {
const json = await response.json()
}
throw new Error('Request failed')
}
response.status
const token = await response.json()
token.symbol
Development
Local API with local Horcrux
Clone the Horcrux backend as a sibling checkout (the checkout must include the
horcrux dev local-signing backend), then prepare this checkout:
git clone https://github.com/tempoxyz/horcrux.git ../horcrux
pnpm setup:env
Set these values in the ignored repo-root .env:
HORCRUX_URL=http://127.0.0.1:3040
HORCRUX_ACCESS_CLIENT_ID=
HORCRUX_ACCESS_CLIENT_SECRET=
HORCRUX_ROUTES_TRANSFER_SUBSIDY_WALLETS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
ROUTES_SUBSIDY_MAX_AMOUNT=0.10
Start the opt-in stack with docker compose --profile subsidies up -d --build.
Set TEMPO_API_HORCRUX_SOURCE if the sibling checkout is elsewhere. Compose mounts
the public seeds/horcrux-wallets.json fixture read-only and starts horcrux dev
with HORCRUX_DEV_WALLETS=/etc/horcrux/wallets.json. To customize the preinitialized
wallets, copy the manifest, preserve its schema, use stable UUIDs, and set
TEMPO_API_HORCRUX_WALLETS to that host path. Horcrux atomically initializes the
fixed development team/grant and refuses metadata, key, ID, address, or grant
conflicts. It preserves wallets, signed history, and replay state on restart; do not
delete its database volume as a routine reinitialization step.
The fixture contains EVM, Tron, and Solana wallets and uses only publicly compromised
developer keys. Never use these keys for real credentials or send real funds to any
derived address. The EVM wallet is public Anvil account 0. API route subsidies still
use only that EVM address through the normal Horcrux viem SDK; local Horcrux does not
add API-managed Tron or Solana subsidies. Direct Horcrux HTTP clients identify all
wallet families by the manifest UUID, while the viem integration can look up its EVM
wallet by address. The dev backend exposes the normal EVM/Tempo, Solana, Tron, and
hash signing APIs without policies or broadcasting.
Horcrux shares the API container's network namespace and listens only on loopback;
unauthenticated SDK access is accepted only by an API with
ENVIRONMENT=development, a loopback HTTP(S) URL, and both Cloudflare credentials
absent. After changing .env or the manifest,
run docker compose restart api horcrux. This profile starts a signer only: it does
not start bridges, Tron, Solana, Base/Ethereum forks, or other chains. Point the
relevant RPC URLs at disposable nodes yourself. Signing locally does not change
where the API broadcasts. Fund the EVM address with disposable destination tokens
and fee funds on the tested chain only when exercising settlement.
ROUTES_SUBSIDY_MAX_AMOUNT is a per-subsidy cap, not a total spending budget.
Developers not exercising Routes subsidies can leave
HORCRUX_ROUTES_TRANSFER_SUBSIDY_WALLETS empty and run the API without Horcrux
access. Fee sponsorship keeps its existing setup; there is no private-key fallback
for Routes subsidies.
Hosted Routes subsidy wallets
Set HORCRUX_ROUTES_TRANSFER_SUBSIDY_WALLETS to a comma-separated list of EVM
wallet addresses registered in Horcrux. The first address funds new commitments;
retain retiring addresses until their existing commitments settle. Address-based
horcruxAccount construction is synchronous and requires no discovery request.
These wallets need the destination token and transaction fee funds on each
supported destination chain. Fee sponsorship, MPP settlement, and rewards signing
remain separate.
The first EVM wallet also signs inbound Tempo deposit subsidies through Horcrux
sign-hash. Fund it with pathUSD and its selected fee token on Tempo. Transfer
reservations do not count deposit top-ups, so monitor the shared Tempo balance for
both obligations. Relay fixed-rate deposit addresses use Relay's 1:1 subsidy.
The SDK is pinned to @zygis-tempo/horcrux-viem@0.1.0 on npm, aliased as
@horcrux/viem. The SDK owns the Horcrux signing protocol; the API still owns
inventory checks, nonce reservations, transaction persistence, and broadcast.
For production, apps/api/wrangler.background.jsonc owns HORCRUX_URL and
HORCRUX_ROUTES_TRANSFER_SUBSIDY_WALLETS. CI and pnpm -C apps/api deploy run
apps/api/scripts/prepare:production.ts to copy only those values into the HTTP
Worker's generated wrangler.deploy.jsonc. The shared wrangler.jsonc has no
production Horcrux defaults, so local development and previews cannot inherit them.
The HTTP deployment keeps queue producers but leaves consumers, cron, and the
reward signer binding on api-background.
Keep HORCRUX_ACCESS_CLIENT_ID and HORCRUX_ACCESS_CLIENT_SECRET as Cloudflare
secrets on both production Workers before rollout. They are not stored in Wrangler
configuration or read from GitHub Actions. If the URL or wallet list was previously
stored as a Cloudflare secret, remove that secret binding before deploying the
same name as a plain-text Wrangler variable.
For PR and main-tracking previews, configure these once in the API repository's
GitHub Actions settings. CI uploads them to each preview Worker under the same
names without the _DEV suffix; no manual Worker configuration is needed.
| Secret or variable | HORCRUX_URL_DEV | Cloudflare-accessible runtime API base URL of the Horcrux instance containing the dev wallet (not its admin URL) |
| Secret or variable | HORCRUX_ROUTES_TRANSFER_SUBSIDY_WALLETS_DEV | Comma-separated EVM wallet addresses, active first, not UUIDs |
| Secret or variable | HORCRUX_ROUTES_TRANSFER_SUBSIDY_TRON_WALLETS_DEV | Comma-separated Tron address and Horcrux UUID pairs, active first |
| Secret | HORCRUX_ACCESS_CLIENT_ID_DEV | Dev Cloudflare Access client ID |
| Secret | HORCRUX_ACCESS_CLIENT_SECRET_DEV | Dev Cloudflare Access client secret |
Secrets take precedence when both a secret and variable have the same name.
The credential pair must be complete or absent. Setting a wallet list requires
the URL and both credentials. Removing both wallet settings disables transfer
subsidies on the next preview deployment; drain pending commitments first.
Absent credentials leave existing Worker credentials unchanged. Use only dev
wallets and an identity authorized for those wallets. CI's disposable local signer
needs none of these settings.
Grant the workload access to each subsidy wallet and permit its destination-chain
ERC-20 transfers. ROUTES_SUBSIDY_MAX_AMOUNT remains the operational USD limit.
An empty wallet list disables transfer subsidies; enabled subsidies with incomplete
Horcrux credentials fail closed. There is no private-key fallback.
For Tempo USDT0 to Tron USDT, set HORCRUX_ROUTES_TRANSFER_SUBSIDY_TRON_WALLETS
to comma-separated TronAddress:HorcruxWalletUUID pairs, active first. Fund those
wallets with Tron USDT and TRX. Set ROUTES_TRON_SUBSIDY_FEE_LIMIT_SUN to the
maximum native fee reserved for each TRC-20 transfer. The API persists Tron
transaction bytes before Horcrux signing and reuses the same signature and
transaction on retries. Keep retiring wallets configured until their pending
transfers settle. The production wallet mapping belongs in
apps/api/wrangler.background.jsonc; the deploy script copies it into the HTTP
Worker when present. An empty mapping leaves Tron transfer subsidies disabled.
This replaces ROUTES_TRANSFER_SUBSIDY_ACCOUNT and
ROUTES_TRANSFER_SUBSIDY_PRIVATE_KEY. For migration, register the existing wallets
in Horcrux or drain their unsigned commitments before switching addresses. After
cutover, remove the unused private-key secrets from Cloudflare and GitHub;
deployment workflows no longer upload them. Persisted signed transactions still
replay through the existing nonce and settlement records. Token top-up billing
is unchanged; destination gas remains paid by Tempo.
Run the configuration tests with disposable local infrastructure:
pnpm test:local apps/api/src/RoutesTransferSubsidies.test.ts --run
Quickprompt
Paste into your agent:
Read https://github.com/tempoxyz/api/blob/main/README.md, then spin up the
Tempo API container and provide me links to the services.
Prerequisites
- OrbStack: runs the Docker Compose stack and serves the
*.tempo.local domains
- pnpm
Commands
pnpm install
pnpm dev
pnpm test
pnpm test:local
Services
pnpm dev spins up:
Reward campaign administration is available locally, but scheduled settlement is production-only;
the development stack intentionally has no production signer or reviewed factory binding.
api | Tempo API | http://api.tempo.local |
console | Developer Console | http://console.tempo.local |
postgres | Database | localhost:5432 |
tempo | Tempo Node | http://rpc.tempo.local:8545 |
tidx | Indexer | http://tidx.tempo.local:8080 |
stripe | Stripe CLI (Webhook Forwarder) | - |