Skip to content

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.

Terminal window
pnpm add @aephia/atlas-kit @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.

import { createSolanaRpc, address } 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 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.

In your own application that address is the thing you have to supply. If your users arrive holding a wallet and nothing else, getting from one to the other is a search rather than a calculation — nothing about a wallet predicts its profile, so there is no offline answer. Identity covers how that search works and what you have to bring to it; it is worth reading before you design a sign-in flow.

You do not have to leave the page. Press Run and this executes against the live test realm, in your browser. Change the address to read a different profile.

The documentation playground remains read-only: it uses only read capability entries, with no wallet or signing surface. The SDK itself is non-custodial and can execute transactions when you explicitly supply a write transport and external Kit signers to a write call.

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.

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.

Both examples on this page share one client, so the second reuses what the first already fetched rather than reading the same accounts again.

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.

How the SDK thinks explains caching, entry points, and why reads are shaped the way they are.

If your users will arrive with a wallet rather than a profile address, read identity first. Wallet-to-profile discovery is the one step the SDK cannot answer by itself, and it shapes everything downstream of your sign-in.