The API layer of Ultrasteller — live satellite fleet tracking and ground-station pass prediction built on real orbital data, with on-chain prediction markets on Stellar/Soroban. Rust, axum.
🌍 Live app: ultrasteller-frontend.vercel.app — the dashboard and markets tab run entirely on this API. · 📖 Docs · 🔌 Production API: ultrasteller-backend-production.up.railway.app
Part of the Ultrasteller trio:
| Repo | Role |
|---|---|
| ultrasteller-frontend | Landing, live globe dashboard, and docs — the user-facing product |
| ultrasteller-backend (this) | SGP4 propagation, pass prediction, and the market-automation loop |
| ultrasteller-contract | Soroban contracts this backend creates and resolves markets on |
All endpoints are public, read-only GET, unauthenticated, and CORS-open
(Access-Control-Allow-Origin: *) — built for the frontend to call
cross-origin. Full request/response reference with examples:
ultrasteller-frontend.vercel.app/docs.
| Endpoint | Returns |
|---|---|
GET /api/fleet |
Current position of every tracked satellite (lat/lng/alt/velocity) |
GET /api/passes?lat=&lng=&altKm=&hours= |
Upcoming passes above 10° elevation. Defaults: Cape Canaveral, 24h window (capped at 72h) |
GET /api/markets |
Open and resolved prediction markets (see below) |
GET /health |
Liveness check, 200 OK |
curl https://ultrasteller-backend-production.up.railway.app/api/fleet
curl "https://ultrasteller-backend-production.up.railway.app/api/passes?lat=28.3922&lng=-80.6077&altKm=0.003&hours=24"
curl https://ultrasteller-backend-production.up.railway.app/api/marketsTLEs are fetched live from CelesTrak and cached for 2 hours (serving stale
data on fetch failure rather than erroring). Orbital propagation uses the
sgp4 crate; GMST and ECI/ECF/geodetic
conversions are a direct port of
satellite.js's algorithms, so
output matches the project's original TypeScript implementation to within
sub-100m position accuracy.
A background loop (every 5 minutes) turns upcoming passes into binary
markets on the prediction-market
Soroban contract — "will this satellite's pass over this station exceed
45° max elevation?" — and resolves them once the pass has happened, using
the same SGP4 engine as /api/passes as the oracle. It shells out to the
stellar CLI, signed with a server-held admin identity.
Market creation/resolution is admin-only by design, not user/permissionless: the whole point is that the backend's own orbital-mechanics output is the source of truth.
Config via env vars (all optional, sensible defaults baked in):
| Env var | Default | Meaning |
|---|---|---|
PORT |
3001 |
Listen port |
PREDICTION_MARKET_CONTRACT_ID |
CARQRXAUUJMCEEJP6TUOWQIOBBAL2UKIK4F6PJTXCSF74MQ66EZGBMGI |
Deployed contract (testnet) |
STELLAR_NETWORK |
testnet |
Network passed to the CLI |
STELLAR_ADMIN_IDENTITY |
ultrasteller-admin |
stellar keys identity that signs |
STELLAR_ADMIN_SEED_PHRASE |
— | Docker only: entrypoint materializes the identity from this seed |
Requires the stellar CLI on PATH and the admin identity configured
(stellar keys generate ultrasteller-admin --network testnet --fund). If
the CLI isn't available, the loop logs and skips a cycle rather than
crashing — /api/fleet and /api/passes keep working regardless.
cargo run
# listens on :3001 by default; override with PORT=The Dockerfile builds the server and bundles the stellar CLI; the
entrypoint materializes the admin identity from STELLAR_ADMIN_SEED_PHRASE
(12/24-word seed) so the market loop can sign — omit it and the loop skips
cycles while the rest of the API works. Production runs on Railway at
ultrasteller-backend-production.up.railway.app.
Note: the default contract ID above is admin-keyed to the current
deployment's identity. If you lose the admin key, redeploy
prediction-market from
ultrasteller-contract,
initialize it with your own admin, and set PREDICTION_MARKET_CONTRACT_ID.
cargo test
curl http://localhost:3001/api/fleet
curl "http://localhost:3001/api/passes?lat=28.3922&lng=-80.6077&altKm=0.003&hours=24"
curl http://localhost:3001/api/marketsMIT — see LICENSE.