Skip to content
Next — unreleased

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

  • claim-stakes — every export in this entry
  • claim-stakes/actions — Claim Stake placement, building-design planning, rent top-up, Fleet-transfer start and recovery, finalization, cancellation, and deconstruction
  • world — the bodies stakes are placed on
  • Crafting habs — the sibling deployment system, sharing the same lifecycle and rent machinery