Claim stakes
A claim stake is a player’s claim on a celestial body: a hub to build infrastructure on, producing resources over time rather than in a single action.
Reading them
Section titled “Reading them”By owner, which is the usual direction. The game records a Claim Stake’s owner as the player’s Profile, so a loaded Character lists the stakes of its Profile:
const stakes = await character.claimStakes.all();With only the Profile address, sage.claimStakes.byProfile(profileAddress)
returns the same stakes.
Or by the body, when you want to know who holds a particular place:
const stakes = await sage.claimStakes.byBody(bodyAddress);For a Game-wide inventory, import getAllClaimStakes from
@aephia/atlas-kit/claim-stakes and call await getAllClaimStakes(ctx).
It returns complete, immutable instance snapshots in all supported lifecycle
states: active, design and deactivated. It queries the configured Game directly
instead of walking every planet. Stored body and owner Profile references are
preserved without fetching those accounts; an unresolved body reference is not
proof that the stake is unplaced. Planet plot occupancy is a separate projection.
The finder uses your configured indexer, or targeted RPC filters when none is
configured. Indexer hints are validated through account reads; stale missing
hints are omitted and reported in readMeta(stakes).indexer. Invalid candidates
or required cargo-definition failures reject the result. Context limits reject
excessive results rather than silently truncating them. Use the existing
commitment, signal and policy: 'no-store' options as needed.
Completeness is bounded by the selected provider’s result and freshness, not a
promise of an atomic chain-wide snapshot. readMeta(stakes) reports the discovery
strategy and observation metadata; a valid empty result has slot 0n and needs
no definition or related-account reads.
Or by the planet’s plots, when you want to know how much room is left before you plan a placement:
import { getPlanetClaimStakePlots } from '@aephia/atlas-kit/world';
const plots = await getPlanetClaimStakePlots(ctx, bodyAddress);for (const tier of plots.tiers) { console.log(tier.tier, tier.available, tier.placementLevel);}for (const row of plots.rows) { console.log(row.level, row.occupied);}A planet’s plots form seven rows, one per Starbase level from level-0 to
css. rows lists them with every Claim Stake placed in each row: occupied
counts stakes of every tier and definition, including deactivated stakes that
still hold their plot, beside their structures and stored placement chronology.
tiers always lists the five Claim Stake tiers. A tier’s plotLimitByLevel
caps how many stakes a row may hold for a new stake of that tier to fit, so
stakes of other tiers in the row count against it. Placement takes the first
row from level-0 up to the system’s current Starbase level that is below the
limit; placementLevel names that row and available sums the free plots of
the tier over those rows. Tiers share rows, so their available counts do not
add up. Both are absent when the system has no Starbase. Each tier also lists
its definitions with requiredTagsProvided. Permissions, rent, faction rules
and concurrent plot races stay chain-authoritative.
Container and building catalogs
Section titled “Container and building catalogs”Read the available definition tables independently of any player’s placed stakes. Containers describe the Claim Stake itself; buildings describe its infrastructure:
import { getClaimStakeDefinition, listClaimStakeDefinitions, getClaimStakeBuildingDefinition, listClaimStakeBuildingDefinitions,} from '@aephia/atlas-kit/claim-stakes';
const containers = await listClaimStakeDefinitions(ctx);const buildings = await listClaimStakeBuildingDefinitions(ctx);const firstContainer = containers[0];if (firstContainer !== undefined) { const definition = await getClaimStakeDefinition(ctx, firstContainer.id); console.log(definition.name, definition.slots);}const firstBuilding = buildings[0];if (firstBuilding !== undefined) { const definition = await getClaimStakeBuildingDefinition( ctx, firstBuilding.id, ); console.log(definition.power, definition.resourceExtraction);}These are two independent catalogs with immutable definitions shared between list and by-id reads. Building production and extraction fields describe the configuration; read a placed stake for its production state. A catalog entry alone does not establish whether a player can build it on a particular stake. See Definition catalogs for loading both together, persistence, and section refresh behavior.
Lifecycle is a state, not a flag
Section titled “Lifecycle is a state, not a flag”A stake’s state.kind is one of design, active, or deactivated. Those are
genuinely different situations rather than degrees of the same one:
if (stake.state.kind === 'active') { // Producing. Other kinds are not.}Reading a stake and assuming it is producing is the mistake this shape exists to prevent.
Buildings and construction
Section titled “Buildings and construction”A stake is not one thing — it is a plot with infrastructure on it. The snapshot carries the whole layout:
const stakes = await character.claimStakes.all();const layouts = stakes.map((stake) => ({ buildings: stake.buildings.length, crew: stake.neededCrew, underConstruction: stake.constructionRemainingSeconds > 0n,}));buildings is what has been placed — extraction, processing, power, storage —
and each wants crew. Construction is live state: a stake can be active and
still have constructionRemainingSeconds left on recent changes.
What it produces
Section titled “What it produces”Production follows the same pattern as mining: rates against a clock, not a stored total.
const output = stake.resources.netProduction;const held = stake.resources.inventory;netProduction is per-resource rates (consumption nets against generation —
a processing chain can make a rate negative), inventory and capacity are
what the stake holds right now, and lastTickAtUnixSeconds anchors the clock
you project from.
Rent keeps it standing
Section titled “Rent keeps it standing”A stake occupies land and pays for it: rentBalanceRaw is what remains and
lastRentAtUnixSeconds when it was last settled. A dry balance eventually
means eviction, and coming back from that is a respawn, not a resume.
The game is introducing rent priced from recorded history. Until a Game’s rent catalog exists, rent is Disabled and charged from the live tables. The Claim Stake planners below plan only for Disabled rent. Each one checks that the Game’s rent catalog and the stake’s rent settlement authority do not exist, and the Plan rechecks both before signing. If either appears, planning stops and directs you to the game client.
Instances have no derivable address
Section titled “Instances have no derivable address”Like crafting processes, claim stake instances cannot be computed from the
owning Profile and the body — the address is assigned at creation. That is why
the reads are byProfile and byBody rather than a derivation, and why finding
one you have no reference to needs discovery.
Placing a claim stake
Section titled “Placing a claim stake”Placement turns the reads above into one planned transaction. If this would be your first write, read How Atlas Kit changes the game first.
The planner needs the Planet and Starbase Player snapshots you intend to use, the exact Claim Stake definition and zero-cost bundled hub ids from the current Game catalog, a positive raw initial rent amount, your Profile authorization, and a fresh address for the new Claim Stake. That address has no derivation, so generate its key before planning and retain the key only until execution:
import { generateKeyPairSigner } from '@solana/kit';import type { PlanAuthorization } from '@aephia/atlas-kit/claim-stakes/actions';import { planPlaceClaimStake } from '@aephia/atlas-kit/claim-stakes/actions';import { executePlan } from '@aephia/atlas-kit/planning';
const authorization = { profile: character.profile.address, authority: authoritySigner.address, keyIndex: 0,} satisfies PlanAuthorization;
const claimStakeKey = await generateKeyPairSigner();const plan = await planPlaceClaimStake(ctx, planet, starbasePlayer, { authorization, claimStake: claimStakeKey.address, claimStakeDefinitionId: 2, hubBuildingId: 21, initialRentAmount: 500_000_000n,});
console.log(plan.summary, plan.requiredSigners, plan.preconditions);
const result = await executePlan(ctx, plan, { feePayer: authoritySigner, signers: [claimStakeKey],});Planning is inert: it does not sign, submit, reserve a plot, or retain either
signer. Inspect the summary, accounts, and freshness preconditions before
passing the Plan to a wallet boundary. The Profile authority and fresh Claim
Stake key sign only during executePlan; afterwards, rediscover the instance
instead of trying to derive its address.
The planner rejects known stale identity, Game catalog, cargo, crew, tag, and plot-capacity inputs before signing. Final plot races, permissions, rent arithmetic, and program capacity remain chain-authoritative, so a locally valid Plan can still fail safely at execution and should never be submitted twice after an unknown result.
Building changes, finalization, cancellation and Fleet-transfer start require Game version 2 and the existing canonical Claim Stake XP ledger. Finalization also requires the Character’s reconciled XP budget and Game XP configuration. Missing, malformed or mismatched XP accounts fail locally with a repair remedy; these planners never initialize or migrate them. Persisted Plans recheck their identity before signing while allowing earned XP and accrued time to advance.
Planning a building design
Section titled “Planning a building design”An owned stake in active or design can plan one finite set of building
additions and removals. Building-design planning uses the loaded stake plus the
same Profile authorization; quantities are positive and each building id can
appear only once:
import { planClaimStakeBuildingChanges } from '@aephia/atlas-kit/claim-stakes/actions';
const buildingPlan = await planClaimStakeBuildingChanges(ctx, stake, { authorization, buildingChanges: [ { buildingId: 9, kind: 'add', quantity: 2 }, { buildingId: 4, kind: 'remove', quantity: 1 }, ],});
console.log( buildingPlan.summary, buildingPlan.requiredSigners, buildingPlan.preconditions,);This planner is also inert. It refreshes the Claim Stake, Game definitions, identity, Starbase controller, cargo, and crew relationships; then it rejects known stale sequences, invalid definitions, quantity underflow or overflow, incompatible tags, insufficient construction cargo, and an unrepresentable resulting design before signing. Rent synchronization and the final concurrent design-sequence race remain chain-authoritative.
Finalizing a building design
Section titled “Finalizing a building design”An owned stake can finalize only while its loaded and fresh state is design.
Finalization accepts the stake and the same Profile authorization, then produces
one inspectable instruction:
import { planFinalizeClaimStakeBuildingChanges } from '@aephia/atlas-kit/claim-stakes/actions';
const finalizePlan = await planFinalizeClaimStakeBuildingChanges(ctx, stake, { authorization,});
console.log( finalizePlan.summary, finalizePlan.requiredSigners, finalizePlan.preconditions,);The planner rejects active, deactivated, foreign, stale, mismatched, or known delinquent stakes locally. The program re-synchronizes rent at execution and keeps final crew/building validation chain-authoritative, so inspect and present the Plan immediately before authorization.
Cancelling a building design
Section titled “Cancelling a building design”An owned stake in design can discard its pending building changes and return
to active. Cancellation accepts the stake and the same Profile authorization:
import { planCancelClaimStakeBuildingChanges } from '@aephia/atlas-kit/claim-stakes/actions';
const cancelPlan = await planCancelClaimStakeBuildingChanges(ctx, stake, { authorization,});
console.log( cancelPlan.summary, cancelPlan.requiredSigners, cancelPlan.preconditions,);The planner rejects active, deactivated, foreign, stale, or mismatched identity graphs locally. It does not estimate a refund. The program re-synchronizes rent and performs the design-to-active transition at execution, so rent arithmetic and concurrent transitions remain chain-authoritative.
Finalization, cancellation, Fleet-transfer start and deconstruction validate the canonical CurrencyCache identity and layout again before signing. Other players changing its shared vault or withdrawal counters do not invalidate these Plans. The existing personal-state, rent, cargo, Game and XP safeguards remain in force; explicit whole-account guards remain strict. Recreate earlier Plans to use the finite cache guard. This does not remove the read-to-landing race or establish live qualification for these Claim Stake routes.
Topping up rent
Section titled “Topping up rent”An owned Claim Stake in active, design or deactivated state can receive
one exact raw rent top-up. Topping up a Deactivated (rent-delinquent) stake is
how you keep it from eviction. Pass raw ATLAS units as a positive bigint; do not pass a display
ATLAS number:
import { planTopUpClaimStakeRent } from '@aephia/atlas-kit/claim-stakes/actions';
const topUpPlan = await planTopUpClaimStakeRent(ctx, stake, { amount: 500_000_000n, authorization,});
console.log( topUpPlan.summary, topUpPlan.requiredSigners, topUpPlan.preconditions,);The planner validates the fresh owned stake, its canonical Character/Planet/System/Starbase Player graph, and the Character’s known raw ATLAS balance. It preserves the exact raw amount in the Plan description and does not estimate how much rent duration the amount purchases; permissions, rent arithmetic, and concurrent balance changes remain chain-authoritative.
Starting a Fleet transfer
Section titled “Starting a Fleet transfer”An owned idle Fleet can start one transfer to an owned active Claim Stake.
Provide exact raw cargo quantities in dense load and unload lists. Loading
moves cargo from the Claim Stake to the Fleet; unloading moves it from the Fleet
to the Claim Stake:
import { planStartClaimStakeFleetTransfer } from '@aephia/atlas-kit/claim-stakes/actions';
const transferPlan = await planStartClaimStakeFleetTransfer(ctx, fleet, stake, { authorization, load: [{ cargoId: 3, amount: 25n }], unload: [{ cargoId: 1, amount: 10n }],});
console.log( transferPlan.summary, transferPlan.requiredSigners, transferPlan.preconditions,);The planner verifies the shared owner and Game, the Fleet’s positive normalized
crew, the current Fleet/Claim Stake/Planet/System/Starbase Player graph, known
source balances, destination capacities, and an optional present
FleetCrewBinding. Starbase Player remains a readonly instruction account and is
guarded by a whole-account precondition; it is not included in the Plan’s
writable affected addresses. Cargo ids must be unique across both lists. The
chain remains authoritative for range, transfer duration, permissions, and
concurrent state changes. Normal exit remains a separate permissionless action.
Recovering a stuck Fleet transfer
Section titled “Recovering a stuck Fleet transfer”After the transfer lock expires, an owner can recover a Fleet only when the current Fleet and Claim Stake cargo snapshots prove that the queued transfer cannot execute exactly:
import { planRecoverClaimStakeFleetTransfer } from '@aephia/atlas-kit/claim-stakes/actions';
const recoveryPlan = await planRecoverClaimStakeFleetTransfer( ctx, fleet, stake, { atUnixSeconds: 1_700_000_100n, authorization, },);
console.log( recoveryPlan.summary, recoveryPlan.requiredSigners, recoveryPlan.preconditions,);The planner accepts source shortfalls, invalid queued cargo ids, or destination overflow. It rejects a currently executable transfer and directs the caller to the permissionless normal exit. Recovery returns the Fleet to idle without moving cargo or changing the active Claim Stake; it never infers closure or uses force-exit semantics.
Deconstructing a claim stake
Section titled “Deconstructing a claim stake”An owned stake can be deconstructed only while it is active and after every
player-funded building has been removed and finalized. The definition-compatible
zero-cost bundled hub remains because deconstruction discards it:
import { planDeconstructClaimStake } from '@aephia/atlas-kit/claim-stakes/actions';
const deconstructPlan = await planDeconstructClaimStake(ctx, stake, { authorization,});
console.log( deconstructPlan.summary, deconstructPlan.requiredSigners, deconstructPlan.preconditions,);The planner derives the exact Planet chronology index and current Starbase plot level from fresh validated Game, Claim Stake, Body, System, Character, and Starbase Player accounts; callers cannot assert either value. It does not predict rent settlement, returned cargo, an account-close outcome, or a refund amount. Those results and any concurrent close race remain chain-authoritative. Deconstruction also closes the stake’s Building XP ledger. A stake that never had one accrued nothing, and its Plan guards that the ledger stays absent.
Gotchas
Section titled “Gotchas”A stake in design is not yet producing. It exists, it is yours, and it
yields nothing. Filter on state before summing output.
Capacity and yield come from definitions. What a stake produces depends on the body and the Game account’s rules, not on fields stored in the stake account alone.
A body can hold stakes from several players. byBody returns an array for
that reason.
Reference
Section titled “Reference”claim-stakes— every export in this entryclaim-stakes/actions— Claim Stake placement, building-design planning, rent top-up, Fleet-transfer start and recovery, finalization, cancellation, and deconstructionworld— the bodies stakes are placed on- Crafting habs — the sibling deployment system, sharing the same lifecycle and rent machinery