Plans
A PlanSequence coordinates a journey that needs more than one confirmed
transaction. It is non-atomic: every step is one transaction, the chain may
advance between steps, and every freshly prepared Plan requires fresh
authorization. The sequence itinerary and each reached Plan description are
distinct authorization moments.
The embedded playground remains read-only. It supplies live Fleet context for the guide without acquiring a signer or executing a sequence; the snippets below exercise execution only against the repository’s deterministic stub.
Build only the itinerary
Section titled “Build only the itinerary”Construction records stable step identities but invokes no capability planner.
The prepare callback owns capability readiness and runs only when its step is
reached. It returns either a freshly prepared Plan or waiting; the planning
core does not interpret the reason or poll an advisory notBefore time.
import type { PlanAuthorization } from '@aephia/atlas-kit/fleets/actions';import { planFleetDock, planFleetUndock,} from '@aephia/atlas-kit/fleets/actions';import { createPlanSequence } from '@aephia/atlas-kit/planning';
const authorization = { profile: fleet.ownerProfile.address, authority: authoritySigner.address, keyIndex: 0,} satisfies PlanAuthorization;
const sequence = await createPlanSequence({ cluster: 'zink-ptr', sequenceId: 'fleet-round-trip', revision: 0, steps: [ { id: 'undock', prepare: async () => ({ status: 'ready', plan: await planFleetUndock(ctx, fleet, { authorization }), }), }, { id: 'dock', prepare: async ({ confirmed }) => { const current = await sage.fleets.get(fleet.address); if (confirmed.length !== 1 || current.state.kind !== 'idle') { return { status: 'waiting', reason: 'Wait until the confirmed undock is visible as idle.', notBefore: '2026-08-28T12:05:00.000Z', }; } return { status: 'ready', plan: await planFleetDock(ctx, current, { authorization }), }; }, }, ],});
console.table(sequence.describe());Show the itinerary before starting. Later, show each fresh Plan and its own
describe() output immediately before that Plan’s signer boundary. Approval of
the itinerary is not blanket approval of later transactions.
Persist waiting progress
Section titled “Persist waiting progress”Create the first checkpoint yourself and keep its JSON in caller-owned durable storage. This deterministic example uses an in-memory string; a real application must await its database or filesystem durability boundary.
import { createPlanSequence, createPlanSequenceCheckpoint, executePlanSequence, stringifyPlanSequenceCheckpoint,} from '@aephia/atlas-kit/planning';
const waitingSequence = await createPlanSequence({ cluster: 'zink-ptr', sequenceId: 'wait-for-capability', revision: 0, steps: [ { id: 'ready-later', prepare: async () => ({ status: 'waiting', reason: 'The capability is not ready yet.', notBefore: '2026-08-28T12:05:00.000Z', }), }, ],});
let checkpointJson = stringifyPlanSequenceCheckpoint( createPlanSequenceCheckpoint(waitingSequence, '2026-08-28T12:00:00.000Z'),);const waiting = await executePlanSequence( ctx, waitingSequence, checkpointJson, { feePayer: authoritySigner, store: { save: async (checkpoint) => { checkpointJson = stringifyPlanSequenceCheckpoint(checkpoint); }, }, },);console.log(waiting.status, waiting.status === 'waiting' && waiting.reason);Capability readiness is not transaction confirmation. notBefore is advisory
capability context, not a timer the SDK follows. A checkpoint’s confirmed
entries are chain confirmation evidence: each records a public signature, slot,
and confirmed or finalized commitment. observedAt is only the caller’s
local observation time; its age never proves confirmation or permission to
continue.
Pause, resume, and complete
Section titled “Pause, resume, and complete”An AbortSignal pauses only between transactions. The first call below is
already aborted, so it returns paused before preparing or signing. The second
call resumes from the persisted checkpoint, presents the fresh Plan, and
completes after one confirmed transaction.
import type { PlanAuthorization } from '@aephia/atlas-kit/fleets/actions';import { planFleetUndock } from '@aephia/atlas-kit/fleets/actions';import { createPlanSequence, createPlanSequenceCheckpoint, executePlanSequence, stringifyPlanSequenceCheckpoint,} from '@aephia/atlas-kit/planning';
const authorization = { profile: fleet.ownerProfile.address, authority: authoritySigner.address, keyIndex: 0,} satisfies PlanAuthorization;const resumable = await createPlanSequence({ cluster: 'zink-ptr', sequenceId: 'safe-undock', revision: 0, steps: [ { id: 'undock', prepare: async () => ({ status: 'ready', plan: await planFleetUndock(ctx, fleet, { authorization }), }), }, ],});
let checkpointJson = stringifyPlanSequenceCheckpoint( createPlanSequenceCheckpoint(resumable, '2026-08-28T12:00:00.000Z'),);const store = { save: async ( checkpoint: Parameters<typeof stringifyPlanSequenceCheckpoint>[0], ) => { checkpointJson = stringifyPlanSequenceCheckpoint(checkpoint); },};const controller = new AbortController();controller.abort();const paused = await executePlanSequence(ctx, resumable, checkpointJson, { feePayer: authoritySigner, signal: controller.signal, store,});checkpointJson = stringifyPlanSequenceCheckpoint(paused.checkpoint);
const result = await executePlanSequence(ctx, resumable, checkpointJson, { feePayer: authoritySigner, store, onBeforeSign: async ({ plan, description }) => { console.log(plan.summary); console.table(description); },});if (result.status === 'completed') console.log(result.signature);Resume safety
Section titled “Resume safety”Persist checkpoints, never signers or secrets. Do not persist a signer, wallet callback, private key, or seed phrase. Never blindly retry, and do not automatically resume after an unknown outcome, an identity mismatch, or confirmation evidence that cannot be verified. Stop and reconcile the intended game action and chain history first.
With an observable signer, the SDK persists the signature and trusted RPC lifetime
metadata before submission for later reconciliation. The metadata is the assembly
RPC’s lastValidBlockHeight and contextSlot, retained only if the signer keeps
the assembled blockhash. A replacement blockhash or nonce produces lifetime: null. Signer-provided heights never replace the RPC height. Checkpoints contain
no signed message or transaction bytes. Their integrity remains the store owner’s
responsibility: keep them in trusted durable storage.
An opaque TransactionSendingSigner combines signing and sending, so an interrupted
invoking attempt has no observable signature and stays unresolved.
Resume an interrupted attempt
Section titled “Resume an interrupted attempt”Pass the saved checkpoint back to executePlanSequence. This is execution,
not a read-only inspection: a successful signed attempt is saved as confirmed,
and the sequence can continue to its next freshly prepared and authorized Plan.
An unsuccessful attempt stops without advancing, clearing, or replaying it:
unknownwithTRANSACTION_SUBMISSION_FAILED: landed failure at the requested commitment. A processed-only failure is not sufficient.unknownwithTRANSACTION_EXPIRED: saved RPC lifetime expired and the signature was absent before and after the expiry checks.unknownwithSEQUENCE_ATTEMPT_UNRESOLVED: pending, missing or uncertain evidence, including older checkpoints without lifetime metadata, signer-replaced blockhashes and nonces whose signature is still absent.
These use the same result codes as live execution. Keep the returned checkpoint
and inspect fresh game state before explicitly creating a recovery sequence;
expiry concerns this signature, not whether another transaction performed the action.
Use commitment: 'finalized' when recovery must exclude confirmed-fork rollback.
Expiry classification assumes a trusted archival RPC with a complete, healthy
transaction-history index. Recovery makes at most four reads for a signed
attempt: history status, finalized getEpochInfo (height and slot),
getFirstAvailableBlock, then history status again. The height must exceed the
saved expiry, retained history must cover the assembly slot, and the final null
status must reach the finalized observation’s slot. This detects pruning and
stale nodes, not missing index entries. Errors or missing optional RPC methods
leave the attempt unresolved. Standard Kit RPC supports these methods; ordinary
SageWriteRpc requirements do not change. Earlier confirmed steps still undergo
the existing confirmation check.
Never invent lifetime metadata for an older checkpoint (including next.95).
Age, balances and a simulation replacement hash do not establish its expiry.
Reference
Section titled “Reference”planning— exact sequence, checkpoint, and result types- Warp & subwarp — one-transaction movement Plans