# Cargo
Documentation: next (source: develop).

Install the preview: `pnpm add @aephia/atlas-kit@next`. These docs follow develop and may include changes not yet published to npm.

> Inspect exact cargo balances and plan Fleet transfers, wallet deposits, and withdrawals.

Markdown source of https://develop.atlas-kit-docs.pages.dev/guides/cargo/ — see https://develop.atlas-kit-docs.pages.dev/ai/ for the full machine-readable surface.

Cargo is what a fleet carries: ore it has mined, fuel it burns, components for
crafting, ammunition. Reading it is one relationship step from the fleet.

```ts
const inventory = await fleet.inventory.get();

for (const item of inventory.cargoHold.items) {
  console.log(item.mint, item.quantityRaw);
}
```

> **Runnable example — Read a fleet's cargo.** Follow a relationship one step further: from a fleet to what it is carrying. Run it in the browser at https://develop.atlas-kit-docs.pages.dev/guides/cargo/.

:::game[Cargo space comes from the fleet]
A fleet can only carry what its ships have room for. General cargo, fuel, and
ammunition use separate storage limits, so empty fuel space cannot
automatically hold more ore. The game calls each of these storage records a
**CargoPod**; that name describes a container in the game data, not necessarily
a physical pod hanging from a ship. Adding more or different ships can increase
the fleet's storage, but it may also make the fleet slower or more expensive to
move.
:::

## Quantities are `bigint`

Every quantity is a `bigint`, never a `number`:

```ts
item.quantityRaw; // 713n
```

This is not pedantry. Game amounts routinely exceed what a JavaScript number
holds exactly, and once a value passes that threshold, arithmetic silently
produces the wrong answer with no error anywhere. Rounding somebody's ore count
by a few units because the value crossed 2^53 is worse than making you type
`n`.

If you need to display a quantity, convert at the edge:

```ts
const display = item.quantityRaw.toString();
```

Do the arithmetic in `bigint` and convert only when rendering.

## Storage units

Cargo definitions expose `storageCost` as an **encoded integer with scale 256**:
`256` means one whole storage unit. Fleet capacities and CargoPod storage usage
are already whole units. Do not compare the encoded definition directly with
remaining capacity.

For a nonnegative `bigint` quantity, calculate whole storage using
`(quantity * BigInt(definition.storageCost) + 255n) / 256n`. Round each transfer
row upward before summing rows. For example, one Food with encoded cost `256`
uses one storage unit, so it fits into 249 remaining units. Use the account's
reported used storage rather than reconstructing it from inventory rows.

## Moving cargo

Reading tells you what is where; moving it is the first write most
applications need. Like every write it follows the Plan flow — creating a Plan
changes nothing until it is signed and executed. If this is your first write,
read [How Atlas Kit changes the game](/start-here/how-it-acts/) before
continuing.

Between a docked fleet and your own storage at that starbase, the planner is
`planFleetTransferCargoAtStarbase`. You state the direction and exactly what
moves, per hold:

```ts
import type { PlanAuthorization } from '@aephia/atlas-kit/cargo/actions';
import { planFleetTransferCargoAtStarbase } from '@aephia/atlas-kit/cargo/actions';

const authorization = {
  profile: fleet.ownerProfile.address,
  authority: authoritySigner.address,
  keyIndex: 0,
} satisfies PlanAuthorization;

const stored = base.cargo.items[0];
if (stored !== undefined) {
  const plan = await planFleetTransferCargoAtStarbase(ctx, fleet, {
    authorization,
    direction: 'toFleet',
    amounts: { cargoHold: [{ cargoId: stored.id, amount: 1n }] },
  });
  console.table(plan.describe());
}
```

The fleet must already be docked — the planner refuses anything else, because
that is the only state in which the game allows the transfer. Amounts are the
same raw `bigint` quantities you read: `cargoHold` takes a list of cargo ids
and amounts, while `ammo` and `fuel` are single quantities because those holds
each carry one resource.

`planFleetTransferCargoWithinFleet` is the same idea inside one fleet: it
moves one exact quantity between the fleet's own holds (`ammo`, `fuel`, or
`cargoHold`), named rather than addressed, so restocking ammunition from the
general hold is one explicit call.

