A multi-aggregator DEX API for the Futarchy protocol that automatically discovers all DAOs, providing real-time pricing, volume, and trading data to CoinGecko, DexScreener, and other consumers.
Base URL: https://your-api-domain.com
Returns all DAO tickers with pricing, volume, and liquidity information. Automatically discovers all DAOs from the Futarchy protocol.
Response:
[
{
"ticker_id": "ZKFHiLAfAFMTcDAuCtjNW54VzpERvoe7PBF9mYgmeta_EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"base_currency": "ZKFHiLAfAFMTcDAuCtjNW54VzpERvoe7PBF9mYgmeta",
"target_currency": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"base_symbol": "ZKFG",
"base_name": "ZKFG",
"target_symbol": "USDC",
"target_name": "USD Coin",
"pool_id": "5FPGRzY9ArJFwY2Hp2y2eqMzVewyWCBox7esmpuZfCvE",
"last_price": "0.081340728222",
"base_volume": "30024.81040000",
"target_volume": "2441.23456789",
"liquidity_in_usd": "180138.45",
"bid": "0.080934024581",
"ask": "0.081747431863",
"high_24h": "0.085000000000",
"low_24h": "0.078000000000",
"startDate": "2025-01-15"
}
]Fields:
| Field | Description |
|---|---|
ticker_id |
Format {BASE_MINT}_{QUOTE_MINT} |
base_currency |
Base token mint address |
target_currency |
Quote token mint address (usually USDC) |
base_symbol / base_name |
Base token symbol and name (from on-chain metadata) |
target_symbol / target_name |
Quote token symbol and name |
pool_id |
DAO address |
last_price |
Current price (quote/base) from spot pool reserves |
base_volume / target_volume |
Rolling 24h trading volume |
liquidity_in_usd |
Total liquidity in USD |
bid / ask |
Spread-adjusted bid/ask prices |
high_24h / low_24h |
24h high/low (when available) |
startDate |
First trade date for the token |
Volume source: futarchy.user_pool_spot_ohlcv in the served ETL DB. If the
served DB is unavailable, the endpoint returns 503 instead of reporting zero volume.
Implements the DexScreener Adapter Spec v1.1. All endpoints are served under /dexscreener/. Requires DATABASE_PG_URL to be configured for the served DB.
Returns the latest Solana slot for which swap data is available.
Response:
{
"block": {
"blockNumber": 312345678,
"blockTimestamp": 1719500000
}
}Returns token metadata for a given Solana mint address. Fetched from on-chain Metaplex Token Metadata.
Response:
{
"asset": {
"id": "ZKFHiLAfAFMTcDAuCtjNW54VzpERvoe7PBF9mYgmeta",
"name": "ZKFG",
"symbol": "ZKFG",
"metadata": {
"decimals": "6"
}
}
}Returns immutable pair info for a DAO. Pair id is the dao_addr from the v0.6 indexer.
Response:
{
"pair": {
"id": "5FPGRzY9ArJFwY2Hp2y2eqMzVewyWCBox7esmpuZfCvE",
"dexKey": "futarchy",
"asset0Id": "ZKFHiLAfAFMTcDAuCtjNW54VzpERvoe7PBF9mYgmeta",
"asset1Id": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"feeBps": 50,
"createdAtBlockNumber": 290000000,
"createdAtBlockTimestamp": 1710000000,
"createdAtTxnId": "5xYz..."
}
}Returns swap events in the given Solana slot range (both inclusive). Events are queried from v0_6_spot_swaps in the external indexer DB.
Response:
{
"events": [
{
"block": { "blockNumber": 312345678, "blockTimestamp": 1719500000 },
"eventType": "swap",
"txnId": "5xYz...",
"txnIndex": 0,
"eventIndex": 0,
"maker": "UserWalletAddress...",
"pairId": "5FPGRzY9ArJFwY2Hp2y2eqMzVewyWCBox7esmpuZfCvE",
"asset1In": 100.5,
"asset0Out": 1234.56,
"priceNative": 0.08134
}
]
}Event mapping:
- Buy (user sends USDC → receives token):
asset1In+asset0Out - Sell (user sends token → receives USDC):
asset0In+asset1Out priceNative= price of asset0 (base token) in asset1 (USDC)- All amounts are decimalized (divided by
10^6)
Returns daily market data for the given date range, split by Futarchy AMM and Meteora sources.
Data source: both FutarchyAMM and Meteora data come from the unified
futarchy.user_pool_daily ETL table in the served DB. The served DB is a hard
dependency: if it's unreachable the endpoint returns 503 rather than reporting
a DB outage as zero volume.
- FutarchyAMM —
source = 'futarchy_amm', pivoted into spot, conditional, and total daily columns. ResponsefutarchyAMM.sourceis"etl-user-pool-daily". - Meteora —
source = 'meteora', with the same daily accounting contract. Responsemeteora.sourceis"etl-meteora-daily".
Returns complete supply breakdown with allocation details for launchpad tokens.
Returns total supply only (plain text number).
Returns circulating supply — total minus team performance package.
| Endpoint | Description |
|---|---|
GET /health |
Liveness: process status and uptime (no dependency checks) |
GET /api/health |
Readiness: served DB connectivity, ETL data contract, and data freshness |
GET /metrics |
Prometheus metrics (HTTP, served-DB health gauges, heartbeat) |
/api/health reports status: "degraded" (with a message) when the served DB
is unreachable, the served-data contract check fails, or the freshness query fails.
Heartbeat: a background self-check runs every HEARTBEAT_INTERVAL_MS (default
1 min). It verifies served-DB connectivity, alerts when the newest swap is older
than HEARTBEAT_MAX_DATA_AGE_SECONDS (default 6h — a stalled ETL with a healthy
connection is still an outage), and re-checks the data contract every 10th tick.
Failures push webhook alerts (with cooldowns) and update the
futarchy_served_db_connected / futarchy_served_contract_ok /
futarchy_served_data_age_seconds Prometheus gauges.
# Install dependencies
bun install
# Build the project
bun run build
# Start the server (runs build first)
bun run start
# Development with hot reload
bun run devCreate a .env file in the root directory (see example.env for reference):
| Variable | Description | Default |
|---|---|---|
| Solana | ||
SOLANA_RPC_URL / RPCPOOL_RPC_URL |
Solana RPC endpoint | https://api.mainnet-beta.solana.com |
SOLANA_WS_URL / RPCPOOL_WS_URL |
Solana WebSocket endpoint | wss://api.mainnet-beta.solana.com |
| Server | ||
PORT |
Server port | 3000 |
SERVER_REQUEST_TIMEOUT |
Request timeout (ms) | 300000 |
TRUST_PROXY_HOPS |
Reverse-proxy hops in front of the API (needed for per-IP rate limiting behind a LB) | 0 |
TRUSTED_API_KEYS |
Comma-separated allowlist of trusted partner keys | — |
TRUSTED_RATE_LIMIT_MAX |
Per-bucket request count per minute for trusted keys | 600 |
CACHE_TICKERS_TTL |
On-chain data cache TTL (ms) | 55000 |
| Served indexer DB (required — the only database this API uses) | ||
DATABASE_PG_URL |
Read-only connection to the served indexer DB (Meteora, tickers, DexScreener, first-trade-dates). Required — /api/market-data returns 503 without it. |
— |
DATABASE_PG_SSL |
Enable SSL (server cert verified against system CAs) | false |
DATABASE_PG_CA_CERT |
PEM CA cert content for private-CA verification | — |
DATABASE_PG_SSL_NO_VERIFY |
Explicit opt-out of TLS verification (stopgap only) | false |
| Heartbeat | ||
HEARTBEAT_INTERVAL_MS |
Background self-check cadence (0 disables) | 60000 |
HEARTBEAT_MAX_DATA_AGE_SECONDS |
Stale-data alert threshold (0 disables) | 21600 |
| Protocol | ||
PROTOCOL_FEE_RATE |
Protocol fee rate | 0.005 (0.5%) |
EXCLUDED_DAOS |
Comma-separated DAO addresses to exclude | — |
| Alerts | ||
ALERT_WEBHOOK_URL |
Telegram alert webhook URL | — |
ALERT_WEBHOOK_SECRET |
Webhook secret | — |
src/
├── app.ts # Express app setup & middleware
├── main.ts # API entry point (serves routes, no indexing workers)
├── runtime/
│ ├── services.ts # API service composition
│ └── heartbeat.ts # Background self-check (served DB, freshness, contract)
├── config.ts # Environment variables & configuration
├── routes/
│ ├── index.ts # Route registration
│ ├── coingecko.ts # GET /api/tickers
│ ├── dexscreener.ts # DexScreener adapter (4 endpoints)
│ ├── market.ts # GET /api/market-data (user_pool ETL)
│ ├── supply.ts # GET /api/supply/*
│ ├── health.ts # Liveness + readiness checks
│ ├── metrics.ts # Prometheus metrics
│ └── root.ts # GET / (API info)
├── services/
│ ├── futarchyService.ts # On-chain DAO/pool/token data
│ ├── priceService.ts # Price, spread, liquidity calculations
│ ├── externalDatabaseService.ts # Read-only served ETL DB connection (the only DB)
│ ├── solanaService.ts # SPL token supply queries
│ ├── launchpadService.ts # Token allocation breakdown
│ └── metricsService.ts # Prometheus counters/histograms
├── types/
│ ├── coingecko.ts # CoinGecko response types
│ └── dexscreener.ts # DexScreener response types
├── middleware/
│ ├── errorHandler.ts # Error handling & asyncHandler
│ └── requestId.ts # Request ID injection
└── utils/ # Logger, alerts, validation, scheduling
The API process serves market data from the read-only served ETL DB:
/api/tickersreads rolling 24h metrics fromfutarchy.user_pool_spot_ohlcv./api/market-datareads FutarchyAMM and Meteora daily rows fromfutarchy.user_pool_daily.- DexScreener routes read raw indexed v0.6 swap/DAO tables from the same served DB.
The served ETL DB is the only database this API connects to (the legacy app DB is fully removed). The API does not start indexing, fetchers, rollups, or any schema setup — it is serve-only.
The DexScreener adapter reads directly from the external indexer DB (v0_6_spot_swaps + v0_6_daos) and serves real-time swap events indexed by Solana slot. No intermediate aggregation — raw swap data mapped to the DexScreener schema.
- Anonymous (default): 60 requests per minute per IP. Returns
429 Too Many Requestswhen exceeded.- Behind a proxy/load balancer, set
TRUST_PROXY_HOPSto the real hop count — otherwise every anonymous client resolves to the proxy's IP and shares a single bucket.
- Behind a proxy/load balancer, set
- Trusted partners: 600 requests per minute per key (configurable via
TRUSTED_RATE_LIMIT_MAX). Send the issued key in theX-API-Keyheader. Each key has its own bucket — partners do not share quota. - Requests sent with an
X-API-Keyheader that does not match the server-side allowlist receive401 Unauthorizedwithcode: "INVALID_API_KEY". - Keys are issued out-of-band by the team. Contact us if you need elevated access.
{
"error": "Error message",
"code": "ERROR_CODE",
"requestId": "uuid"
}| Code | Description |
|---|---|
400 |
Bad Request (missing/invalid parameters) |
401 |
Unauthorized (invalid X-API-Key) |
404 |
Not Found |
429 |
Rate limit exceeded |
503 |
Service unavailable (DB not connected) |
500 |
Internal server error |
This project is licensed under the MIT License - see the LICENSE file for details.