cardano-api/README.md
Cobb Hayes aa8879bc69 Public-flip audit: drop audit-ticket prefixes + topology refs + AI scaffolding
cardano-api: strip 'Fix #N:' audit-ticket prefixes from inline comments (was
50+ in main.py), drop hardening-pass changelog blocks from module docstring,
rewrite README to drop deploy paths + marketing sections, keep tier/auth/TTL
+ policy IDs.

cardano-checkout-py: drop TradeCraft lineage refs, swap chromaticcraft/tradecraft
test fixtures for acme/globex, repository URL → git.sulkta.com.
2026-05-27 11:15:02 -07:00

148 lines
4.5 KiB
Markdown

# cardano-api
REST API over cardano-db-sync + cardano-node. FastAPI + asyncpg + Redis.
Read paths hit the db-sync Postgres. UTxO queries, protocol params, and tx submit shell out to `cardano-cli` against a local node socket. Auth is TRP token-gated via CIP-8 wallet signatures.
## Stack
- FastAPI / uvicorn
- asyncpg → cardano-db-sync Postgres (`cexplorer`)
- redis.asyncio → rate limiting + response cache + API-key storage
- cardano-cli (baked into the image) → node socket queries + tx submit
- pycardano + PyNaCl + cbor2 → CIP-8 verification
## Run
```
docker compose up -d --build
```
Listens on `:8765` inside the container. Wire it to whatever proxy / port-forward the deploy wants.
## Tiers + rate limits
| Tier | TRP needed | Rate (req/min) | tx submit (per min) | Node read | Node submit |
|---|---|---|---|---|---|
| anonymous | 0 | 20 | 0 | no | no |
| standard | ≥50 | 100 | 2 | yes | no |
| elevated | ≥500 | 1000 | 10 | yes | yes |
| master | n/a | unlimited | unlimited | yes | yes |
Anonymous is rate-limited per source IP. Authed tiers are rate-limited per key.
TRP-gated keys expire 48h after issue and must be re-auth'd. A background task re-checks balances every 10 minutes and re-tiers in place.
## Auth flow (TRP-gated)
```
POST /v1/auth/challenge { "address": "addr1..." }
→ { "nonce": "...", "expires_at": "..." }
# sign nonce with the wallet via CIP-8
POST /v1/auth/verify { "address", "nonce", "signature", "key" }
→ { "api_key": "capi_...", "tier", "trp_balance" }
POST /v1/auth/refresh (X-API-Key header — self-service only)
→ { "tier", "trp_balance", "expires_at", ... }
```
Master key is issued out-of-band via `API_MASTER_KEY` env. Master-key-created keys (`/admin/keys`) don't expire.
Header is preferred (`X-API-Key: capi_...`); `?api_key=` works too.
## Endpoints
```
GET /health
GET /v1/sync/status
GET /v1/block/latest
GET /v1/block/{block_no}
GET /v1/address/{address}/balance
GET /v1/address/{address}/tokens?page=&limit=
GET /v1/address/{address}/transactions?page=&limit=&order=
GET /v1/address/{address}/utxos (standard+)
GET /v1/tx/{tx_hash}
POST /v1/tx/submit (elevated+)
GET /v1/asset/{policy_id}/info?page=&limit=
GET /v1/asset/{policy_id}/{asset_name}/holders?limit=
GET /v1/pool/{pool_id}/info
GET /v1/protocol-params (standard+)
POST /v1/auth/challenge
POST /v1/auth/verify
POST /v1/auth/refresh
POST /admin/keys (master)
DELETE /admin/keys/{key} (master)
GET /admin/keys (master)
POST /admin/refresh-tiers (master)
GET /admin/stats (master)
```
## Cache TTLs (Redis)
```
balance 60s
tokens 60s
transactions 30s
block_latest 10s
tx_details 300s (immutable)
asset_info 120s
pool_info 120s
sync_status 5s
protocol_params 300s
utxos 10s
```
## Validation
Inputs hit regex gates before any DB query:
- bech32 mainnet/testnet addresses (`addr1...` / `addr_test1...`)
- 64-hex tx hashes
- 56-hex policy IDs
Tx submit body is capped at 64 KB (middleware reads the actual stream; chunked transfer can't bypass).
CIP-8 verification rejects non-EdDSA (`alg ≠ -8`), wrong key length, empty payload, payload-not-nonce, and bad key→address hash binding.
## Key storage
Keys are stored as `sha256(key)`. The raw key is returned exactly once at issue. Lookups, admin listing, and revoke all operate on the hash.
TRP-gated keys are tracked in a `trp_gated_keys` Redis set so the refresh task can batch a single Postgres query for all owner addresses instead of N+1.
## Known policy IDs
```
TRP 9c4bd4a90cdb73d9ff681215ecf7dea9fb183d916d30487d17098e05
MAP 24bd9e7b9ae3a61df79eca72fd8355d0f7767e4c55a04a0d919c019c
```
## Environment
```
DB_HOST (default: postgres-dbsync — compose service name)
DB_PORT 5432
DB_NAME cexplorer
DB_USER dbsync
DB_PASS
REDIS_HOST (default: redis-api — compose service name)
REDIS_PORT 6379
API_MASTER_KEY unrestricted-tier key
CARDANO_NODE_SOCKET_PATH /node-ipc/node.socket
CARDANO_NETWORK mainnet
```
`TRUSTED_PROXIES` (in `main.py`) is the set of IPs whose `X-Forwarded-For` header is honoured. Defaults to loopback + the docker default bridges. If the deployment fronts the API with a different proxy, override the set.