Community TypeScript SDK for Star Atlas SAGE
Speak in fleets,
not bytes.
Build Star Atlas apps without becoming an on‑chain program expert. The Kit speaks the language of the game — fleets, cargo, recipes, starbases, movement — instead of accounts, byte arrays, and instruction builders. Read the universe, plan your moves, sign nothing you haven’t seen.
npm install @aephia/atlas-kit
- Position
- —
- Progress
- —
- Arrival
- —
This flight is
interpolateSubwarpPosition(state, now) — every
frame, on the real Kit. real Kit math
· demo state
import { deriveFleetTimers, getFleet, interpolateSubwarpPosition, } from '@aephia/atlas-kit/fleets'; const fleet = await getFleet(ctx, fleetAddress); const timers = deriveFleetTimers(fleet); if (fleet.state.kind === 'subwarp') { const now = BigInt(Math.floor(Date.now() / 1_000)); const { position, progress } = interpolateSubwarpPosition(fleet.state, now); }{ arrivesAt: 2026-08-23T21:14:05Z, warpCooldownEndsAt, scanCooldownEndsAt, … }Every cooldown and ETA as a realDate, each derived from exact on-chain unix seconds — and only the timers that apply to the fleet’s current state are present. Ready for countdowns, scheduling, ortoISOString().'subwarp'One of eight states —'idle' | 'docked' | 'warp' | 'subwarp' | 'mining' | 'respawn' | 'destroyed' | 'claimStakeTransfer'— and each variant carries its own typed fields, so aswitchnarrows to exactly the data that exists.{ x: 12.4, y: -3.1 }The fleet’s world coordinates at this exact second. The flight crossing this page’s background is this call, every frame — clamped to the route’s endpoints and explicitly display-only, so the chain stays authoritative.0.620 at departure, 1 at arrival, clamped. The HUD’s progress bar reads this value.
Work with the game,
not the blockchain.
You want a fleet’s name — the blockchain gives you bytes.
You want its cargo — the blockchain gives you accounts to find,
fetch, decode, and join. You want to know whether it’s
docked — the answer is buried in raw state.
The Kit does that work for you. Names are
strings, quantities are exact bigints, state is a
union you can switch on, and relationships read like
a data model because the Kit knows how every account finds every
other one. Underneath, it still drives the generated C4 bindings
— all 48 account codecs and 239 instruction builders of them —
but no raw shape ever crosses your app’s boundary.
import { address, createSolanaRpc } from '@solana/kit'; import { createSageClient } from '@aephia/atlas-kit'; const rpc = createSolanaRpc('https://testnet-rpc.z.ink'); const sage = createSageClient({ cluster: 'zink-ptr', rpc }); const myWallet = address('J4r2s9QA2SHWf8zLPmvPhVphiK92h3rVmoXxcSM8M2vv'); const character = await sage.characters.forProfile(myWallet); const fleets = await character.fleets.all(); const inventory = await fleets[0].inventory.get();A cluster preset: it carries the program addresses and the canonicalGameaddress, so there’s nothing to hunt down. Custom deployments work too — pass a custom cluster with an explicitgameaddress.[{ name: 'Ravager', state: { kind: 'docked', … } }, …]Loaded, immutable views: names are strings, not padded byte arrays, and state is a union you canswitchon. Relations keep traversing —fleets[0].inventory.get(),.mining.get()— so the API reads like a data model, not RPC plumbing.{ cargoHold, components, respawningCargo, fuel, ammo, capacities }The Fleet’s whole loadout, resolved and translated: every pod lists named items —cargoHold.items[0]→{ name: 'Fuel', quantityRaw: 74_500n }— andcapacitiescarries exact used / total / remaining units for cargo, fuel, and ammo. Every quantity is abigint, never a rounded float.
Keep the blockchain
out of your app.
Every piece of SAGE internals you implement yourself becomes code your app has to maintain. A mining screen shouldn’t need to know how SAGE encodes quantities, where definitions live, or how slots become time. With the Kit, that knowledge stays in the Kit — when Star Atlas ships the next SAGE version, the Kit absorbs the churn, and your app keeps talking about fleets. Less code that says more. A mining status screen, both ways:
Raw bindings
// fetch the Fleet account, decode it
// check the discriminator, unpack the state enum
// fetch the 2.5 MB Game account, decode it
// join resource ids against the definitions block
// convert Floyd-encoded rates by hand
// derive elapsed time from slots yourself
// ...
// repeat after every SAGE upgrade
Atlas Kit
const mining = await fleet.mining.get(); const output = mining?.outputs[0];{ asteroid, miningRate, outputs, lastUpdatedAtUnixSeconds, … }The Fleet’s active extraction, fully joined:asteroidis a typed reference to where it’s parked,miningRate.unitsPerSecondis the fleet’s own translated rate, andoutputslists the extracted resources with names already resolved through the definitions registry.undefinedsimply means this fleet isn’t mining — no sentinel values to decode.{ cargo: { name: 'Carbon' }, richness, unitsPerSecond: 3.84 }One extracted resource:cargois the full definition with its name resolved through the registry,richnesskeeps both display and exact fixed-point values, andunitsPerSecondis the fleet’s rate with richness already applied.
The chain stores the past.
The Kit renders the present.
A fleet crossing the galaxy doesn’t keep writing its position to the blockchain. The chain records that it left here, is heading there, departed at this time, and will take this long. Where is it now? That’s math — and the Kit ships it as pure functions: state in, now out.
You already watched one: the flight at the top of this page. Here’s another — drop into the belt and watch a hold fill at the chain’s own rate. And because the math is local and deterministic, your UI never has to keep asking an RPC where things are. Fetch once. Let the clock move it.
- Rate
- — u/s
- Extracted
- —
- Capacity
- —
This progress bar — and the one over the ship above — is
projectMiningOutputUnits(rate, elapsed). So is the one
in your app. real Kit math · demo
state
import { deriveMiningStateAgeSeconds, getFleetMiningState, projectMiningOutputUnits, } from '@aephia/atlas-kit/mining'; const mining = await getFleetMiningState(ctx, fleetAddress); const output = mining?.outputs[0]; if (mining && output) { const now = BigInt(Math.floor(Date.now() / 1_000)); const elapsed = deriveMiningStateAgeSeconds(mining.lastUpdatedAtUnixSeconds, now); const mined = projectMiningOutputUnits(output.unitsPerSecond, elapsed); }512Seconds since the chain last wrote this mining state — a pure derivation from the snapshot’sbiginttimestamp and whatever clock you pass, so it’s deterministic and testable.1966.08The exact units in the hold right now — rate × elapsed, nothing hidden. One fetch, no polling: the cargo bar above is this projection, run against the clock.
Your copilot gets it
right the first time.
Ask Claude, Codex, or Copilot to “show the player’s
fleets and their cargo”, and generating
await character.fleets.all() is a far easier job
than reconstructing the right sequence of SAGE accounts,
addresses, and codecs. That’s not luck — the Kit is
deliberately designed to be easy for humans and coding
agents to understand.
-
llms.txt&llms-full.txt— the full API, served in the format agents actually read. - Every example compiles in CI. Every public export carries a tested example — what your assistant pattern‑matches on is guaranteed to work.
- Errors that teach. Every error code ships a remedy written for self‑correction — by humans and by agents.
-
The legacy trap, defused. AI training data still
suggests the old
@staratlas/sagepackage — built for the previous game, incompatible with SAGE C4. The Kit and its docs steer assistants around it.
Writes you can read
before you sign.
Say you want your fleet to refuel, undock, and begin a warp. The Kit builds those actions into a Plan — and a Plan is just data. Inspect it, combine it, show it to your user, serialize it, or assemble it into one unsigned transaction under an explicit compute and fee policy. The Kit never signs and never submits — your wallet, your final look.
import { combinePlans, assemblePlan } from '@aephia/atlas-kit/planning'; const departure = combinePlans(refuel, undock, warp); const tx = await assemblePlan(ctx, departure, policy);{ summary: 'Refuel, undock, and warp to [12, 4].', steps, preconditions, requiredSigners, … }An inert, serializable description of intent:departure.describe()lists every step in plain game language, and the signers are known before anything is built. Plans survivetoJSON()round-trips and compose deterministically — no signer, no network, no side effects.Explicit compute-budget, priority-fee, and transaction-size limits. Assembly refuses to guess: what the signer pays is bounded by what you wrote down.Unsigned, with a blockhash lifetime — hand it to the wallet of your choice while it’s fresh. The Kit never signs and never submits: your wallet, your final look.
- Refuel Ravager at Starbase MRZ‑12.
- Undock from the starbase.
- Begin the warp to [12, 4].
One plan is one transaction: only steps the chain can execute
together, in the same instant. Anything separated by game time —
warp cool‑downs, subwarp travel — belongs to a
next plan, and deriveFleetTimers tells you
when the fleet is ready for it.
Under the hood
- TypeScript-native Strict types, named exports only — no mystery shapes.
- Exact by default
bigintwhere precision matters, display values alongside. - Tree-shakeable Import only the capabilities you use.
- No hidden I/O The Kit fetches when you tell it to — and only then.
- Node (v20+) & browsers Works in both Node and browsers.
- Open & verifiable Apache-2.0, community-maintained, npm provenance.
Show me more
- Zero-config clusters The
'zink-ptr'preset knows the program and Game addresses. - Smart caching Address-keyed, commitment-aware, seqId-validated definitions.
- Live data
watchFleet()snapshots and replaceable subscription providers. - Errors with remedies Every code documented with its producer — written for self-correction.
- Every example tested Compiled and CI-verified, across the whole public surface.
- AI-readable docs
llms.txt/llms-full.txt. - Escape hatch included The generated C4 bindings, re-exported at
@aephia/atlas-kit/bindings.