Introduction

Ultrasteller is a live satellite fleet tracking and ground-station pass prediction platform built on real orbital data, with on-chain prediction markets on Stellar/Soroban. It answers two operational questions — where is every satellite in the fleet right now? and exactly when can a given ground station talk to each of them? — and then turns those pass predictions into verifiable on-chain markets.

Nothing is simulated. TLEs (two-line element sets) are fetched live from CelesTrak and propagated with SGP4, the same model used by NORAD for orbit prediction. Positions on the dashboard refresh every 5 seconds; pass predictions look up to 72 hours out.

Architecture

The product is three repositories, one per layer:

RepositoryStackRole
ultrasteller-frontendNext.js · Tailwind · react-globe.glThis site — landing page, live globe dashboard, and these docs. UI only; no orbital math of its own.
ultrasteller-backendRust · axum · sgp4The API: live fleet positions, pass prediction, and the automation that creates and resolves on-chain markets.
ultrasteller-contractRust · Soroban SDKSoroban smart contracts: pass-anchor and prediction-market, deployed on Stellar testnet.
CelesTrak (live TLEs, cached 2h)
        │
        ▼
ultrasteller-backend (Rust/axum, SGP4 propagation)
   │            │                     │
   │ /api/fleet │ /api/passes         │ market loop (every 5 min)
   ▼            ▼                     ▼
ultrasteller-frontend          stellar CLI ──▶ prediction-market
(globe + pass dashboard)                       contract (Soroban testnet)

How It Works

1. Ingest. The backend fetches TLEs for the tracked fleet from CelesTrak and caches them for 2 hours. On a fetch failure it serves stale data rather than erroring, so the dashboard never goes dark because CelesTrak hiccuped.

2. Propagate. Every request runs real SGP4 propagation (via the Rust sgp4crate). GMST and ECI/ECF/geodetic conversions are a direct port of satellite.js's algorithms, matching the original TypeScript implementation to sub-100m accuracy.

3. Predict.For a ground station's coordinates, the backend computes look-angles over the requested window and reports every pass where the satellite rises above 10° elevation: rise time, set time, max elevation, and duration.

4. Bet & resolve. A background loop turns upcoming passes into binary on-chain markets — “will this pass exceed 45° max elevation?” — and resolves them after the pass using the same SGP4 engine as the oracle. See Prediction Markets.

Quickstart

Run the whole stack locally. Backend first (Rust 1.75+):

git clone https://github.com/UltraStellar-HQ/ultrasteller-backend
cd ultrasteller-backend
cargo run
# listens on :3001 by default; override with PORT=

Then the frontend (Node 20+):

git clone https://github.com/UltraStellar-HQ/ultrasteller-frontend
cd ultrasteller-frontend
npm install
cp .env.example .env.local   # NEXT_PUBLIC_API_URL, defaults to http://localhost:3001
npm run dev

The prediction-market loop is optional and needs the stellar CLI on PATH plus a funded testnet admin identity:

stellar keys generate ultrasteller-admin --network testnet --fund

If the CLI isn't available the loop logs and skips a cycle — /api/fleet and /api/passes keep working regardless.

API Reference

