Real-time monitoring, alerting, and on-chain health checks for Soroban smart contracts on Stellar. The only observability tool in the Stellar ecosystem with a deployed Soroban watchdog contract for proactive contract monitoring.
Soroban's public RPC retains events for 24 hours and transaction data for up to 7 days. There is no persistent index of what your contract did, what it cost, or which storage entries are close to expiring. The Stellar Lab contract explorer is excellent for point-in-time inspection but has no history, no API, and no way to know when a contract you depend on is unhealthy. Sorolens fills that gap.
- Contract event indexing: every event your contract emits, with decoded topics and values, indexed into Postgres and queryable via REST.
- Invocation tracing: per-transaction CPU instructions, memory, ledger I/O bytes, and fees charged.
- Storage tracking: snapshot of every temporary, persistent, and instance storage entry, with TTL health.
- Watchdog monitoring (new): the on-chain
sorolens-watchdogSoroban contract lets any contract owner register a contract, push health status, and raise alerts. Sorolens indexes those events and materialises them into a dashboard, giving you the only proactive-monitoring path on Stellar without a centralised heartbeat service. - Alerting: when persistent storage entries are within a configurable number of ledgers of expiry, or when a monitored contract goes
Unresponsive. - Custom alert rules: a small expression language lets each team encode its own SLOs without a code change —
fee_per_invocation > 0.5 XLM for 5m on network testnet. Rules are validated on save, editable with syntax highlighting and a live preview in the dashboard, and evaluated by the indexer every pass. Seedocs/rule-language.md. - Multi-network: track and query testnet, mainnet, and futurenet contracts side by side, with a network selector in the dashboard header and a
?network=filter on every list endpoint. - Snapshot / replay:
GET /api/v1/contracts/:id/snapshot?ledger=Nreplays a contract's storage state and last known event as of any ledger, with a ledger scrubber on the contract page for time-travel debugging. - Scoped API keys: per-key permissions (
read:contracts,write:contracts,read:watchdog,admin:*) enforced by route metadata, so a monitoring bot can hold a read-only watchdog key. - API playground: an interactive
/playgroundpage to explore every endpoint, send requests, and copy them as curl. - Build introspection:
GET /api/versionreturns the running API'sversion,git_sha, andbuilt_at(injected at build time via-ldflags, falling back todevlocally). It is unauthenticated and does no database or Redis work, so deploy checks and uptime monitors can poll it cheaply.
- Docker and Docker Compose
- Go 1.23+
- Node 22+ and pnpm 9+
docker compose up -dThis starts Postgres 16 and Redis locally. The docker-compose.yml at the repo root maps Postgres to localhost:5432 and Redis to localhost:6379.
The one-shot migrate service applies apps/api/internal/db/migrations/*.up.sql on first start, so the schema is ready once it finishes (docker compose logs -f migrate). To reset to an empty database instead, run docker compose down -v and start again.
# Register a contract (uses the local API)
go run ./cmd/sorolens track CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC --network testnet
# Run one indexer cycle manually
go run ./cmd/sorolens index --onceThe dashboard is at http://localhost:3000 after pnpm dev in apps/web.
The indexer processes contracts concurrently. Set INDEXER_WORKERS to tune
the worker count; it defaults to GOMAXPROCS. Each contract is handled by one
worker at a time, preserving that contract's processing order.
- Find an issue – Browse the open issues and pick one that interests you. Look for labels like
good first issueorhelp wanted. - Claim it – Comment "I'd like to work on this" on the issue. A maintainer will assign it to you.
- Open a PR – Follow the branch naming and commit conventions in CONTRIBUTING.md and open a pull request against
main.
See CONTRIBUTING.md for the full walkthrough, including local setup, running tests, and the PR checklist.
Real-time chat with maintainers and other contributors: join the Sorolens Discord. Announcements, PR reviews, weekly office hours, and a live GitHub activity feed all live there. Prefer Telegram? Mirror is at t.me/sorolens_community; Discord is the primary hub.
Sorolens is developed in the open, and most of the conversation happens in real time:
| Channel | Where | What it is for |
|---|---|---|
| Discord | discord.gg/D9jATUezYX | Primary hub — live chat with maintainers, PR reviews, weekly office hours, and the live GitHub activity feed. Setup and code questions go in #help; PRs that need eyes in #reviews-wanted. |
| Telegram | t.me/sorolens_community | Mirror group with topic-based channels, for those who prefer Telegram. |
| GitHub Discussions | sorolens/sorolens/discussions | Long-form questions, design proposals, and show-and-tell that should stay searchable beyond chat scrollback. |
The GitHub issue thread remains the source of truth for any individual change. For behaviour expectations in these spaces, see CODE_OF_CONDUCT.md.
+-------------------+ +--------------------+
| Soroban RPC | <---- | Sorolens indexer |
+-------------------+ | (Go, GH Actions) |
+--------+-----------+
|
v
+-----------------+ +-------+ +---------------+ +-------------+
| Watchdog | | | | Postgres 16 | <-- | REST API |
| (Soroban) |==>events==>| | (Neon) | | (Go, Vercel)|
+-----------------+ +-------+ +---------------+ +------+------+
|
v
+---------------+
| Next.js 15 |
| dashboard |
| (Vercel) |
+---------------+
The watchdog contract at contracts/watchdog/ is the piece that makes Sorolens unique: contracts you operate emit HealthCheckEvent, ContractAlert, ContractRegistered, and ContractDeregistered on-chain, the indexer picks them up along with everything else, and the dashboard renders your fleet's live health.
See
ARCHITECTURE.mdfor the full system diagram, data flows, schema DDL, and REST API reference.
The REST API is described by an OpenAPI 3.0 spec at docs/openapi.yaml. Load it into Swagger UI, Redoc or Postman, or generate a client from it (the Go client in packages/go-client is generated this way). Run make openapi after changing a route: it fails if any route is undocumented, lints the spec with Redocly, and regenerates the Go client.
| Contract | ID | Explorer |
|---|---|---|
| Watchdog | CACXRL67WL5KRD6HKWGYADHEUF6RQOCODUN26UQE7MGFZEMIR7PAX6R7 |
Stellar Expert |
Admin: GAZ3HN2QNDKWLOI2OQEG65KBJEAUP4PROR3FJNXNDY34UH547MN4CJUI
| Contract | ID | Explorer | Status |
|---|---|---|---|
| Watchdog | TBA — not deployed yet | — | ⏳ Coming soon |
⏳ Coming soon. Mainnet is on the roadmap. The row above is a placeholder: the mainnet contract ID and explorer link will be published here once the deployment is live.
The Go API ships with a multi-stage apps/api/Dockerfile that produces a
minimal, non-root image (~20 MB) ready to run anywhere Docker is available.
# From the repo root:
docker build \
--build-arg VERSION=$(git describe --tags --always) \
--build-arg GIT_SHA=$(git rev-parse --short HEAD) \
--build-arg BUILT_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t sorolens/api:latest \
-f apps/api/Dockerfile \
.docker-compose.prod.yml wires the API, Postgres, Redis, and a one-shot
migration service together:
cp .env.example .env # fill in DATABASE_URL, REDIS_URL, etc.
docker compose -f docker-compose.prod.yml up -dRequired environment variables:
| Variable | Description |
|---|---|
DATABASE_URL |
Pooled Postgres connection string |
REDIS_URL |
Redis connection string |
STELLAR_NETWORK |
testnet, mainnet, or futurenet |
SOROBAN_RPC_URL |
Soroban JSON-RPC endpoint |
WATCHDOG_CONTRACT_ID |
Deployed watchdog contract address |
CORS_ALLOWED_ORIGINS |
Comma-separated allowed origins (e.g. https://sorolens-web-iota.vercel.app); omit or set to * to allow all |
The API is healthy once GET /health returns {"status":"ok"}. GET /readyz
additionally checks Postgres and Redis reachability.
A railway.toml at the repo root points Railway at the Dockerfile and health
check. Click New Project → Deploy from GitHub in the Railway dashboard,
select this repo, and Railway will pick up railway.toml automatically.
Set the required environment variables in the Variables panel before the first
deploy.
| Layer | Technology |
|---|---|
| API | Go 1.23, chi, pgx v5, deployed as Vercel serverless functions |
| Indexer | Go 1.23, runs as a GitHub Actions scheduled workflow (5-minute cron) |
| Database | Postgres 16, hosted on Neon |
| Cache / locks | Redis, hosted on Upstash |
| Dashboard | Next.js 15, TypeScript, Tailwind CSS, deployed on Vercel |
| XDR decoder | TypeScript package (packages/xdr), wraps @stellar/stellar-sdk |
| CLI | Go 1.23, cobra |
| Go client | Generated from docs/openapi.yaml into packages/go-client |
| Python client | Generated from docs/openapi.yaml into packages/python-sdk (sync + async, published to PyPI as sorolens) |
| Fixture contract | Rust (stable), Soroban SDK, deployed to Stellar testnet |
| Watchdog contract | Rust (stable), Soroban SDK, on-chain health tracking (contracts/watchdog) |
sorolens/
apps/
api/ Go API (Vercel serverless functions)
api/rulelang/ Alert-rule language: parser, validator, evaluator (imported by the indexer)
web/ Next.js 15 dashboard (landing page + /contracts + /watchdog + /playground)
services/
indexer/ Go indexer worker (+ internal/watchdog event classifier)
packages/
xdr/ TypeScript XDR decoder
ui/ Shared React UI primitives
go-client/ Auto-generated Go API client (from docs/openapi.yaml)
python-sdk/ Official Python API client, sync + async (from docs/openapi.yaml)
cli/ Go CLI (cobra)
contracts/
counter/ Rust Soroban fixture contract
watchdog/ Rust Soroban watchdog contract (on-chain health tracking)
deploy/
terraform/ Self-hosting Terraform modules (AWS; see docs/self-hosting/aws.md)
pulumi/ Self-hosting Pulumi (TypeScript) components, mirroring terraform/
docs/
screenshots/ Screenshot placeholders
packages/python-sdk ships the official sorolens Python client: a generated
low-level layer built from docs/openapi.yaml plus a hand-written facade with sync
and async transports.
from sorolens import Client
with Client(api_key="sk_live_...") as client:
print(client.stats.global_stats().tracked_contracts)
for contract in client.contracts.list(limit=10).contracts:
print(contract.id, contract.status)See packages/python-sdk/README.md for the quickstart, API reference
and release process.
| Layer | Location |
|---|---|
| On-chain contract | contracts/watchdog/src/lib.rs (register_contract, report_status, report_alert, get_all_monitored, ...) |
| Deploy script | contracts/watchdog/scripts/deploy-testnet.sh |
| Event classifier | services/indexer/internal/watchdog/classifier.go |
| Postgres schema | apps/api/internal/db/migrations/000002_watchdog.up.sql (monitored_contracts, health_checks, contract_alerts) |
| Store methods | apps/api/internal/store/watchdog.go |
| REST endpoints | GET /api/v1/watchdog/{stats,contracts,alerts,contracts/{id}/{health,alerts}} |
| Dashboard pages | apps/web/app/(app)/watchdog/* |
See CONTRIBUTING.md for local setup, branch conventions, commit format, and a full walkthrough of adding a new XDR type decoder.
See CODE_OF_CONDUCT.md.
Thanks to everyone who has contributed to Sorolens!
MIT. See LICENSE.




