Your first read
By the end of this page you will have read a real fleet out of the live game. No wallet, no keys, no setup beyond an install.
Install
Section titled “Install”pnpm add @aephia/sage @solana/kit@solana/kit is a peer dependency: it provides the RPC client and the Address
type. Installing it yourself means you control its version.
Read a fleet
Section titled “Read a fleet”import { createSolanaRpc, address } from '@solana/kit';import { createSageClient } from '@aephia/sage';
const rpc = createSolanaRpc('https://testnet-rpc.z.ink');const sage = createSageClient({ cluster: 'zink-ptr', rpc });
const profileAddress = address('J4r2s9QA2SHWf8zLPmvPhVphiK92h3rVmoXxcSM8M2vv');const character = await sage.characters.forProfile(profileAddress);const fleets = await character.fleets.all();
const fleet = fleets[0];if (!fleet) throw new Error('That profile has no fleets.');
console.log(fleet.name); // "Ravager"console.log(fleet.state); // { kind: 'docked', system: … }That address is a real profile on the test realm. It should work as written.
What just happened
Section titled “What just happened”Four things, worth naming because the rest of the SDK works the same way.
createSageClient did no network work. It is synchronous and allocates a
context: a cache, an RPC binding, and the cluster’s known addresses. Nothing is
fetched until you ask for something.
cluster: 'zink-ptr' filled in the game. The preset knows the program
address and the Game account, so you did not have to.
forProfile returned a loaded object. Not a handle you have to load()
first — the data is already there. Every read in this SDK works that way.
.fleets.all() followed a relationship. Going from a character to its
fleets is a separate read, so it is a separate await. Anything that costs a
network round trip is a method you call, not a property you touch.
Reading a bit more
Section titled “Reading a bit more”Cargo is a relationship too:
const inventory = await fleet.inventory.get();console.log(inventory.cargoHold.items[0]?.quantityRaw);Quantities are bigint, not number. Game amounts routinely exceed what a
JavaScript number can hold exactly, and silently rounding someone’s ore count
is worse than making you type n.
If it did not work
Section titled “If it did not work”A network or CORS error usually means the RPC endpoint rejected the request. See setting up your RPC.
MissingGameContextError means a cluster other than zink-ptr was used
without supplying a Game address. Use the preset unless you know you need
otherwise.
An empty fleets array is a valid answer: that profile currently has no
fleets. Try another address.
Where to go next
Section titled “Where to go next”How the SDK thinks explains caching, entry points, and why reads are shaped the way they are.