Base URL: your backend deployment (locally http://localhost:3001). All endpoints are public, read-only GET, unauthenticated, and CORS-open (Access-Control-Allow-Origin: *).

GET /api/fleet

Current position of every tracked satellite.

curl http://localhost:3001/api/fleet
{
  "generatedAt": "2026-07-11T09:30:00.000Z",
  "satellites": [
    {
      "name": "ISS (ZARYA)",
      "lat": 12.47,
      "lng": -101.3,
      "altKm": 417.2,
      "velocityKmS": 7.66
    }
  ]
}

GET /api/passes

Upcoming passes over a ground station. All parameters optional:

ParamDefaultMeaning
lat28.3922Station latitude, degrees (default: Cape Canaveral)
lng-80.6077Station longitude, degrees
altKm0.003Station altitude, km
hours24Look-ahead window, capped at 72

A pass is reported when the satellite rises above 10° elevation.

curl "http://localhost:3001/api/passes?lat=28.3922&lng=-80.6077&altKm=0.003&hours=24"
{
  "generatedAt": "2026-07-11T09:30:00.000Z",
  "passes": [
    {
      "satelliteName": "ISS (ZARYA)",
      "riseTime": "2026-07-11T11:02:13.000Z",
      "setTime": "2026-07-11T11:12:45.000Z",
      "maxElevationDeg": 63.4,
      "maxElevationTime": "2026-07-11T11:07:30.000Z",
      "durationSec": 632
    }
  ]
}

GET /api/markets

Open and resolved prediction markets.

curl http://localhost:3001/api/markets
{
  "generatedAt": "2026-07-11T09:30:00.000Z",
  "markets": [
    {
      "marketId": "iss-zarya:cape-canaveral:2026-07-11T11:02Z",
      "satellite": "ISS (ZARYA)",
      "station": "Cape Canaveral, USA",
      "thresholdCentideg": 4500,
      "closeTime": "2026-07-11T11:02:13.000Z",
      "yesPool": 250000000,
      "noPool": 100000000,
      "resolved": false,
      "outcome": null
    }
  ]
}

GET /health

Liveness check, returns 200 OK.

Prediction Markets

Every 5 minutes a background loop in the backend turns upcoming passes into binary markets on the Soroban prediction-market contract, then resolves them once the pass has happened. The question is always the same shape: “will this satellite's pass over this station exceed 45° max elevation?”

StageWhat happens
CreatedBackend predicts an upcoming pass and calls create_marketwith a 45.00° threshold and a close time at the pass's rise time.
OpenAnyone can place_bet YES or NO, staked in native XLM.
ClosedBetting stops at the pass's rise time.
ResolvedAfter the pass, the backend recomputes the actual max elevation with the same SGP4 engine and calls resolve_market. The orbital-mechanics output is the oracle.
ClaimWinners claim their stake back plus a pro-rata share of the losing pool. Pure pari-mutuel, zero house fee.

Market creation and resolution are admin-only by design, signed by a server-held identity (STELLAR_ADMIN_IDENTITY) — the whole point is that the backend's own orbital-mechanics output is the source of truth, not user input. Backend configuration (all optional): PREDICTION_MARKET_CONTRACT_ID, STELLAR_NETWORK (default testnet), STELLAR_ADMIN_IDENTITY (default ultrasteller-admin).

Smart Contracts

A Cargo workspace of two Soroban contracts, deployed on Stellar testnet.

pass-anchor

Anchors pass events on-chain as immutable, timestamped proofs. The pass_hash is computed off-chain (e.g. sha256 over satellite name, rise/set/max-elevation times, and station) — the contract only attests that a pass happened and when.

FunctionBehavior
anchor_pass(submitter, pass_hash, satellite)Writes a record the first time a hash is submitted (rejects duplicates), emits PassAnchored.
get_pass(pass_hash)Reads a record back; None if unknown.

prediction-market

FunctionBehavior
initialize(admin, native_token)One-time setup.
create_market(market_id, satellite, station, threshold_centideg, close_time)Admin-only; opens a YES/NO market on a pass.
place_bet(bettor, market_id, side, amount)Stake XLM on a side (side = true is YES). Rejected after close time.
resolve_market(market_id, actual_max_elevation_centideg)Admin-only; sets the outcome once. Double-resolve rejected.
claim(bettor, market_id)Pays stake + pro-rata share of the losing pool if you won; nothing if you lost. Double-claim rejected.
get_market(market_id), get_position(market_id, bettor)Read-only views.

Known v1 limitation: if a market resolves with zero stake on the winning side, the losing pool stays locked in the contract (rare; recoverable via an admin sweep, not built yet).

# build & test
cargo test --workspace
stellar contract build
stellar contract build -p prediction-market   # or just one

Ground Stations

Six real launch/tracking sites are built into the dashboard — pass prediction works for any coordinates via the API.

StationLatLngAlt (km)
Cape Canaveral, USA28.3922-80.60770.003
Kourou, French Guiana5.236-52.76860.01
Baikonur, Kazakhstan45.96563.3050.09
Vandenberg, USA34.742-120.57240.1
Tanegashima, Japan30.4008130.970.02
Wallops Island, USA37.94-75.46650.01

FAQ

Is the orbital data real?

Yes. TLEs come live from CelesTrak and are propagated with SGP4 — the standard model for near-Earth orbit prediction. Nothing is mocked or simulated.

Which satellites are tracked?

The CelesTrak “stations” group (the ISS and other crewed/visited stations). The fleet is defined by the backend's TLE source and can be repointed at any CelesTrak group.

Why are markets admin-only?

The market's integrity comes from the fact that the same deterministic SGP4 engine that predicted the pass also resolves it. Letting users create or resolve markets would break the oracle guarantee.

Which network are the contracts on?

Stellar testnet by default (STELLAR_NETWORK). Bets are staked in native XLM through the standard token interface.

How accurate are positions?

The coordinate conversions are a direct port of satellite.js and match it to sub-100m; overall accuracy is bounded by TLE freshness (cached up to 2 hours, TLEs themselves updated by CelesTrak daily-ish).