A read-only HTTP cache for on-chain data stored on Solana via IQ Labs. Fetches data from the blockchain and serves it over HTTP with multi-tier caching. Anyone can run their own gateway.
- No single point of failure - if one gateway goes down, spin up another
- Your own cache - faster for your users, your region
- Data is always recoverable - everything lives on Solana, any gateway can serve it
- No vendor lock-in - switch gateways anytime, same data
git clone https://github.com/IQCoreTeam/iq-gateway.git
cd iq-gateway
bun install
cp .env.example .envEdit .env:
SOLANA_CLUSTER=mainnet-beta
SOLANA_RPC_ENDPOINT=https://api.mainnet-beta.solana.com
PORT=3000
Run:
bun run devThat's it. Your gateway is live at http://localhost:3000.
| Variable | Required | Description |
|---|---|---|
SOLANA_CLUSTER |
Yes | devnet, mainnet-beta, or testnet |
SOLANA_RPC_ENDPOINT |
Yes | Solana RPC URL (must match cluster) |
HELIUS_API_KEY |
No | Helius API key for faster reads (paid plan enables gTFA + batch) |
HELIUS_API_KEYS |
No | Comma-separated Helius keys for 429 fallback (overrides HELIUS_API_KEY) |
BACKFILL_FROM_SLOT |
No | Start slot for historical backfill (requires paid Helius). Set to 398615411 for full IQ Labs history |
PORT |
No | Server port (default: 3000) |
BASE_PATH |
No | URL prefix if behind reverse proxy |
MAX_CACHE_SIZE |
No | Max disk cache before cleanup (default: 10GB) |
| Endpoint | Description |
|---|---|
GET /meta/{sig}.json |
Metaplex-compatible JSON metadata |
GET /img/{sig}.png |
Raw image/file bytes |
GET /data/{sig} |
Raw asset data |
GET /view/{sig} |
Rendered HTML view of text inscriptions |
GET /render/{sig} |
PNG/SVG render of text inscriptions |
| Endpoint | Description |
|---|---|
GET /table/{pda}/rows |
Read table rows with pagination |
GET /table/{pda}/index |
Full signature index for a table |
GET /table/{pda}/slice |
Read specific rows by signature |
GET /table/{pda}/meta |
Table metadata (name, columns, gate config) |
POST /table/{pda}/notify |
Notify about a new tx for instant cache injection |
GET /table/dbroot |
DB root info (tables, creators) |
GET /table/cache/stats |
Cache statistics |
| Endpoint | Description |
|---|---|
GET /user/{pubkey}/assets |
List assets uploaded by a wallet |
GET /user/{pubkey}/sessions |
List user sessions |
GET /user/{pubkey}/profile |
User profile data |
GET /user/{pubkey}/state |
User state |
GET /user/{pubkey}/state |
Raw user state account |
GET /user/{pubkey}/connections |
User connections |
| Endpoint | Description |
|---|---|
GET /site/{manifestSig} |
Serve a website from Solana (index.html) |
GET /site/{manifestSig}/{path} |
Serve any file from an on-chain manifest |
Supports both Iqoogle and gateway manifest formats. SPA fallback for unknown paths. Root-relative asset requests (e.g. /logo.webp) are resolved against the active manifest.
Deploy a site:
bun run scripts/deploy-site.ts ./my-site ./keypair.json| Endpoint | Description |
|---|---|
GET /health |
Health check + cache stats |
GET /version |
Server version info |
Three-tier cache with different TTLs:
| Layer | TTL | Purpose |
|---|---|---|
| Memory (LRU) | 60s head, 5min other | Fast reads |
| Disk (SQLite) | 5min rows, 24h immutable | Persistent across restarts |
| Chain (Solana) | Permanent | Source of truth |
Individual rows are cached for 24 hours (on-chain data is immutable). Head page responses are cached for 60 seconds with throttled background refresh.
With a paid Helius plan (HELIUS_API_KEY), the gateway automatically uses:
- gTFA (
getTransactionsForAddress) — 100 full transactions per call, ~100x faster reads for session files - Batch JSON-RPC — multiple
getTransactioncalls in one POST for table row reads - Backfill — pre-cache all historical IQ Labs transactions on startup (set
BACKFILL_FROM_SLOT)
Without Helius, everything still works using standard Solana RPC — just slower for large files.
- Build and run with Docker:
docker build -t iq-gateway .
docker run -d \
-p 3000:3000 \
-v iq-cache:/app/cache \
--env-file .env \
--restart unless-stopped \
iq-gateway- Put it behind a reverse proxy (nginx, caddy, etc.) for SSL:
server {
listen 443 ssl;
server_name gateway.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/gateway.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/gateway.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}- Point DNS:
Type: A
Name: gateway
Value: <your-server-ip>
- Create an SDL file (
deploy.yaml):
---
version: "2.0"
services:
gateway:
image: ghcr.io/iqcoreteam/iq-gateway:latest
expose:
- port: 3000
as: 80
accept:
- gateway.yourdomain.com
to:
- global: true
env:
- SOLANA_CLUSTER=mainnet-beta
- SOLANA_RPC_ENDPOINT=https://api.mainnet-beta.solana.com
- PORT=3000
params:
storage:
cache:
mount: /app/cache
readOnly: false
profiles:
compute:
gateway:
resources:
cpu:
units: 2
memory:
size: 2Gi
storage:
- size: 1Gi
- name: cache
size: 10Gi
attributes:
persistent: true
class: beta3
placement:
dcloud:
pricing:
gateway:
denom: uakt
amount: 100000
deployment:
gateway:
dcloud:
profile: gateway
count: 1-
Deploy via Akash Console or CLI
-
Point DNS to the ingress URI Akash gives you:
Type: CNAME
Name: gateway
Value: <your-deployment>.ingress.akashprovid.com
The accept field in the SDL tells the Akash provider to route traffic for your domain to the container.
The gateway is a read-only cache layer. It never writes to Solana. All data is public and recoverable from chain. Multiple gateways can serve the same data independently.