injunDave / commonAPI

FatCat common API

The only public surface for tokenized stocks. Built for a crowd: stocksUI, bots, and many keys at once. The indexer never serves HTTP. Each client is isolated by key. Markets are coalesced so a stampede is one Carbon read, not one per caller.

Clients

Never receive Redis or Postgres URLs
stocksUIREST, WebSocket, gRPC-Web
BotsgRPC and REST. OAuth client_credentials
MCP agentsJSON-RPC POST /mcp, same keys
TLS load balancer · N API replicas

commonAPI — public surface

AuthRegistry · rate limits · no Jupiter
REST /v1markets, portfolio, quote
WebSocketswaps, pools, gated compliance
gRPCnative + gRPC-Web
MCPtools wrap the same reads
AuthAPI key → JWT → OIDC hook. Per-key quotas
Markets cacheSingleflight. One Carbon trip per stampede
shared-txCarbon quote. Send is live_disabled
read-only SQL · Redis SUBSCRIBE · dry-run RPC

Carbon Postgres

source of truth
sqlx READ pooltoken_mints, pool_prices_latest, holdings, compliance_events. SELECT only.

Redis

fan-out only
event:token_lifecyclePUBLISH / SETEX. No Lua. API is the authorizer.

Solana RPC

not live yet
shared-tx sendAlways 403 live_disabled. Quotes never hit Jupiter.
indexer writes · never HTTP to the world

Indexer — private

Phase 1 OPENx on main
YellowstonegRPC stream of accounts and txs
indexer-coreToken-2022 TLV, DEX, OPENx
sinkPostgres first, then Redis
OPENx mintcompliance events + lifecycle

Public, authenticated Private writer Postgres is truth. Redis never blocks ingest.

Liveness

checking

Auth

API keys plus OAuth2 client_credentials. Handlers take ApiIdentity, not a mode switch.

Execution

dry-run POST /v1/tx/send is 403 live_disabled.

Read pool

sqlx connections set default_transaction_read_only and statement timeouts.

Scale

Stateless REST. N replicas. Each one subscribes to Redis itself. No sticky sessions.

Many clients

Per-key quotas. REST, gRPC, and MCP. Markets cache so Carbon is not stampeded.

MCP

Agents POST JSON-RPC to /mcp. Same keys as REST. Tools wrap markets, portfolio, quote.

Security controls

ControlPosture
AuthAPI keys and OAuth2 client_credentials (JWT). REST rejects query-string keys. OIDC/Privy is a fail-closed hook.
REST credentialsX-Api-Key or Authorization: Bearer only. ?api_key= is 401.
WebSocketHeader preferred; query fallback for handshake. Origin allow-list, idle timeout, max connections, max frame size. Binary frames closed.
ScopesFree cannot read compliance REST or subscribe to compliance / lifecycle. Market detail omits flags for free.
Rate limitsPer-IP (TCP peer; X-Forwarded-For only if TRUST_PROXY), per-key, global cap. 429 + Retry-After.
CORSExact origins in production. No wildcard, no credentialed *, no mirrored methods/headers.
InputsSolana pubkeys (32-byte base58). Amounts are positive u128 decimals. Redis keys interpolated only after validation.
ErrorsInternal messages logged, never returned. Health does not leak config.
Headersnosniff, DENY framing, no-referrer, no-store, CSP with frame-ancestors none.

Horizontal scale

LayerSetup
ReplicasRun N identical processes behind one TLS load balancer. REST is stateless; any replica can serve any call.
Probes/health = process up (liveness). /ready = Carbon reachable (readiness). Drain a replica on 503.
WebSocketEach replica SUBSCRIBEs event:token_lifecycle and fans out to its own clients. Sticky sessions are not required.
PostgresRead replica + SELECT-only role. Connection budget is replicas × DATABASE_MAX_CONNECTIONS. Markets is two queries, not 1+N.
RedisOne subscriber per replica. Indexer publishes. Clients never receive the URL. No new channel names, no Lua.
Rate limitsGovernor is in-process (per replica). Put the global cap at the edge. TRUST_PROXY only behind a proxy you own.
Do notDo not expose the indexer. Do not share Redis with browsers. Do not raise the DB pool without checking indexer headroom.
StampedeAuthenticated GET /v1/markets is coalesced per replica (default 750ms). A burst of callers shares one catalog page load. Market detail is not cached — premium flags stay gated.

Live modules

Same-origin GET /v1. Many clients share this catalog; new endpoints register here.

modules
checking…

Live response headers

Same-origin probe of GET /health.

status
checking…

REST (v1)

MethodPathNotes
GET/openapi.jsonOpenAPI 3.1. Public. No secrets
GET/metricsPrometheus. Public. No fingerprints
POST/mcpMCP Streamable HTTP. Auth. JSON-RPC tools
GET/v1Public module catalog
POST/v1/oauth/tokenOAuth2 client_credentials → Bearer JWT
GET/healthPublic liveness, no config leak
GET/readyPostgres ping
GET/v1/marketsAuth. Paged active catalog with captured mint metadata and optional pool prices
GET/v1/markets/{mint}Auth. compliance flags premium-only
GET/v1/portfolio/{wallet}Auth. validated pubkey
GET/v1/multiplier/{mint}Auth. Redis SETEX then Carbon
GET/v1/compliance/eventsPremium scope
POST/v1/tx/quoteshared-tx, Carbon pool state
POST/v1/tx/send403 live_disabled
gRPC/fatcat.v1.FatCatNative + gRPC-Web. Same auth metadata (x-api-key / Bearer)
WS/v1/wsswaps, pool_updates, compliance, lifecycle

Local development keys are disabled in production. Send the header, never the query string:

curl -s -H 'X-Api-Key: ' ORIGIN/v1/markets