Both planners validate what they can prove from current state — ownership,
fleet state, the holds involved — and produce the same inspectable Plan that
every write uses. Signing and executing it works exactly as in [Warp &
subwarp](/guides/moving-a-fleet/#sign-and-execute-once).

### Storage paid from your Profile

A hold or your Starbase storage keeps one line per cargo type. When an action
adds a line, for a cargo type that place doesn't hold yet, the game pays for
the extra account storage from your Profile's SOL vault with the key that signs.
Emptying a line refunds that storage to your Profile. Both transfer planners
and the deposit planner below work this out and state it in the Plan summary,
apart from transaction fees. A Fleet–Starbase transfer is settled as one pair:
a line freed on one side pays for a line added on the other.

When storage grows, the signing key must be a Profile AUTH key or also hold
`drainSolVault`, and your Profile needs that many lamports above its own rent
reserve. The planner refuses otherwise, and execution checks again before
signing.

## Depositing wallet cargo into the game

If your Profile does not yet have a local Starbase account, start with
[Preparing wallet imports](/guides/import-onboarding/). Reading a Character
does not create one, and depositing cargo does not perform onboarding.

`planDepositCargoToGame` moves tokens from your wallet into an existing
player-local Starbase. Give it a loaded Starbase Player, a cargo ID, an exact
raw token amount, and two address-only roles: the Profile authority and the
wallet token owner. Those roles can use the same address or different addresses.

Wallet deposits require a Central Space Station (CSS). On a loaded System
snapshot, check `system.starbase?.maxLevel.kind === 'css'`; the planner rejects
a non-CSS location. Registration alone does not prove import access, so simulate
the resulting Plan before signing.

```ts
import { planDepositCargoToGame } from '@aephia/atlas-kit/cargo/actions';
import type { PlanAuthorization } from '@aephia/atlas-kit/cargo/actions';
import type { SageContext, Address } from '@aephia/atlas-kit/client';
import type { StarbasePlayerSnapshot } from '@aephia/atlas-kit/starbases';

declare const ctx: SageContext;
declare const player: StarbasePlayerSnapshot;
declare const authorization: PlanAuthorization;
declare const owner: Address;

const plan = await planDepositCargoToGame(ctx, player, {
  authorization,
  owner,
  cargoId: 0,
  amount: 10n,
});
console.table(plan.describe());
```

The planner derives your standard wallet token account and the shared Game
vault. Pass `source` to select a different existing token account owned by
`owner`. It freshly checks the accounts, source balance, mint, and Game/Profile
relationships. Only the original Token program is supported; Token-2022,
delegated spending, and token multisig are outside this planner's scope.

The amount is in **raw token units**, not a decimal display amount. The planner
does not predict the resulting game inventory credit from mint decimals.
Access rules, game limits, and final credit remain program-authoritative.

The wallet source, Game vault, Character, and Starbase Player must already
exist. This Plan contains one deposit instruction and creates no accounts.
Inspect it and [simulate before signing](/start-here/how-it-acts/). Execution
requires both signer roles; when they share an address, one signer suffices.
The same API works in browsers and Node.js, with no wallet UI or local-storage
dependency.

## Withdrawing cargo into a wallet

`planWithdrawCargoFromGame` takes cargo out of your player-local Starbase and
sends the corresponding tokens to an explicit `recipient`. The recipient does
not sign merely to receive tokens: your Profile authority authorizes withdrawal.
Always select the recipient deliberately, even when it is your own wallet.

Illustrative example — supply your loaded player, authorization, and addresses:

```ts
import { planWithdrawCargoFromGame } from '@aephia/atlas-kit/cargo/actions';
import type { PlanAuthorization } from '@aephia/atlas-kit/cargo/actions';
import type { SageContext, Address } from '@aephia/atlas-kit/client';
import type { StarbasePlayerSnapshot } from '@aephia/atlas-kit/starbases';

declare const ctx: SageContext;
declare const player: StarbasePlayerSnapshot;
declare const authorization: PlanAuthorization;
declare const recipient: Address;
declare const payer: Address;

const plan = await planWithdrawCargoFromGame(ctx, player, {
  authorization,
  recipient,
  cargoId: 0,
  amount: 1n,
  destinationAccount: { mode: 'create-if-missing', payer },
});
console.table(plan.describe());
```

By default the recipient's standard associated token account (ATA) must already
exist. The explicit `create-if-missing` option above lets the Plan create that
ATA immediately before withdrawing, in the same transaction. The separate
`payer` funds account storage and signs only when creation is included. The
Plan describes that storage funding separately from transaction fees; planning
itself spends nothing. If someone creates the correct ATA before you execute,
idempotent creation allows the withdrawal to proceed. An account with the wrong
mint, owner, program, or state is rejected.

Pass `destination` to select another existing token account for the same mint
and recipient. A missing custom account cannot be created by this planner.
Token-2022 and token multisig authorities are unsupported. The initial withdrawal
implementation accepts zero-decimal cargo mints, matching the verified PTR
configuration; it rejects nonzero decimals instead of guessing how token units
convert to game inventory.

Your own cargo entitlement and the shared Game vault's token balance are checked
separately. The planner rejects disabled withdrawals and an amount above the
configured per-epoch cap. It records the observed withdrawal counters but does
not calculate an available epoch balance from unproven rollover arithmetic.
Timing, permissions, concurrent withdrawals, and final balances remain
chain-authoritative. Inspect the Plan, [simulate it](/start-here/how-it-acts/),
then sign explicitly. After confirmation, refresh your inventory and check the
transaction's token balances; never automatically retry an uncertain outcome.

Cargo Plans tolerate unrelated shared cache counters, unrelated Game definition
changes and incoming tokens. Before signing, they still check relevant cargo
configuration, account identities and enough tokens to cover the transfer.
Wallet deposit and withdrawal Plans freshly validate the original Token Mint:
correct program owner, non-executable 82-byte initialized layout, valid optional
authority tags, and unchanged decimals. Minting or burning by another player
can change supply without invalidating the Plan; mint and freeze authorities
may also change. Final transfer behavior remains chain-authoritative, so
simulate before signing.
An existing destination must remain present; only a Plan containing idempotent
ATA creation accepts absence. Native account-storage funding is checked during
planning, and simulation checks current funding; owner-and-data hashes do not
freeze native balances.

New wallet deposit and withdrawal Plans compare the selected cargo's full
encoded configuration and its cache address. Changes to any encoded field,
including whitespace in its stored name, require a fresh Plan before signing.
The comparison uses canonical decoding and re-encoding: the pinned Name decoder
removes every NUL character, including leading and embedded NULs. Raw name
changes that only add or remove those NULs therefore preserve the comparison.
Unrelated cargo or ship definitions and sequence counters may still change.
Unsupported saved Plan formats must be recreated using the current planner.

If the deposit's wallet source owner or withdrawal recipient was absent during
planning, new Plans allow that address to remain absent or receive native
funding as an empty, non-executable System account. Its balance may change;
other program owners or account data are rejected. This is separate from the
source/destination token accounts and does not fund anything automatically.
Addresses already present during planning retain their existing owner/data
checks.

## Raw amounts and definitions

`quantityRaw` is exactly what the chain stores. Turning it into something
human-facing — a resource name, a decimal amount — needs the cargo definitions
from the Game account, which the SDK loads and caches for you.

The `mint` on each item is the resource's identity. Match it against the
registry's cargo definitions to get the name and its properties.

## Cargo catalog

Read cargo names and properties independently of anyone's inventory:

```ts
import {
  getCargoDefinition,
  listCargoDefinitions,
} from '@aephia/atlas-kit/cargo';

const definitions = await listCargoDefinitions(ctx);
const first = definitions[0];
if (first !== undefined) {
  const definition = await getCargoDefinition(ctx, first.id);
  console.log(definition.name, definition.mint);
}
```

The listing and by-id reads share the same immutable definitions in the loaded
cargo section. They describe kinds of cargo; owned quantities still come from
inventory reads. See [Definition catalogs](/concepts/definitions/) for preload,
persistence, and section refresh behavior.

## Gotchas

**A fleet has more than one hold.** `cargoHold` is the general one; fuel and
ammunition are tracked separately, because the game treats them separately.
Reading only `cargoHold` will miss them.

**Capacity is not a single number.** What a fleet can carry depends on its
ships and their cargo pods, which come from the Game definitions rather than
from the fleet account itself.

**An empty `items` array is normal.** A fleet that has just unloaded carries
nothing. That is not an error and not a missing read.

## Reference

- [`cargo`](/reference/cargo/) — every export in this entry
- [`cargo/actions`](/reference/cargo/actions/) — the transfer planners
- [`fleets`](/guides/fleets/) — where inventory hangs from
- [`mining`](/reference/mining/) — how cargo gets filled
