Indexer: deploy & scanning in production
The @p15/indexer is a dev/ops convenience: it polls the one fixed announce
anchor, extracts every encrypted p15 memo, and serves them over HTTP so wallets
scan their inbox in a single request instead of N getTransaction RPC calls.
It never sees plaintext — ciphertext + the public 1-byte view tag only.
Decryption stays client-side with the recipient's scan key.
API contract
Types live in indexer/src/types.ts (the source of truth, imported by the
handlers):
GET /announcements?before=<sig>&until=<sig>&limit=200→AnnouncementsPage({ items: AnnouncementRow[], nextBefore: string | null }), newest-first.GET /health→HealthResponse({ ok, rows, rpc }).
The dapp/SDK scanner consumes this via ScanOptions.indexerUrl
(NEXT_PUBLIC_INDEXER_URL).
Env
| var | default | meaning |
|---|---|---|
SOLANA_RPC | https://api.devnet.solana.com | cluster to poll |
PORT | 8787 | HTTP port |
DATA_FILE | ./announcements.json | JSON persistence path |
POLL_MS | 10000 | poll interval (ms) |
Docker
@p15/stealth is a sibling consumed via file:, which Docker can't see outside
the build context — so build from the outer repo root, and build the stealth
dist first:
cd protocol-15
(cd packages/stealth && npm ci && npm run build)
docker build -f indexer/Dockerfile -t p15-indexer .
docker run -p 8787:8787 -v p15-index-data:/data \
-e SOLANA_RPC=https://api.devnet.solana.com p15-indexer
curl localhost:8787/healthThe JSON store persists on the /data volume, so restarts resume the backfill
instead of starting over.
Deploy (Railway / Fly)
- Railway: point at the repo, set the Dockerfile path to
indexer/Dockerfileand the build context to the repo root. Add a persistent volume mounted at/data. SetSOLANA_RPC(a paid RPC is strongly recommended — the public devnet RPC 429s hard during the initial backfill; the poller already retries with exponential backoff but a real endpoint is far faster). - Fly:
fly launch --dockerfile indexer/Dockerfilefrom the repo root, add a volume for/data, set the same env. HealthcheckGET /health.
RPC fallback (no indexer)
The scanner works without an indexer: when indexerUrl is unset,
scanAnnouncementsPage RPC-scans the anchor directly
(getSignaturesForAddress + per-tx getTransaction). Tradeoff:
- With indexer: one HTTP request per page; view tags pre-extracted. Best for wallets with shared infra.
- Without: N
getTransactioncalls per page, rate-limit sensitive on public RPC. Fine for low-volume or self-hosted single-user scanning.
Use the indexer when many clients scan the same anchor or when on a public RPC; skip it for one-off local scans.