Skip to content
Next — unreleased

Claim stakes

View as Markdown

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:

const stakes = await character.claimStakes.all();

Or by the body, when you want to know who holds a particular place:

const stakes = await sage.claimStakes.byBody(bodyAddress);

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.

Like crafting processes, claim stake instances cannot be computed from the character and the body — the address is assigned at creation. That is why the reads are byCharacter 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.

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.

An owned Claim Stake in active or design state can receive one exact raw rent top-up. 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.

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