Skip to content
0.5.0 — latest

Plans

View as Markdown

A PlanSequence coordinates a journey that needs more than one confirmed transaction. It is deliberately 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.

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.

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.

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);

Persist checkpoints, never signers or secrets. Do not persist a signer, wallet callback, transaction, 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 can persist the public signature before submission and reconcile it only after matching chain confirmation. An opaque TransactionSendingSigner combines signing and sending; if that boundary is interrupted, its durable invoking attempt has no public signature and remains unresolved. The SDK cannot overstate recoverability where no observable signature exists.