Soroban incoming token transfer indexer — fills the gap that Horizon leaves open.
Horizon indexes Classic Stellar operations (payments, path payments) but does not index Soroban transfer events by recipient address. Wraith polls Stellar RPC getEvents, parses CAP-67/SEP-41 token events (transfer, mint, burn, clawback), stores them in Postgres, and exposes a REST API to query by address.
Stellar RPC getEvents (polling loop)
↓
Parser (ScVal decoding, CAP-67 normalisation)
↓
Postgres (Prisma ORM — indexed by toAddress, fromAddress, contractId, ledger)
↓
Express REST API (GET /transfers/incoming/:address, etc.)
git clone <repo>
cd wraith
npm install
npx prisma generatecp .env.example .envEdit .env:
DATABASE_URL="postgresql://wraith:wraith@localhost:5432/wraith"
STELLAR_RPC_URL="https://soroban-testnet.stellar.org"
# Leave blank to start from the chain tip (recommended for first run)
START_LEDGER=
# Comma-separated contract IDs to watch, or leave empty for all contracts
CONTRACT_IDS=
PORT=3000docker-compose up -d dbnpx prisma migrate dev --name init# Development (hot reload)
npm run dev
# Production
npm run build && npm startOr run everything via Docker:
docker-compose up --buildBase URL: http://localhost:3000
Indexer health — current ledger, network tip, lag, uptime.
curl http://localhost:3000/status{
"ok": true,
"lastIndexedLedger": 5842100,
"latestLedger": 5842102,
"lagLedgers": 2,
"startedAt": "2025-10-01T10:00:00.000Z",
"uptimeSeconds": 3600,
"totalIndexed": 12430
}All token transfers received by an address.
| Param | Type | Description |
|---|---|---|
contractId |
string | Filter to a specific token contract (C...) |
fromLedger |
int | Inclusive lower ledger bound |
toLedger |
int | Inclusive upper ledger bound |
limit |
int | Page size (max 200, default 50) |
offset |
int | Pagination offset |
# All incoming transfers for an address
curl "http://localhost:3000/transfers/incoming/GABC123..."
# Filter to a specific token, last 1000 ledgers
curl "http://localhost:3000/transfers/incoming/GABC123...?contractId=CTOKEN...&fromLedger=5840000&limit=20"All token transfers sent by an address. Same query params as /incoming.
curl "http://localhost:3000/transfers/outgoing/GABC123..."All token events emitted within a transaction.
curl "http://localhost:3000/transfers/tx/abcdef1234567890..."| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
— | Postgres connection string (required) |
STELLAR_RPC_URL |
— | Stellar RPC endpoint (required) |
START_LEDGER |
(tip) | Ledger to start indexing from. Leave blank to resume from DB state, or start near the tip. |
POLL_INTERVAL_MS |
6000 |
Polling interval (~1 ledger ≈ 6 s) |
CONTRACT_IDS |
(all) | Comma-separated token contract IDs to watch. Empty = watch all (heavy on mainnet) |
EVENTS_BATCH_SIZE |
10000 |
Max events per RPC call (RPC hard-cap is 10 000) |
PORT |
3000 |
REST API port |
| Network | URL |
|---|---|
| Mainnet | https://mainnet.sorobanrpc.com |
| Testnet | https://soroban-testnet.stellar.org |
| Futurenet | https://rpc-futurenet.stellar.org |
Important: Stellar RPC retains ~7 days of event history. For longer historical coverage, use Galexie + the Token Transfer Processor.
| Type | fromAddress |
toAddress |
Context |
|---|---|---|---|
transfer |
✅ sender | ✅ recipient | Standard SEP-41 token transfer |
mint |
null | ✅ recipient | New tokens minted to an address |
burn |
✅ holder | null | Tokens burned from an address |
clawback |
✅ holder | null | Tokens clawed back by admin |
From the CAP-67 discussion, SDF's stated position:
"We've made that mistake before with Horizon, by solving all indexing problems at the Horizon layer which encouraged folks to build on Horizon rather than innovate on new and or better data sources."
Wraith is the third-party solution that SDF's architecture intentionally encourages.