@aephia/atlas-kit 0.6.0-next.104 → 0.6.0-next.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -362,11 +362,10 @@ export async function planPlaceClaimStake(ctx, body, player, input) {
|
|
|
362
362
|
}
|
|
363
363
|
const currentBodyAccount = requireAccount(bodyAccount, body.address, `Celestial Body ${body.address} is missing. Refresh the Body and retry placement.`);
|
|
364
364
|
const currentPlayerAccount = requireAccount(playerAccount, player.address, `Starbase Player ${player.address} is missing. Refresh it and retry placement.`);
|
|
365
|
-
const currentCharacterAccount = requireAccount(characterAccount, character, `Canonical Character ${character} is missing. Refresh the Profile Character and retry placement.`);
|
|
366
365
|
const currentSystemAccount = requireAccount(systemAccount, body.system.address, `Star System ${body.system.address} is missing. Refresh the System and retry placement.`);
|
|
367
366
|
const currentBody = celestialBodyAccountDefinition.decode(currentBodyAccount);
|
|
368
367
|
const currentPlayer = starbasePlayerAccountDefinition.decode(currentPlayerAccount);
|
|
369
|
-
const
|
|
368
|
+
const { account: currentCharacterAccount } = requireProfileCharacter(characterAccount, character, options.authorization.profile, game, 'placement');
|
|
370
369
|
const currentSystem = starSystemAccountDefinition.decode(currentSystemAccount);
|
|
371
370
|
const canonicalSystem = await deriveStarSystemAddress(game, currentSystem.systemId);
|
|
372
371
|
if (canonicalSystem !== body.system.address ||
|
|
@@ -388,10 +387,6 @@ export async function planPlaceClaimStake(ctx, body, player, input) {
|
|
|
388
387
|
currentPlayer.totalCrew - currentPlayer.busyCrew !== player.availableCrew) {
|
|
389
388
|
fail(`Starbase Player ${player.address} changed or does not match the authorization Profile, canonical Character, System, or available crew. Refresh it and retry.`, player.address);
|
|
390
389
|
}
|
|
391
|
-
if (currentCharacter.playerProfile !== options.authorization.profile ||
|
|
392
|
-
currentCharacter.game !== game) {
|
|
393
|
-
fail(`Character ${character} does not match authorization Profile ${options.authorization.profile} and configured Game ${game}. Refresh the Character and retry.`, character);
|
|
394
|
-
}
|
|
395
390
|
const currentCargo = currentPlayer.cargo.amounts.find(({ cargoId }) => cargoId === definition.cargoId)?.amount ?? 0n;
|
|
396
391
|
if (currentCargo !== cargoQuantity || currentCargo < 1n) {
|
|
397
392
|
fail(`Starbase Player ${player.address} Claim Stake cargo changed from the loaded snapshot or is no longer sufficient. Refresh its cargo and retry.`, player.address);
|
|
@@ -418,7 +413,7 @@ export async function planPlaceClaimStake(ctx, body, player, input) {
|
|
|
418
413
|
precondition(freshGame.account, `The configured Game retains Claim Stake definition ${String(definition.id)} and bundled hub ${String(hub.id)} eligibility.`),
|
|
419
414
|
precondition(currentBodyAccount, `Planet ${body.name} retains an available ${definition.name} plot and its configured Game/System relationship.`),
|
|
420
415
|
precondition(currentPlayerAccount, `Starbase Player ${player.address} retains its Profile, Character, System, available crew, and ${cargoName} balance.`),
|
|
421
|
-
|
|
416
|
+
characterPrecondition(currentCharacterAccount, options.authorization.profile),
|
|
422
417
|
precondition(currentSystemAccount, `Star System ${currentSystem.name} retains its configured Game, sequence, and active Starbase level.`),
|
|
423
418
|
])),
|
|
424
419
|
absentPrecondition(options.claimStake, 'The caller-supplied Claim Stake signer address remains unallocated.'),
|
|
@@ -469,6 +464,165 @@ function sameStakeState(left, right) {
|
|
|
469
464
|
sameQuantities(left.buildings, right.buildings));
|
|
470
465
|
}
|
|
471
466
|
}
|
|
467
|
+
// Common facts for planners acting on an owned, placed Claim Stake. Each
|
|
468
|
+
// planner keeps its own eligibility checks and Plan wording at its call site;
|
|
469
|
+
// `action` completes the shared remedies ("retry <action>").
|
|
470
|
+
/** Requires the loaded Claim Stake to belong to the configured Game and to the
|
|
471
|
+
* authorization Profile's canonical Character, which it returns. */
|
|
472
|
+
async function requireOwnedStake(stake, game, profile) {
|
|
473
|
+
if (stake.game !== game) {
|
|
474
|
+
fail(`Claim Stake ${stake.address} does not match the configured Game ${game}. Refresh it through this context and retry.`, stake.address);
|
|
475
|
+
}
|
|
476
|
+
const character = await findCharacterPda(profile, game);
|
|
477
|
+
if (stake.ownerCharacter.address !== character) {
|
|
478
|
+
fail(`Claim Stake ${stake.address} does not belong to authorization Profile ${profile}'s canonical Character ${character}. Choose an owned Claim Stake, refresh it, and retry.`, stake.address);
|
|
479
|
+
}
|
|
480
|
+
return character;
|
|
481
|
+
}
|
|
482
|
+
/** Reads and decodes the fresh Claim Stake and Body, with `extra` accounts in
|
|
483
|
+
* the same batch. */
|
|
484
|
+
async function readOwnedStake(ctx, stake, action, extra = []) {
|
|
485
|
+
const [stakeCandidate, bodyCandidate, ...extraAccounts] = await readContextRawAccounts(ctx, [
|
|
486
|
+
stake.address,
|
|
487
|
+
stake.body.address,
|
|
488
|
+
...extra,
|
|
489
|
+
]);
|
|
490
|
+
const stakeAccount = requireAccount(stakeCandidate, stake.address, `Claim Stake ${stake.address} is missing. Refresh it and retry ${action}.`);
|
|
491
|
+
const bodyAccount = requireAccount(bodyCandidate, stake.body.address, `Celestial Body ${stake.body.address} is missing. Refresh the Claim Stake Body and retry ${action}.`);
|
|
492
|
+
return {
|
|
493
|
+
bodyAccount,
|
|
494
|
+
currentBody: celestialBodyAccountDefinition.decode(bodyAccount),
|
|
495
|
+
currentStake: claimStakeAccountDefinition.decode(stakeAccount),
|
|
496
|
+
extraAccounts,
|
|
497
|
+
stakeAccount,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
/** Whether the fresh Claim Stake keeps the loaded ownership, Body,
|
|
501
|
+
* definitions, buildings and System generation. */
|
|
502
|
+
function sameOwnedStake(current, stake, game, character) {
|
|
503
|
+
return (current.game === game &&
|
|
504
|
+
current.body === stake.body.address &&
|
|
505
|
+
current.ownerCharacter === character &&
|
|
506
|
+
current.claimStakeDefinitionId === stake.claimStakeDefinitionId &&
|
|
507
|
+
current.claimStakeDefinitionSeqId === stake.claimStakeDefinitionSeqId &&
|
|
508
|
+
current.buildingDefinitionSeqId === stake.buildingDefinitionSeqId &&
|
|
509
|
+
current.systemSeqId === stake.systemSeqId &&
|
|
510
|
+
sameQuantities(current.buildings, stake.buildings));
|
|
511
|
+
}
|
|
512
|
+
/** Whether the fresh Claim Stake keeps the loaded transfer facts: ownership,
|
|
513
|
+
* Body, active state, System generation, resources and capacity. */
|
|
514
|
+
function sameTransferStake(current, stake, game, character) {
|
|
515
|
+
return (current.game === game &&
|
|
516
|
+
current.ownerCharacter === character &&
|
|
517
|
+
current.body === stake.body.address &&
|
|
518
|
+
current.systemSeqId === stake.systemSeqId &&
|
|
519
|
+
current.state.kind === 'active' &&
|
|
520
|
+
current.production.cargoPod.storageCost ===
|
|
521
|
+
stake.resources.capacity.usedRaw &&
|
|
522
|
+
current.production.resourceCapacityRaw ===
|
|
523
|
+
stake.resources.capacity.totalRaw &&
|
|
524
|
+
sameCargoAmounts(current.production.cargoPod.amounts, stake.resources.inventory.items.map(({ id, quantityRaw }) => ({
|
|
525
|
+
amount: quantityRaw,
|
|
526
|
+
id,
|
|
527
|
+
}))));
|
|
528
|
+
}
|
|
529
|
+
/** Whether the fresh Fleet keeps the loaded Game, owner, name, cargo and
|
|
530
|
+
* capacities. */
|
|
531
|
+
function sameTransferFleet(current, fleet, game, profile) {
|
|
532
|
+
return (current.game === game &&
|
|
533
|
+
current.ownerProfile === profile &&
|
|
534
|
+
current.name === fleet.name &&
|
|
535
|
+
current.ammoBank.cargoId === fleet.ammo.id &&
|
|
536
|
+
current.ammoBank.amount === fleet.ammo.amount &&
|
|
537
|
+
current.fuelTank.cargoId === fleet.fuel.id &&
|
|
538
|
+
current.fuelTank.amount === fleet.fuel.amount &&
|
|
539
|
+
current.cargoHold.storageCost === fleet.cargoHold.storageCost &&
|
|
540
|
+
sameCargoAmounts(current.cargoHold.amounts, fleet.cargoHold.items) &&
|
|
541
|
+
current.stats.cargo.ammoCapacity === fleet.capacities.ammo.total &&
|
|
542
|
+
current.stats.cargo.fuelCapacity === fleet.capacities.fuel.total &&
|
|
543
|
+
current.stats.cargo.cargoCapacity === fleet.capacities.cargo.total);
|
|
544
|
+
}
|
|
545
|
+
/** Requires the fresh Body to be the Claim Stake's canonical Planet in the
|
|
546
|
+
* configured Game at the Claim Stake's System generation. */
|
|
547
|
+
async function requireStakePlanet(currentBody, stake, game, action) {
|
|
548
|
+
if (currentBody.kind !== 'planet' ||
|
|
549
|
+
currentBody.game !== game ||
|
|
550
|
+
currentBody.systemSequenceId !== stake.systemSeqId ||
|
|
551
|
+
(await deriveCelestialBodyAddress(game, currentBody.id)) !==
|
|
552
|
+
stake.body.address) {
|
|
553
|
+
fail(`Celestial Body ${stake.body.address} does not match the Claim Stake's configured Game, canonical Planet identity, or System generation. Refresh the Body and Claim Stake before retrying ${action}.`, stake.body.address);
|
|
554
|
+
}
|
|
555
|
+
return currentBody;
|
|
556
|
+
}
|
|
557
|
+
/** Requires and decodes the canonical Character of the authorization Profile in
|
|
558
|
+
* the configured Game. */
|
|
559
|
+
function requireProfileCharacter(candidate, character, profile, game, action) {
|
|
560
|
+
const account = requireAccount(candidate, character, `Canonical Character ${character} is missing. Refresh the Profile Character and retry ${action}.`);
|
|
561
|
+
const current = characterAccountDefinition.decode(account);
|
|
562
|
+
if (current.playerProfile !== profile || current.game !== game) {
|
|
563
|
+
fail(`Character ${character} does not match authorization Profile ${profile} and configured Game ${game}. Refresh the Character and retry ${action}.`, character);
|
|
564
|
+
}
|
|
565
|
+
return { account, current };
|
|
566
|
+
}
|
|
567
|
+
/** Reads the Claim Stake's System and the authorization Profile's canonical
|
|
568
|
+
* Starbase Player there, with `extra` accounts in the same batch. Requires a
|
|
569
|
+
* canonical, current System with an active Starbase that lists the Body, and a
|
|
570
|
+
* Starbase Player of the Profile, Character and System. */
|
|
571
|
+
async function readStakeStarbase(ctx, stake, game, system, character, profile, action, extra = []) {
|
|
572
|
+
const player = await deriveStarbasePlayerAddress(system, character);
|
|
573
|
+
const [systemCandidate, playerCandidate, ...extraAccounts] = await readContextRawAccounts(ctx, [system, player, ...extra]);
|
|
574
|
+
const systemAccount = requireAccount(systemCandidate, system, `Star System ${system} is missing. Refresh the Claim Stake's System and retry ${action}.`);
|
|
575
|
+
const playerAccount = requireAccount(playerCandidate, player, `Starbase Player ${player} is missing for authorization Profile ${profile}. Register or refresh it in the Claim Stake's System before retrying ${action}.`);
|
|
576
|
+
const currentSystem = starSystemAccountDefinition.decode(systemAccount);
|
|
577
|
+
const currentPlayer = starbasePlayerAccountDefinition.decode(playerAccount);
|
|
578
|
+
if (currentSystem.game !== game ||
|
|
579
|
+
currentSystem.sequenceId !== stake.systemSeqId ||
|
|
580
|
+
currentSystem.starbase === undefined ||
|
|
581
|
+
!currentSystem.celestialBodyAddresses.includes(stake.body.address) ||
|
|
582
|
+
(await deriveStarSystemAddress(game, currentSystem.systemId)) !== system) {
|
|
583
|
+
fail(`Star System ${system} does not match the configured Game, canonical identity, Claim Stake generation, active nested Starbase, or Claim Stake Body relationship. Refresh the System and retry ${action}.`, system);
|
|
584
|
+
}
|
|
585
|
+
if (currentPlayer.playerProfile !== profile ||
|
|
586
|
+
currentPlayer.character !== character ||
|
|
587
|
+
currentPlayer.system !== system) {
|
|
588
|
+
fail(`Starbase Player ${player} does not match the authorization Profile, canonical Character, and Claim Stake System. Refresh it and retry ${action}.`, player);
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
currentPlayer,
|
|
592
|
+
currentSystem,
|
|
593
|
+
extraAccounts,
|
|
594
|
+
player,
|
|
595
|
+
playerAccount,
|
|
596
|
+
starbase: currentSystem.starbase,
|
|
597
|
+
systemAccount,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
async function claimStakeCurrencyCache(game) {
|
|
601
|
+
const [currencyCache] = await deriveCurrencyCache(game);
|
|
602
|
+
return currencyCache;
|
|
603
|
+
}
|
|
604
|
+
/** Requires the canonical CurrencyCache and returns its Plan guard. */
|
|
605
|
+
async function requireCurrencyCache(candidate, currencyCache, game, action, planName) {
|
|
606
|
+
await validateCurrencyCache(requireAccount(candidate, currencyCache, `Canonical CurrencyCache ${currencyCache} is missing for configured Game ${game}. Refresh the Game currency configuration and retry ${action}.`), game, planName);
|
|
607
|
+
return {
|
|
608
|
+
kind: 'currency-cache',
|
|
609
|
+
address: currencyCache,
|
|
610
|
+
game,
|
|
611
|
+
describes: 'The canonical CurrencyCache remains assigned to the configured Game; shared accounting may change.',
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function planetPrecondition(account, system) {
|
|
615
|
+
return precondition(account, `The Claim Stake Planet remains assigned to System ${system} at the current generation.`);
|
|
616
|
+
}
|
|
617
|
+
function characterPrecondition(account, profile) {
|
|
618
|
+
return precondition(account, `The canonical Character remains assigned to Profile ${profile} in the configured Game.`);
|
|
619
|
+
}
|
|
620
|
+
function systemPrecondition(account, name) {
|
|
621
|
+
return precondition(account, `Star System ${name} retains its configured Game, sequence, nested Starbase, and Claim Stake Body relationship.`);
|
|
622
|
+
}
|
|
623
|
+
function playerPrecondition(account, player, facts = 'Profile, Character, and System') {
|
|
624
|
+
return precondition(account, `Starbase Player ${player} retains its ${facts}.`);
|
|
625
|
+
}
|
|
472
626
|
const LEGACY_CONTROLLER_ID = Object.freeze({
|
|
473
627
|
unaligned: 0,
|
|
474
628
|
mud: 1,
|
|
@@ -501,85 +655,37 @@ function buildingDescription(change, name) {
|
|
|
501
655
|
export async function planClaimStakeBuildingChanges(ctx, stake, input) {
|
|
502
656
|
const options = validateBuildingChangeOptions(input);
|
|
503
657
|
const game = requireGameAddress(ctx);
|
|
658
|
+
const { profile } = options.authorization;
|
|
504
659
|
if (stake.state.kind === 'deactivated') {
|
|
505
660
|
fail(`Claim Stake ${stake.address} is deactivated; building changes require an active or design Claim Stake. Refresh it or choose another owned Claim Stake before retrying.`, stake.address);
|
|
506
661
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const character = await findCharacterPda(options.authorization.profile, game);
|
|
511
|
-
if (stake.ownerCharacter.address !== character) {
|
|
512
|
-
fail(`Claim Stake ${stake.address} does not belong to authorization Profile ${options.authorization.profile}'s canonical Character ${character}. Choose an owned Claim Stake, refresh it, and retry.`, stake.address);
|
|
513
|
-
}
|
|
514
|
-
const [freshGame, firstAccounts] = await Promise.all([
|
|
662
|
+
const character = await requireOwnedStake(stake, game, profile);
|
|
663
|
+
const action = 'building changes';
|
|
664
|
+
const [freshGame, loaded] = await Promise.all([
|
|
515
665
|
readFreshGameClaimStakeConfiguration(ctx),
|
|
516
|
-
|
|
666
|
+
readOwnedStake(ctx, stake, action, [character]),
|
|
517
667
|
]);
|
|
518
668
|
if (freshGame === undefined) {
|
|
519
669
|
fail(`Configured Game ${game} is missing. Refresh the Game catalog and retry building changes.`, game);
|
|
520
670
|
}
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
const characterAccount = requireAccount(firstAccounts[2], character, `Canonical Character ${character} is missing. Refresh the Profile Character and retry building changes.`);
|
|
524
|
-
const currentStake = claimStakeAccountDefinition.decode(stakeAccount);
|
|
525
|
-
const currentBody = celestialBodyAccountDefinition.decode(bodyAccount);
|
|
526
|
-
const currentCharacter = characterAccountDefinition.decode(characterAccount);
|
|
527
|
-
if (currentStake.game !== game ||
|
|
528
|
-
currentStake.body !== stake.body.address ||
|
|
529
|
-
currentStake.ownerCharacter !== character ||
|
|
530
|
-
currentStake.claimStakeDefinitionId !== stake.claimStakeDefinitionId ||
|
|
531
|
-
currentStake.claimStakeDefinitionSeqId !==
|
|
532
|
-
stake.claimStakeDefinitionSeqId ||
|
|
533
|
-
currentStake.buildingDefinitionSeqId !== stake.buildingDefinitionSeqId ||
|
|
534
|
-
currentStake.systemSeqId !== stake.systemSeqId ||
|
|
535
|
-
!sameQuantities(currentStake.buildings, stake.buildings) ||
|
|
671
|
+
const { bodyAccount, currentStake, stakeAccount } = loaded;
|
|
672
|
+
if (!sameOwnedStake(currentStake, stake, game, character) ||
|
|
536
673
|
currentStake.neededCrew !== stake.neededCrew) {
|
|
537
674
|
fail(`Claim Stake ${stake.address} changed or does not match its loaded ownership, Body, definitions, buildings, crew, or System generation. Refresh it and retry building changes.`, stake.address);
|
|
538
675
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
if (currentBody.kind !== 'planet' ||
|
|
544
|
-
currentBody.game !== game ||
|
|
545
|
-
currentBody.systemSequenceId !== stake.systemSeqId ||
|
|
546
|
-
(await deriveCelestialBodyAddress(game, currentBody.id)) !==
|
|
547
|
-
stake.body.address) {
|
|
548
|
-
fail(`Celestial Body ${stake.body.address} does not match the Claim Stake's configured Game, canonical Planet identity, or System generation. Refresh the Body and Claim Stake before retrying.`, stake.body.address);
|
|
549
|
-
}
|
|
550
|
-
const system = currentBody.system;
|
|
551
|
-
const [player, ownership, profileFaction] = await Promise.all([
|
|
552
|
-
deriveStarbasePlayerAddress(system, character),
|
|
676
|
+
const { system } = await requireStakePlanet(loaded.currentBody, stake, game, action);
|
|
677
|
+
const { account: characterAccount } = requireProfileCharacter(loaded.extraAccounts[0], character, profile, game, action);
|
|
678
|
+
const [ownership, profileFaction] = await Promise.all([
|
|
553
679
|
deriveFactionOwnershipAddress(game, 'starbase', system),
|
|
554
|
-
deriveProfileFactionAddress(
|
|
555
|
-
]);
|
|
556
|
-
const [systemAccountCandidate, playerAccountCandidate, ownershipAccount, profileFactionAccountCandidate,] = await readContextRawAccounts(ctx, [
|
|
557
|
-
system,
|
|
558
|
-
player,
|
|
559
|
-
ownership,
|
|
560
|
-
profileFaction,
|
|
680
|
+
deriveProfileFactionAddress(profile),
|
|
561
681
|
]);
|
|
562
|
-
const
|
|
563
|
-
const
|
|
564
|
-
const profileFactionAccount = requireAccount(profileFactionAccountCandidate, profileFaction, `ProfileFaction ${profileFaction} is missing for authorization Profile ${options.authorization.profile}. Choose or repair the Profile faction, refresh it, and retry building changes.`);
|
|
565
|
-
const currentSystem = starSystemAccountDefinition.decode(systemAccount);
|
|
566
|
-
const currentPlayer = starbasePlayerAccountDefinition.decode(playerAccount);
|
|
682
|
+
const { currentPlayer, currentSystem, extraAccounts: [ownershipAccount, profileFactionCandidate], player, playerAccount, starbase, systemAccount, } = await readStakeStarbase(ctx, stake, game, system, character, profile, action, [ownership, profileFaction]);
|
|
683
|
+
const profileFactionAccount = requireAccount(profileFactionCandidate, profileFaction, `ProfileFaction ${profileFaction} is missing for authorization Profile ${profile}. Choose or repair the Profile faction, refresh it, and retry building changes.`);
|
|
567
684
|
const currentProfileFaction = profileFactionIdentityAccountDefinition.decode(profileFactionAccount);
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
currentSystem.starbase === undefined ||
|
|
571
|
-
(await deriveStarSystemAddress(game, currentSystem.systemId)) !== system) {
|
|
572
|
-
fail(`Star System ${system} does not match the configured Game, canonical identity, Claim Stake generation, or an active nested Starbase. Refresh the System and retry building changes.`, system);
|
|
573
|
-
}
|
|
574
|
-
if (currentPlayer.playerProfile !== options.authorization.profile ||
|
|
575
|
-
currentPlayer.character !== character ||
|
|
576
|
-
currentPlayer.system !== system) {
|
|
577
|
-
fail(`Starbase Player ${player} does not match the authorization Profile, canonical Character, and Claim Stake System. Refresh it and retry building changes.`, player);
|
|
685
|
+
if (currentProfileFaction.profile !== profile) {
|
|
686
|
+
fail(`ProfileFaction ${profileFaction} does not match authorization Profile ${profile}. Refresh or repair the Profile faction before retrying building changes.`, profileFaction);
|
|
578
687
|
}
|
|
579
|
-
|
|
580
|
-
fail(`ProfileFaction ${profileFaction} does not match authorization Profile ${options.authorization.profile}. Refresh or repair the Profile faction before retrying building changes.`, profileFaction);
|
|
581
|
-
}
|
|
582
|
-
let controllerFactionId = LEGACY_CONTROLLER_ID[currentSystem.starbase.owner];
|
|
688
|
+
let controllerFactionId = LEGACY_CONTROLLER_ID[starbase.owner];
|
|
583
689
|
let ownershipPrecondition;
|
|
584
690
|
if (ownershipAccount === undefined) {
|
|
585
691
|
ownershipPrecondition = absentPrecondition(ownership, `The canonical Starbase FactionOwnership ${ownership} remains unallocated so the chain applies its legacy controller gate.`);
|
|
@@ -724,7 +830,7 @@ export async function planClaimStakeBuildingChanges(ctx, stake, input) {
|
|
|
724
830
|
if (currentStake.production.cargoPod.storageCost > resourceStorageRaw) {
|
|
725
831
|
fail(`The resulting Claim Stake design provides ${String(resourceStorageRaw)} raw storage capacity, below the fresh known cargo usage of ${String(currentStake.production.cargoPod.storageCost)}. Keep enough storage buildings, remove cargo, refresh the Claim Stake, and retry building changes.`, stake.address);
|
|
726
832
|
}
|
|
727
|
-
const currencyCache = await
|
|
833
|
+
const currencyCache = await claimStakeCurrencyCache(game);
|
|
728
834
|
const description = `Change buildings on Claim Stake ${stake.address}: ${descriptions.join('; ')}.`;
|
|
729
835
|
const xp = await resolveClaimStakeXp(ctx, game, stake.address);
|
|
730
836
|
return createPlan({
|
|
@@ -733,11 +839,11 @@ export async function planClaimStakeBuildingChanges(ctx, stake, input) {
|
|
|
733
839
|
...(await Promise.all([
|
|
734
840
|
precondition(freshGame.account, 'The configured Game retains the exact Claim Stake and building definitions.'),
|
|
735
841
|
precondition(stakeAccount, `Claim Stake ${stake.address} retains its ownership, design sequence, buildings, cargo, crew, and Body relationship.`),
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
precondition(profileFactionAccount, `ProfileFaction ${profileFaction} remains assigned to Profile ${
|
|
842
|
+
planetPrecondition(bodyAccount, system),
|
|
843
|
+
characterPrecondition(characterAccount, profile),
|
|
844
|
+
systemPrecondition(systemAccount, currentSystem.name),
|
|
845
|
+
playerPrecondition(playerAccount, player, 'Profile, Character, System, and available crew'),
|
|
846
|
+
precondition(profileFactionAccount, `ProfileFaction ${profileFaction} remains assigned to Profile ${profile} and aligned with the current Starbase controller.`),
|
|
741
847
|
])),
|
|
742
848
|
ownershipPrecondition,
|
|
743
849
|
xp.guard,
|
|
@@ -791,22 +897,18 @@ export async function planClaimStakeBuildingChanges(ctx, stake, input) {
|
|
|
791
897
|
export async function planFinalizeClaimStakeBuildingChanges(ctx, stake, input) {
|
|
792
898
|
const options = validateFinalizeBuildingChangeOptions(input);
|
|
793
899
|
const game = requireGameAddress(ctx);
|
|
900
|
+
const { profile } = options.authorization;
|
|
794
901
|
if (stake.state.kind !== 'design') {
|
|
795
902
|
fail(`Claim Stake ${stake.address} is not in design state. Refresh it and finalize only a pending building design.`, stake.address);
|
|
796
903
|
}
|
|
797
|
-
|
|
798
|
-
fail(`Claim Stake ${stake.address} does not match the configured Game ${game}. Refresh it through this context and retry.`, stake.address);
|
|
799
|
-
}
|
|
800
|
-
const character = await findCharacterPda(options.authorization.profile, game);
|
|
801
|
-
if (stake.ownerCharacter.address !== character) {
|
|
802
|
-
fail(`Claim Stake ${stake.address} does not belong to authorization Profile ${options.authorization.profile}'s canonical Character ${character}. Choose an owned Claim Stake, refresh it, and retry.`, stake.address);
|
|
803
|
-
}
|
|
904
|
+
const character = await requireOwnedStake(stake, game, profile);
|
|
804
905
|
if (stake.rentBalanceRaw <= 0n) {
|
|
805
906
|
fail(`Claim Stake ${stake.address} has a known delinquent rent balance of ${String(stake.rentBalanceRaw)} raw units. Top up rent, refresh the Claim Stake, and retry finalization.`, stake.address);
|
|
806
907
|
}
|
|
807
|
-
const
|
|
908
|
+
const action = 'finalization';
|
|
909
|
+
const [freshGame, loaded] = await Promise.all([
|
|
808
910
|
readFreshGameClaimStakeConfiguration(ctx),
|
|
809
|
-
|
|
911
|
+
readOwnedStake(ctx, stake, action, [character]),
|
|
810
912
|
]);
|
|
811
913
|
if (freshGame === undefined) {
|
|
812
914
|
fail(`Configured Game ${game} is missing. Refresh the Game catalog and retry finalization.`, game);
|
|
@@ -815,59 +917,19 @@ export async function planFinalizeClaimStakeBuildingChanges(ctx, stake, input) {
|
|
|
815
917
|
freshGame.data.buildings.sequenceId !== stake.buildingDefinitionSeqId) {
|
|
816
918
|
fail(`Claim Stake ${stake.address} definition sequence is stale against the current Game catalog. Refresh the Claim Stake and definitions to rule out stale local data. If the on-chain generation still differs, use a Claim Stake matching the current catalog before retrying finalization.`, stake.address);
|
|
817
919
|
}
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
const characterAccount = requireAccount(firstAccounts[2], character, `Canonical Character ${character} is missing. Refresh the Profile Character and retry finalization.`);
|
|
821
|
-
const currentStake = claimStakeAccountDefinition.decode(stakeAccount);
|
|
822
|
-
const currentBody = celestialBodyAccountDefinition.decode(bodyAccount);
|
|
823
|
-
const currentCharacter = characterAccountDefinition.decode(characterAccount);
|
|
824
|
-
if (currentStake.game !== game ||
|
|
825
|
-
currentStake.body !== stake.body.address ||
|
|
826
|
-
currentStake.ownerCharacter !== character ||
|
|
827
|
-
currentStake.claimStakeDefinitionId !== stake.claimStakeDefinitionId ||
|
|
828
|
-
currentStake.claimStakeDefinitionSeqId !==
|
|
829
|
-
stake.claimStakeDefinitionSeqId ||
|
|
830
|
-
currentStake.buildingDefinitionSeqId !== stake.buildingDefinitionSeqId ||
|
|
831
|
-
currentStake.systemSeqId !== stake.systemSeqId ||
|
|
920
|
+
const { bodyAccount, currentStake, stakeAccount } = loaded;
|
|
921
|
+
if (!sameOwnedStake(currentStake, stake, game, character) ||
|
|
832
922
|
currentStake.rentBalanceRaw !== stake.rentBalanceRaw ||
|
|
833
|
-
!sameQuantities(currentStake.buildings, stake.buildings) ||
|
|
834
923
|
currentStake.state.kind !== 'design' ||
|
|
835
924
|
currentStake.state.sequenceId !== stake.state.sequenceId ||
|
|
836
925
|
!sameQuantities(currentStake.state.buildings, stake.state.buildings)) {
|
|
837
926
|
fail(`Claim Stake ${stake.address} changed or does not match its loaded ownership, Body, definitions, buildings, design sequence, rent, or System generation. Refresh it and retry finalization.`, stake.address);
|
|
838
927
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
currentBody.game !== game ||
|
|
845
|
-
currentBody.systemSequenceId !== stake.systemSeqId ||
|
|
846
|
-
(await deriveCelestialBodyAddress(game, currentBody.id)) !==
|
|
847
|
-
stake.body.address) {
|
|
848
|
-
fail(`Celestial Body ${stake.body.address} does not match the Claim Stake's configured Game, canonical Planet identity, or System generation. Refresh the Body and Claim Stake before retrying finalization.`, stake.body.address);
|
|
849
|
-
}
|
|
850
|
-
const system = currentBody.system;
|
|
851
|
-
const player = await deriveStarbasePlayerAddress(system, character);
|
|
852
|
-
const currencyCache = await deriveCurrencyCache(game).then(([address]) => address);
|
|
853
|
-
const [systemCandidate, playerCandidate, currencyCandidate] = await readContextRawAccounts(ctx, [system, player, currencyCache]);
|
|
854
|
-
const systemAccount = requireAccount(systemCandidate, system, `Star System ${system} is missing. Refresh the Claim Stake's System and retry finalization.`);
|
|
855
|
-
const playerAccount = requireAccount(playerCandidate, player, `Starbase Player ${player} is missing for authorization Profile ${options.authorization.profile}. Register or refresh it in the Claim Stake's System before retrying finalization.`);
|
|
856
|
-
const currencyAccount = requireAccount(currencyCandidate, currencyCache, `Canonical CurrencyCache ${currencyCache} is missing for configured Game ${game}. Refresh the Game currency configuration and retry finalization.`);
|
|
857
|
-
const currentSystem = starSystemAccountDefinition.decode(systemAccount);
|
|
858
|
-
const currentPlayer = starbasePlayerAccountDefinition.decode(playerAccount);
|
|
859
|
-
if (currentSystem.game !== game ||
|
|
860
|
-
currentSystem.sequenceId !== stake.systemSeqId ||
|
|
861
|
-
currentSystem.starbase === undefined ||
|
|
862
|
-
(await deriveStarSystemAddress(game, currentSystem.systemId)) !== system) {
|
|
863
|
-
fail(`Star System ${system} does not match the configured Game, canonical identity, Claim Stake generation, or an active nested Starbase. Refresh the System and retry finalization.`, system);
|
|
864
|
-
}
|
|
865
|
-
if (currentPlayer.playerProfile !== options.authorization.profile ||
|
|
866
|
-
currentPlayer.character !== character ||
|
|
867
|
-
currentPlayer.system !== system) {
|
|
868
|
-
fail(`Starbase Player ${player} does not match the authorization Profile, canonical Character, and Claim Stake System. Refresh it and retry finalization.`, player);
|
|
869
|
-
}
|
|
870
|
-
await validateCurrencyCache(currencyAccount, game, 'Claim Stake finalization');
|
|
928
|
+
const { system } = await requireStakePlanet(loaded.currentBody, stake, game, action);
|
|
929
|
+
const { account: characterAccount } = requireProfileCharacter(loaded.extraAccounts[0], character, profile, game, action);
|
|
930
|
+
const currencyCache = await claimStakeCurrencyCache(game);
|
|
931
|
+
const { currentSystem, extraAccounts: [currencyCandidate], player, playerAccount, systemAccount, } = await readStakeStarbase(ctx, stake, game, system, character, profile, action, [currencyCache]);
|
|
932
|
+
const currencyGuard = await requireCurrencyCache(currencyCandidate, currencyCache, game, action, 'Claim Stake finalization');
|
|
871
933
|
const description = `Finalize building changes on Claim Stake ${stake.address}.`;
|
|
872
934
|
const xp = await resolveClaimStakeXp(ctx, game, stake.address, character);
|
|
873
935
|
return createPlan({
|
|
@@ -877,17 +939,12 @@ export async function planFinalizeClaimStakeBuildingChanges(ctx, stake, input) {
|
|
|
877
939
|
...(await Promise.all([
|
|
878
940
|
precondition(freshGame.account, 'The configured Game remains current.'),
|
|
879
941
|
precondition(stakeAccount, `Claim Stake ${stake.address} retains its ownership, design sequence, rent balance, Body, and System generation.`),
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
942
|
+
characterPrecondition(characterAccount, profile),
|
|
943
|
+
planetPrecondition(bodyAccount, system),
|
|
944
|
+
systemPrecondition(systemAccount, currentSystem.name),
|
|
945
|
+
playerPrecondition(playerAccount, player),
|
|
884
946
|
])),
|
|
885
|
-
|
|
886
|
-
kind: 'currency-cache',
|
|
887
|
-
address: currencyCache,
|
|
888
|
-
game,
|
|
889
|
-
describes: 'The canonical CurrencyCache remains assigned to the configured Game; shared accounting may change.',
|
|
890
|
-
},
|
|
947
|
+
currencyGuard,
|
|
891
948
|
],
|
|
892
949
|
steps: [
|
|
893
950
|
{
|
|
@@ -934,66 +991,23 @@ export async function planFinalizeClaimStakeBuildingChanges(ctx, stake, input) {
|
|
|
934
991
|
export async function planCancelClaimStakeBuildingChanges(ctx, stake, input) {
|
|
935
992
|
const options = validateCancelBuildingChangeOptions(input);
|
|
936
993
|
const game = requireGameAddress(ctx);
|
|
994
|
+
const { profile } = options.authorization;
|
|
937
995
|
if (stake.state.kind !== 'design') {
|
|
938
996
|
fail(`Claim Stake ${stake.address} is not in design state. Refresh it and cancel only a pending building design.`, stake.address);
|
|
939
997
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
if (stake.ownerCharacter.address !== character) {
|
|
945
|
-
fail(`Claim Stake ${stake.address} does not belong to authorization Profile ${options.authorization.profile}'s canonical Character ${character}. Choose an owned Claim Stake, refresh it, and retry.`, stake.address);
|
|
946
|
-
}
|
|
947
|
-
const firstAccounts = await readContextRawAccounts(ctx, [
|
|
948
|
-
stake.address,
|
|
949
|
-
stake.body.address,
|
|
950
|
-
]);
|
|
951
|
-
const stakeAccount = requireAccount(firstAccounts[0], stake.address, `Claim Stake ${stake.address} is missing. Refresh it and retry cancellation.`);
|
|
952
|
-
const bodyAccount = requireAccount(firstAccounts[1], stake.body.address, `Celestial Body ${stake.body.address} is missing. Refresh the Claim Stake Body and retry cancellation.`);
|
|
953
|
-
const currentStake = claimStakeAccountDefinition.decode(stakeAccount);
|
|
954
|
-
const currentBody = celestialBodyAccountDefinition.decode(bodyAccount);
|
|
955
|
-
if (currentStake.game !== game ||
|
|
956
|
-
currentStake.body !== stake.body.address ||
|
|
957
|
-
currentStake.ownerCharacter !== character ||
|
|
958
|
-
currentStake.claimStakeDefinitionId !== stake.claimStakeDefinitionId ||
|
|
959
|
-
currentStake.claimStakeDefinitionSeqId !==
|
|
960
|
-
stake.claimStakeDefinitionSeqId ||
|
|
961
|
-
currentStake.buildingDefinitionSeqId !== stake.buildingDefinitionSeqId ||
|
|
962
|
-
currentStake.systemSeqId !== stake.systemSeqId ||
|
|
963
|
-
!sameQuantities(currentStake.buildings, stake.buildings) ||
|
|
998
|
+
const character = await requireOwnedStake(stake, game, profile);
|
|
999
|
+
const action = 'cancellation';
|
|
1000
|
+
const { bodyAccount, currentBody, currentStake, stakeAccount } = await readOwnedStake(ctx, stake, action);
|
|
1001
|
+
if (!sameOwnedStake(currentStake, stake, game, character) ||
|
|
964
1002
|
currentStake.state.kind !== 'design' ||
|
|
965
1003
|
currentStake.state.sequenceId !== stake.state.sequenceId ||
|
|
966
1004
|
!sameQuantities(currentStake.state.buildings, stake.state.buildings)) {
|
|
967
1005
|
fail(`Claim Stake ${stake.address} changed or does not match its loaded ownership, Body, definitions, buildings, design sequence, or System generation. Refresh it and retry cancellation.`, stake.address);
|
|
968
1006
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
stake.body.address) {
|
|
974
|
-
fail(`Celestial Body ${stake.body.address} does not match the Claim Stake's configured Game, canonical Planet identity, or System generation. Refresh the Body and Claim Stake before retrying cancellation.`, stake.body.address);
|
|
975
|
-
}
|
|
976
|
-
const system = currentBody.system;
|
|
977
|
-
const player = await deriveStarbasePlayerAddress(system, character);
|
|
978
|
-
const currencyCache = await deriveCurrencyCache(game).then(([address]) => address);
|
|
979
|
-
const [systemCandidate, playerCandidate, currencyCandidate] = await readContextRawAccounts(ctx, [system, player, currencyCache]);
|
|
980
|
-
const systemAccount = requireAccount(systemCandidate, system, `Star System ${system} is missing. Refresh the Claim Stake's System and retry cancellation.`);
|
|
981
|
-
const playerAccount = requireAccount(playerCandidate, player, `Starbase Player ${player} is missing for authorization Profile ${options.authorization.profile}. Register or refresh it in the Claim Stake's System before retrying cancellation.`);
|
|
982
|
-
const currencyAccount = requireAccount(currencyCandidate, currencyCache, `Canonical CurrencyCache ${currencyCache} is missing for configured Game ${game}. Refresh the Game currency configuration and retry cancellation.`);
|
|
983
|
-
const currentSystem = starSystemAccountDefinition.decode(systemAccount);
|
|
984
|
-
const currentPlayer = starbasePlayerAccountDefinition.decode(playerAccount);
|
|
985
|
-
if (currentSystem.game !== game ||
|
|
986
|
-
currentSystem.sequenceId !== stake.systemSeqId ||
|
|
987
|
-
currentSystem.starbase === undefined ||
|
|
988
|
-
(await deriveStarSystemAddress(game, currentSystem.systemId)) !== system) {
|
|
989
|
-
fail(`Star System ${system} does not match the configured Game, canonical identity, Claim Stake generation, or an active nested Starbase. Refresh the System and retry cancellation.`, system);
|
|
990
|
-
}
|
|
991
|
-
if (currentPlayer.playerProfile !== options.authorization.profile ||
|
|
992
|
-
currentPlayer.character !== character ||
|
|
993
|
-
currentPlayer.system !== system) {
|
|
994
|
-
fail(`Starbase Player ${player} does not match the authorization Profile, canonical Character, and Claim Stake System. Refresh it and retry cancellation.`, player);
|
|
995
|
-
}
|
|
996
|
-
await validateCurrencyCache(currencyAccount, game, 'Claim Stake cancellation');
|
|
1007
|
+
const { system } = await requireStakePlanet(currentBody, stake, game, action);
|
|
1008
|
+
const currencyCache = await claimStakeCurrencyCache(game);
|
|
1009
|
+
const { currentSystem, extraAccounts: [currencyCandidate], player, playerAccount, systemAccount, } = await readStakeStarbase(ctx, stake, game, system, character, profile, action, [currencyCache]);
|
|
1010
|
+
const currencyGuard = await requireCurrencyCache(currencyCandidate, currencyCache, game, action, 'Claim Stake cancellation');
|
|
997
1011
|
const description = `Cancel the pending building design on Claim Stake ${stake.address} and return it to active state.`;
|
|
998
1012
|
const xp = await resolveClaimStakeXp(ctx, game, stake.address);
|
|
999
1013
|
return createPlan({
|
|
@@ -1002,16 +1016,11 @@ export async function planCancelClaimStakeBuildingChanges(ctx, stake, input) {
|
|
|
1002
1016
|
xp.guard,
|
|
1003
1017
|
...(await Promise.all([
|
|
1004
1018
|
precondition(stakeAccount, `Claim Stake ${stake.address} retains its ownership, design sequence, buildings, Body, and System generation.`),
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1019
|
+
planetPrecondition(bodyAccount, system),
|
|
1020
|
+
systemPrecondition(systemAccount, currentSystem.name),
|
|
1021
|
+
playerPrecondition(playerAccount, player),
|
|
1008
1022
|
])),
|
|
1009
|
-
|
|
1010
|
-
kind: 'currency-cache',
|
|
1011
|
-
address: currencyCache,
|
|
1012
|
-
game,
|
|
1013
|
-
describes: 'The canonical CurrencyCache remains assigned to the configured Game; shared accounting may change.',
|
|
1014
|
-
},
|
|
1023
|
+
currencyGuard,
|
|
1015
1024
|
],
|
|
1016
1025
|
steps: [
|
|
1017
1026
|
{
|
|
@@ -1053,46 +1062,25 @@ export async function planCancelClaimStakeBuildingChanges(ctx, stake, input) {
|
|
|
1053
1062
|
export async function planDeconstructClaimStake(ctx, stake, input) {
|
|
1054
1063
|
const options = validateDeconstructOptions(input);
|
|
1055
1064
|
const game = requireGameAddress(ctx);
|
|
1065
|
+
const { profile } = options.authorization;
|
|
1056
1066
|
if (stake.state.kind !== 'active') {
|
|
1057
1067
|
fail(`Claim Stake ${stake.address} is not in active state. Refresh it, finalize or cancel any pending design, and retry deconstruction.`, stake.address);
|
|
1058
1068
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
}
|
|
1062
|
-
const character = await findCharacterPda(options.authorization.profile, game);
|
|
1063
|
-
if (stake.ownerCharacter.address !== character) {
|
|
1064
|
-
fail(`Claim Stake ${stake.address} does not belong to authorization Profile ${options.authorization.profile}'s canonical Character ${character}. Choose an owned Claim Stake, refresh it, and retry.`, stake.address);
|
|
1065
|
-
}
|
|
1066
|
-
const [freshGame, firstAccounts] = await Promise.all([
|
|
1069
|
+
const character = await requireOwnedStake(stake, game, profile);
|
|
1070
|
+
const action = 'deconstruction';
|
|
1071
|
+
const [freshGame, { bodyAccount, currentBody, currentStake, stakeAccount }] = await Promise.all([
|
|
1067
1072
|
readFreshGameClaimStakeConfiguration(ctx),
|
|
1068
|
-
|
|
1073
|
+
readOwnedStake(ctx, stake, action),
|
|
1069
1074
|
]);
|
|
1070
1075
|
if (freshGame === undefined) {
|
|
1071
1076
|
fail(`Configured Game ${game} is missing. Refresh the Game definitions and retry deconstruction.`, game);
|
|
1072
1077
|
}
|
|
1073
|
-
|
|
1074
|
-
const bodyAccount = requireAccount(firstAccounts[1], stake.body.address, `Celestial Body ${stake.body.address} is missing. Refresh the Claim Stake Body and retry deconstruction.`);
|
|
1075
|
-
const currentStake = claimStakeAccountDefinition.decode(stakeAccount);
|
|
1076
|
-
const currentBody = celestialBodyAccountDefinition.decode(bodyAccount);
|
|
1077
|
-
if (currentStake.game !== game ||
|
|
1078
|
-
currentStake.body !== stake.body.address ||
|
|
1079
|
-
currentStake.ownerCharacter !== character ||
|
|
1080
|
-
currentStake.claimStakeDefinitionId !== stake.claimStakeDefinitionId ||
|
|
1081
|
-
currentStake.claimStakeDefinitionSeqId !==
|
|
1082
|
-
stake.claimStakeDefinitionSeqId ||
|
|
1083
|
-
currentStake.buildingDefinitionSeqId !== stake.buildingDefinitionSeqId ||
|
|
1084
|
-
currentStake.systemSeqId !== stake.systemSeqId ||
|
|
1085
|
-
!sameQuantities(currentStake.buildings, stake.buildings) ||
|
|
1078
|
+
if (!sameOwnedStake(currentStake, stake, game, character) ||
|
|
1086
1079
|
currentStake.state.kind !== 'active') {
|
|
1087
1080
|
fail(`Claim Stake ${stake.address} changed or does not match its loaded ownership, Body, definitions, buildings, active state, or System generation. Refresh it and retry deconstruction.`, stake.address);
|
|
1088
1081
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
currentBody.systemSequenceId !== stake.systemSeqId ||
|
|
1092
|
-
(await deriveCelestialBodyAddress(game, currentBody.id)) !==
|
|
1093
|
-
stake.body.address) {
|
|
1094
|
-
fail(`Celestial Body ${stake.body.address} does not match the Claim Stake's configured Game, canonical Planet identity, or System generation. Refresh the Body and Claim Stake before retrying deconstruction.`, stake.body.address);
|
|
1095
|
-
}
|
|
1082
|
+
const planet = await requireStakePlanet(currentBody, stake, game, action);
|
|
1083
|
+
const { system } = planet;
|
|
1096
1084
|
if (freshGame.data.claimStakes.sequenceId !== stake.claimStakeDefinitionSeqId) {
|
|
1097
1085
|
fail(`Claim Stake ${stake.address} definition generation is stale against the current Game catalog. Refresh the Claim Stake and definitions to rule out stale local data. If the on-chain generation still differs, use a Claim Stake matching the current catalog before retrying deconstruction.`, stake.address);
|
|
1098
1086
|
}
|
|
@@ -1126,7 +1114,7 @@ export async function planDeconstructClaimStake(ctx, stake, input) {
|
|
|
1126
1114
|
? `Claim Stake ${stake.address} does not contain exactly one verified bundled hub. Refresh it and restore the definition-compatible bundled hub before retrying deconstruction.`
|
|
1127
1115
|
: `Claim Stake ${stake.address} holds ${bundledHub.name} (${String(bundledHub.id)}), which is not the zero-cost ${claimDefinition.tier} bundled hub for Claim Stake definition ${String(claimDefinition.id)} (${claimDefinition.name}). Refresh the Claim Stake and Game definitions so the recorded hub matches its definition before retrying deconstruction.`, stake.address);
|
|
1128
1116
|
}
|
|
1129
|
-
const groups =
|
|
1117
|
+
const groups = planet.details.claimedPlots.filter(({ key }) => key === claimDefinition.id);
|
|
1130
1118
|
if (groups.length !== 1) {
|
|
1131
1119
|
fail(`Claim Stake definition ${String(claimDefinition.id)} does not have exactly one coherent group in Planet ${stake.body.address}'s Claim Stake Body data. Refresh the Body before retrying deconstruction.`, stake.body.address);
|
|
1132
1120
|
}
|
|
@@ -1143,7 +1131,7 @@ export async function planDeconstructClaimStake(ctx, stake, input) {
|
|
|
1143
1131
|
}
|
|
1144
1132
|
const claimStakeIndex = chronologyMatches[0].index;
|
|
1145
1133
|
const tierIndex = TIER_INDEX[claimDefinition.tier];
|
|
1146
|
-
const plotCapacities =
|
|
1134
|
+
const plotCapacities = planet.details.claimStakePlots.map((row) => row[tierIndex]);
|
|
1147
1135
|
if (plotCapacities.some((capacity, index) => index > 0 && capacity < plotCapacities[index - 1])) {
|
|
1148
1136
|
fail(`Planet ${stake.body.address} Claim Stake plot capacity is not coherent across Starbase levels for definition tier ${claimDefinition.tier}. Refresh the Body and verify the pinned binding version before retrying deconstruction.`, stake.body.address);
|
|
1149
1137
|
}
|
|
@@ -1152,31 +1140,8 @@ export async function planDeconstructClaimStake(ctx, stake, input) {
|
|
|
1152
1140
|
}
|
|
1153
1141
|
const plotLevelIndex = plotCapacities.findIndex((capacity) => claimStakeIndex < capacity);
|
|
1154
1142
|
const claimStakeLevel = CLAIM_STAKE_PLOT_LEVELS[plotLevelIndex];
|
|
1155
|
-
const
|
|
1156
|
-
const
|
|
1157
|
-
const [characterCandidate, systemCandidate, playerCandidate] = await readContextRawAccounts(ctx, [character, system, player]);
|
|
1158
|
-
const characterAccount = requireAccount(characterCandidate, character, `Canonical Character ${character} is missing. Refresh the Profile Character and retry deconstruction.`);
|
|
1159
|
-
const systemAccount = requireAccount(systemCandidate, system, `Star System ${system} is missing. Refresh the Claim Stake's System and retry deconstruction.`);
|
|
1160
|
-
const playerAccount = requireAccount(playerCandidate, player, `Starbase Player ${player} is missing for authorization Profile ${options.authorization.profile}. Register or refresh it in the Claim Stake's System before retrying deconstruction.`);
|
|
1161
|
-
const currentCharacter = characterAccountDefinition.decode(characterAccount);
|
|
1162
|
-
const currentSystem = starSystemAccountDefinition.decode(systemAccount);
|
|
1163
|
-
const currentPlayer = starbasePlayerAccountDefinition.decode(playerAccount);
|
|
1164
|
-
if (currentCharacter.playerProfile !== options.authorization.profile ||
|
|
1165
|
-
currentCharacter.game !== game) {
|
|
1166
|
-
fail(`Character ${character} does not match authorization Profile ${options.authorization.profile} and configured Game ${game}. Refresh the Character and retry deconstruction.`, character);
|
|
1167
|
-
}
|
|
1168
|
-
if (currentSystem.game !== game ||
|
|
1169
|
-
currentSystem.sequenceId !== stake.systemSeqId ||
|
|
1170
|
-
currentSystem.starbase === undefined ||
|
|
1171
|
-
!currentSystem.celestialBodyAddresses.includes(stake.body.address) ||
|
|
1172
|
-
(await deriveStarSystemAddress(game, currentSystem.systemId)) !== system) {
|
|
1173
|
-
fail(`Star System ${system} does not match the configured Game, canonical identity, Claim Stake generation, or an active nested Starbase. Refresh the System and retry deconstruction.`, system);
|
|
1174
|
-
}
|
|
1175
|
-
if (currentPlayer.playerProfile !== options.authorization.profile ||
|
|
1176
|
-
currentPlayer.character !== character ||
|
|
1177
|
-
currentPlayer.system !== system) {
|
|
1178
|
-
fail(`Starbase Player ${player} does not match the authorization Profile, canonical Character, and Claim Stake System. Refresh it and retry deconstruction.`, player);
|
|
1179
|
-
}
|
|
1143
|
+
const { currentSystem, extraAccounts: [characterCandidate], player, playerAccount, systemAccount, } = await readStakeStarbase(ctx, stake, game, system, character, profile, action, [character]);
|
|
1144
|
+
const { account: characterAccount } = requireProfileCharacter(characterCandidate, character, profile, game, action);
|
|
1180
1145
|
const description = `Deconstruct Claim Stake ${stake.address} and discard its bundled hub ${bundledHub.name}; rent settlement, cargo return, account closure, and any refund remain chain-authoritative.`;
|
|
1181
1146
|
return createPlan({
|
|
1182
1147
|
kind: 'claim-stakes.deconstruct',
|
|
@@ -1184,9 +1149,9 @@ export async function planDeconstructClaimStake(ctx, stake, input) {
|
|
|
1184
1149
|
precondition(freshGame.account, `The configured Game retains Claim Stake definition ${String(claimDefinition.id)}, bundled hub ${String(bundledHub.id)}, and their current generations.`),
|
|
1185
1150
|
precondition(stakeAccount, `Claim Stake ${stake.address} retains its ownership, active state, definitions, bundled hub, Body, and System generation.`),
|
|
1186
1151
|
precondition(bodyAccount, `The Planet retains Claim Stake ${stake.address} at chronology index ${String(claimStakeIndex)} in definition group ${String(claimDefinition.id)}.`),
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1152
|
+
characterPrecondition(characterAccount, profile),
|
|
1153
|
+
systemPrecondition(systemAccount, currentSystem.name),
|
|
1154
|
+
playerPrecondition(playerAccount, player),
|
|
1190
1155
|
]),
|
|
1191
1156
|
steps: [
|
|
1192
1157
|
{
|
|
@@ -1230,86 +1195,34 @@ export async function planDeconstructClaimStake(ctx, stake, input) {
|
|
|
1230
1195
|
export async function planTopUpClaimStakeRent(ctx, stake, input) {
|
|
1231
1196
|
const options = validateTopUpRentOptions(input);
|
|
1232
1197
|
const game = requireGameAddress(ctx);
|
|
1198
|
+
const { profile } = options.authorization;
|
|
1233
1199
|
const loadedState = stake.state;
|
|
1234
1200
|
if (loadedState.kind === 'deactivated') {
|
|
1235
1201
|
fail(`Claim Stake ${stake.address} is not placed. Refresh it and choose an active Claim Stake or pending building design before topping up rent.`, stake.address);
|
|
1236
1202
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
if (stake.ownerCharacter.address !== character) {
|
|
1242
|
-
fail(`Claim Stake ${stake.address} does not belong to authorization Profile ${options.authorization.profile}'s canonical Character ${character}. Choose an owned Claim Stake, refresh it, and retry.`, stake.address);
|
|
1243
|
-
}
|
|
1244
|
-
const firstAccounts = await readContextRawAccounts(ctx, [
|
|
1245
|
-
stake.address,
|
|
1246
|
-
stake.body.address,
|
|
1247
|
-
character,
|
|
1248
|
-
]);
|
|
1249
|
-
const stakeAccount = requireAccount(firstAccounts[0], stake.address, `Claim Stake ${stake.address} is missing. Refresh it and retry the rent top-up.`);
|
|
1250
|
-
const bodyAccount = requireAccount(firstAccounts[1], stake.body.address, `Celestial Body ${stake.body.address} is missing. Refresh the Claim Stake Body and retry the rent top-up.`);
|
|
1251
|
-
const characterAccount = requireAccount(firstAccounts[2], character, `Canonical Character ${character} is missing. Refresh the Profile Character and retry the rent top-up.`);
|
|
1252
|
-
const currentStake = claimStakeAccountDefinition.decode(stakeAccount);
|
|
1253
|
-
const currentBody = celestialBodyAccountDefinition.decode(bodyAccount);
|
|
1254
|
-
const currentCharacter = characterAccountDefinition.decode(characterAccount);
|
|
1255
|
-
if (currentStake.game !== game ||
|
|
1256
|
-
currentStake.body !== stake.body.address ||
|
|
1257
|
-
currentStake.ownerCharacter !== character ||
|
|
1258
|
-
currentStake.claimStakeDefinitionId !== stake.claimStakeDefinitionId ||
|
|
1259
|
-
currentStake.claimStakeDefinitionSeqId !==
|
|
1260
|
-
stake.claimStakeDefinitionSeqId ||
|
|
1261
|
-
currentStake.buildingDefinitionSeqId !== stake.buildingDefinitionSeqId ||
|
|
1262
|
-
currentStake.systemSeqId !== stake.systemSeqId ||
|
|
1203
|
+
const character = await requireOwnedStake(stake, game, profile);
|
|
1204
|
+
const action = 'the rent top-up';
|
|
1205
|
+
const { bodyAccount, currentBody, currentStake, extraAccounts: [characterCandidate], stakeAccount, } = await readOwnedStake(ctx, stake, action, [character]);
|
|
1206
|
+
if (!sameOwnedStake(currentStake, stake, game, character) ||
|
|
1263
1207
|
currentStake.rentBalanceRaw !== stake.rentBalanceRaw ||
|
|
1264
|
-
!sameQuantities(currentStake.buildings, stake.buildings) ||
|
|
1265
1208
|
!sameStakeState(currentStake.state, loadedState)) {
|
|
1266
1209
|
fail(`Claim Stake ${stake.address} changed or does not match its loaded ownership, Body, definitions, buildings, rent balance, placed state, or System generation. Refresh it and retry the rent top-up.`, stake.address);
|
|
1267
1210
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
currentBody.systemSequenceId !== stake.systemSeqId ||
|
|
1271
|
-
(await deriveCelestialBodyAddress(game, currentBody.id)) !==
|
|
1272
|
-
stake.body.address) {
|
|
1273
|
-
fail(`Celestial Body ${stake.body.address} does not match the Claim Stake's configured Game, canonical Planet identity, or System generation. Refresh the Body and Claim Stake before retrying the rent top-up.`, stake.body.address);
|
|
1274
|
-
}
|
|
1275
|
-
if (currentCharacter.playerProfile !== options.authorization.profile ||
|
|
1276
|
-
currentCharacter.game !== game) {
|
|
1277
|
-
fail(`Character ${character} does not match authorization Profile ${options.authorization.profile} and configured Game ${game}. Refresh the Character and retry the rent top-up.`, character);
|
|
1278
|
-
}
|
|
1211
|
+
const { system } = await requireStakePlanet(currentBody, stake, game, action);
|
|
1212
|
+
const { account: characterAccount, current: currentCharacter } = requireProfileCharacter(characterCandidate, character, profile, game, action);
|
|
1279
1213
|
if (currentCharacter.atlasRaw < options.amount) {
|
|
1280
1214
|
fail(`Character ${character} has only ${String(currentCharacter.atlasRaw)} known raw ATLAS, below the requested ${String(options.amount)} raw rent top-up. Reduce the amount or add ATLAS, refresh the Character, and retry.`, character);
|
|
1281
1215
|
}
|
|
1282
|
-
const
|
|
1283
|
-
const player = await deriveStarbasePlayerAddress(system, character);
|
|
1284
|
-
const [systemCandidate, playerCandidate] = await readContextRawAccounts(ctx, [
|
|
1285
|
-
system,
|
|
1286
|
-
player,
|
|
1287
|
-
]);
|
|
1288
|
-
const systemAccount = requireAccount(systemCandidate, system, `Star System ${system} is missing. Refresh the Claim Stake's System and retry the rent top-up.`);
|
|
1289
|
-
const playerAccount = requireAccount(playerCandidate, player, `Starbase Player ${player} is missing for authorization Profile ${options.authorization.profile}. Register or refresh it in the Claim Stake's System before retrying the rent top-up.`);
|
|
1290
|
-
const currentSystem = starSystemAccountDefinition.decode(systemAccount);
|
|
1291
|
-
const currentPlayer = starbasePlayerAccountDefinition.decode(playerAccount);
|
|
1292
|
-
if (currentSystem.game !== game ||
|
|
1293
|
-
currentSystem.sequenceId !== stake.systemSeqId ||
|
|
1294
|
-
currentSystem.starbase === undefined ||
|
|
1295
|
-
!currentSystem.celestialBodyAddresses.includes(stake.body.address) ||
|
|
1296
|
-
(await deriveStarSystemAddress(game, currentSystem.systemId)) !== system) {
|
|
1297
|
-
fail(`Star System ${system} does not match the configured Game, canonical identity, Claim Stake generation, active nested Starbase, or Claim Stake Body relationship. Refresh the System and retry the rent top-up.`, system);
|
|
1298
|
-
}
|
|
1299
|
-
if (currentPlayer.playerProfile !== options.authorization.profile ||
|
|
1300
|
-
currentPlayer.character !== character ||
|
|
1301
|
-
currentPlayer.system !== system) {
|
|
1302
|
-
fail(`Starbase Player ${player} does not match the authorization Profile, canonical Character, and Claim Stake System. Refresh it and retry the rent top-up.`, player);
|
|
1303
|
-
}
|
|
1216
|
+
const { currentSystem, player, playerAccount, systemAccount } = await readStakeStarbase(ctx, stake, game, system, character, profile, action);
|
|
1304
1217
|
const description = `Top up Claim Stake ${stake.address} rent with ${String(options.amount)} raw rent units; rent arithmetic remains chain-authoritative.`;
|
|
1305
1218
|
return createPlan({
|
|
1306
1219
|
kind: 'claim-stakes.top-up-rent',
|
|
1307
1220
|
preconditions: await Promise.all([
|
|
1308
1221
|
precondition(stakeAccount, `Claim Stake ${stake.address} retains its ownership, rent balance, placed state, Body, and System generation.`),
|
|
1309
|
-
|
|
1310
|
-
precondition(characterAccount, `The canonical Character remains assigned to Profile ${
|
|
1311
|
-
|
|
1312
|
-
|
|
1222
|
+
planetPrecondition(bodyAccount, system),
|
|
1223
|
+
precondition(characterAccount, `The canonical Character remains assigned to Profile ${profile} with at least ${String(options.amount)} known raw ATLAS.`),
|
|
1224
|
+
systemPrecondition(systemAccount, currentSystem.name),
|
|
1225
|
+
playerPrecondition(playerAccount, player),
|
|
1313
1226
|
]),
|
|
1314
1227
|
steps: [
|
|
1315
1228
|
{
|
|
@@ -1388,118 +1301,53 @@ function describeCargoRequests(direction, requests) {
|
|
|
1388
1301
|
export async function planStartClaimStakeFleetTransfer(ctx, fleet, stake, input) {
|
|
1389
1302
|
const options = validateStartFleetTransferOptions(input);
|
|
1390
1303
|
const game = requireGameAddress(ctx);
|
|
1304
|
+
const { profile } = options.authorization;
|
|
1391
1305
|
if (fleet.game !== game || stake.game !== game) {
|
|
1392
1306
|
fail(`Fleet ${fleet.address} and Claim Stake ${stake.address} must both belong to configured Game ${game}. Refresh both snapshots through this context and retry.`, fleet.game !== game ? fleet.address : stake.address);
|
|
1393
1307
|
}
|
|
1394
|
-
if (fleet.ownerProfile.address !==
|
|
1395
|
-
fail(`Fleet ${fleet.name} belongs to Profile ${fleet.ownerProfile.address}, not authorization Profile ${
|
|
1308
|
+
if (fleet.ownerProfile.address !== profile) {
|
|
1309
|
+
fail(`Fleet ${fleet.name} belongs to Profile ${fleet.ownerProfile.address}, not authorization Profile ${profile}. Use the owning Profile, refresh the Fleet, and retry.`, fleet.address);
|
|
1396
1310
|
}
|
|
1397
1311
|
if (fleet.state.kind !== 'idle') {
|
|
1398
1312
|
fail(`Fleet ${fleet.name} is ${fleet.state.kind}, but starting a Claim Stake transfer requires an idle Fleet. Return it to idle state, refresh it, and retry.`, fleet.address);
|
|
1399
1313
|
}
|
|
1314
|
+
const crewFailure = `Fleet ${fleet.name} has no valid normalized crew and cannot start a Claim Stake transfer. Crew the Fleet, refresh it, and retry.`;
|
|
1400
1315
|
if (!Number.isSafeInteger(fleet.crewCount) || fleet.crewCount <= 0) {
|
|
1401
|
-
fail(
|
|
1316
|
+
fail(crewFailure, fleet.address);
|
|
1402
1317
|
}
|
|
1403
1318
|
if (stake.state.kind !== 'active') {
|
|
1404
1319
|
fail(`Claim Stake ${stake.address} is ${stake.state.kind}, but starting a Fleet transfer requires an active Claim Stake. Activate or restore it, refresh it, and retry.`, stake.address);
|
|
1405
1320
|
}
|
|
1406
|
-
const character = await
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
}
|
|
1410
|
-
const [fleetCandidate, stakeCandidate, bodyCandidate, characterCandidate] = await readContextRawAccounts(ctx, [
|
|
1411
|
-
fleet.address,
|
|
1412
|
-
stake.address,
|
|
1413
|
-
stake.body.address,
|
|
1414
|
-
character,
|
|
1415
|
-
]);
|
|
1416
|
-
const fleetAccount = requireAccount(fleetCandidate, fleet.address, `Fleet ${fleet.name} is missing. Refresh it and retry the Claim Stake transfer.`);
|
|
1417
|
-
const stakeAccount = requireAccount(stakeCandidate, stake.address, `Claim Stake ${stake.address} is missing. Refresh it and retry the Fleet transfer.`);
|
|
1418
|
-
const bodyAccount = requireAccount(bodyCandidate, stake.body.address, `Celestial Body ${stake.body.address} is missing. Refresh the Claim Stake Body and retry the Fleet transfer.`);
|
|
1419
|
-
const characterAccount = requireAccount(characterCandidate, character, `Canonical Character ${character} is missing. Refresh the Profile Character and retry the Claim Stake transfer.`);
|
|
1321
|
+
const character = await requireOwnedStake(stake, game, profile);
|
|
1322
|
+
const action = 'the Fleet transfer';
|
|
1323
|
+
const { bodyAccount, currentBody, currentStake, extraAccounts: [fleetCandidate, characterCandidate], stakeAccount, } = await readOwnedStake(ctx, stake, action, [fleet.address, character]);
|
|
1324
|
+
const fleetAccount = requireAccount(fleetCandidate, fleet.address, `Fleet ${fleet.name} is missing. Refresh it and retry the Fleet transfer.`);
|
|
1420
1325
|
const currentFleet = fleetAccountDefinition.decode(fleetAccount);
|
|
1421
|
-
const currentStake = claimStakeAccountDefinition.decode(stakeAccount);
|
|
1422
|
-
const currentBody = celestialBodyAccountDefinition.decode(bodyAccount);
|
|
1423
|
-
const currentCharacter = characterAccountDefinition.decode(characterAccount);
|
|
1424
1326
|
if (!Number.isSafeInteger(currentFleet.crewCount) ||
|
|
1425
1327
|
currentFleet.crewCount <= 0) {
|
|
1426
|
-
fail(
|
|
1328
|
+
fail(crewFailure, fleet.address);
|
|
1427
1329
|
}
|
|
1428
|
-
if (currentFleet
|
|
1429
|
-
currentFleet.ownerProfile !== options.authorization.profile ||
|
|
1430
|
-
currentFleet.name !== fleet.name ||
|
|
1330
|
+
if (!sameTransferFleet(currentFleet, fleet, game, profile) ||
|
|
1431
1331
|
currentFleet.state.kind !== 'idle' ||
|
|
1432
1332
|
currentFleet.crewCount !== fleet.crewCount ||
|
|
1433
|
-
(await deriveFleetAddress(currentFleet)) !== fleet.address
|
|
1434
|
-
currentFleet.ammoBank.cargoId !== fleet.ammo.id ||
|
|
1435
|
-
currentFleet.ammoBank.amount !== fleet.ammo.amount ||
|
|
1436
|
-
currentFleet.fuelTank.cargoId !== fleet.fuel.id ||
|
|
1437
|
-
currentFleet.fuelTank.amount !== fleet.fuel.amount ||
|
|
1438
|
-
currentFleet.cargoHold.storageCost !== fleet.cargoHold.storageCost ||
|
|
1439
|
-
!sameCargoAmounts(currentFleet.cargoHold.amounts, fleet.cargoHold.items) ||
|
|
1440
|
-
currentFleet.stats.cargo.ammoCapacity !== fleet.capacities.ammo.total ||
|
|
1441
|
-
currentFleet.stats.cargo.fuelCapacity !== fleet.capacities.fuel.total ||
|
|
1442
|
-
currentFleet.stats.cargo.cargoCapacity !== fleet.capacities.cargo.total) {
|
|
1333
|
+
(await deriveFleetAddress(currentFleet)) !== fleet.address) {
|
|
1443
1334
|
fail(`Fleet ${fleet.name} changed or no longer matches its loaded Game, owner, canonical address, name, idle state, normalized crew, cargo, or capacities. Refresh it and create a new Claim Stake transfer Plan.`, fleet.address);
|
|
1444
1335
|
}
|
|
1445
|
-
if (currentStake
|
|
1446
|
-
currentStake.ownerCharacter !== character ||
|
|
1447
|
-
currentStake.body !== stake.body.address ||
|
|
1448
|
-
currentStake.systemSeqId !== stake.systemSeqId ||
|
|
1449
|
-
currentStake.state.kind !== 'active' ||
|
|
1450
|
-
currentStake.production.cargoPod.storageCost !==
|
|
1451
|
-
stake.resources.capacity.usedRaw ||
|
|
1452
|
-
currentStake.production.resourceCapacityRaw !==
|
|
1453
|
-
stake.resources.capacity.totalRaw ||
|
|
1454
|
-
!sameCargoAmounts(currentStake.production.cargoPod.amounts, stake.resources.inventory.items.map(({ id, quantityRaw }) => ({
|
|
1455
|
-
amount: quantityRaw,
|
|
1456
|
-
id,
|
|
1457
|
-
})))) {
|
|
1336
|
+
if (!sameTransferStake(currentStake, stake, game, character)) {
|
|
1458
1337
|
fail(`Claim Stake ${stake.address} changed or no longer matches its loaded Game, owner, Body, active state, resources, capacity, or System generation. Refresh it and create a new Fleet transfer Plan.`, stake.address);
|
|
1459
1338
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
(
|
|
1464
|
-
|
|
1465
|
-
fail(`Celestial Body ${stake.body.address} does not match the Claim Stake's configured Game, canonical Planet identity, or System generation. Refresh the Body and Claim Stake before retrying the Fleet transfer.`, stake.body.address);
|
|
1466
|
-
}
|
|
1467
|
-
if (currentCharacter.playerProfile !== options.authorization.profile ||
|
|
1468
|
-
currentCharacter.game !== game) {
|
|
1469
|
-
fail(`Character ${character} does not match authorization Profile ${options.authorization.profile} and configured Game ${game}. Refresh the Character and retry the Claim Stake transfer.`, character);
|
|
1470
|
-
}
|
|
1471
|
-
const system = currentBody.system;
|
|
1472
|
-
const player = await deriveStarbasePlayerAddress(system, character);
|
|
1473
|
-
const currencyCache = await deriveCurrencyCache(game).then(([address]) => address);
|
|
1474
|
-
const crewBinding = await deriveFleetCrewBindingAddress(fleet.address);
|
|
1475
|
-
const [systemCandidate, playerCandidate, currencyCandidate, crewCandidate] = await readContextRawAccounts(ctx, [
|
|
1476
|
-
system,
|
|
1477
|
-
player,
|
|
1478
|
-
currencyCache,
|
|
1479
|
-
crewBinding,
|
|
1339
|
+
const { system } = await requireStakePlanet(currentBody, stake, game, action);
|
|
1340
|
+
const { account: characterAccount } = requireProfileCharacter(characterCandidate, character, profile, game, action);
|
|
1341
|
+
const [currencyCache, crewBinding] = await Promise.all([
|
|
1342
|
+
claimStakeCurrencyCache(game),
|
|
1343
|
+
deriveFleetCrewBindingAddress(fleet.address),
|
|
1480
1344
|
]);
|
|
1481
|
-
const
|
|
1482
|
-
const
|
|
1483
|
-
const currencyAccount = requireAccount(currencyCandidate, currencyCache, `Canonical CurrencyCache ${currencyCache} is missing for configured Game ${game}. Refresh the Game currency configuration and retry the Fleet transfer.`);
|
|
1484
|
-
const currentSystem = starSystemAccountDefinition.decode(systemAccount);
|
|
1485
|
-
const currentPlayer = starbasePlayerAccountDefinition.decode(playerAccount);
|
|
1486
|
-
if (currentSystem.game !== game ||
|
|
1487
|
-
currentSystem.sequenceId !== stake.systemSeqId ||
|
|
1488
|
-
currentSystem.starbase === undefined ||
|
|
1489
|
-
!currentSystem.celestialBodyAddresses.includes(stake.body.address) ||
|
|
1490
|
-
(await deriveStarSystemAddress(game, currentSystem.systemId)) !== system) {
|
|
1491
|
-
fail(`Star System ${system} does not match the configured Game, canonical identity, Claim Stake generation, nested Starbase, or Claim Stake Body relationship. Refresh the System and retry the Fleet transfer.`, system);
|
|
1492
|
-
}
|
|
1493
|
-
if (currentPlayer.playerProfile !== options.authorization.profile ||
|
|
1494
|
-
currentPlayer.character !== character ||
|
|
1495
|
-
currentPlayer.system !== system) {
|
|
1496
|
-
fail(`Starbase Player ${player} does not match the authorization Profile, canonical Character, and Claim Stake System. Refresh it and retry the Fleet transfer.`, player);
|
|
1497
|
-
}
|
|
1498
|
-
await validateCurrencyCache(currencyAccount, game, 'Claim Stake Fleet transfer');
|
|
1345
|
+
const { currentSystem, extraAccounts: [currencyCandidate, crewCandidate], player, playerAccount, systemAccount, } = await readStakeStarbase(ctx, stake, game, system, character, profile, action, [currencyCache, crewBinding]);
|
|
1346
|
+
const currencyGuard = await requireCurrencyCache(currencyCandidate, currencyCache, game, action, 'Claim Stake Fleet transfer');
|
|
1499
1347
|
if (crewCandidate !== undefined) {
|
|
1500
1348
|
const currentCrewBinding = fleetCrewBindingAccountDefinition.decode(crewCandidate);
|
|
1501
1349
|
if (crewCandidate.address !== crewBinding ||
|
|
1502
|
-
!matchesFleetCrewBindingIdentity(currentCrewBinding, fleet.address,
|
|
1350
|
+
!matchesFleetCrewBindingIdentity(currentCrewBinding, fleet.address, profile)) {
|
|
1503
1351
|
fail(`FleetCrewBinding ${crewCandidate.address} does not match Fleet ${fleet.address}, its owner Profile, or its canonical PDA. Refresh the Fleet crew registration or remove the invalid binding before retrying.`, crewCandidate.address);
|
|
1504
1352
|
}
|
|
1505
1353
|
}
|
|
@@ -1542,22 +1390,17 @@ export async function planStartClaimStakeFleetTransfer(ctx, fleet, stake, input)
|
|
|
1542
1390
|
...(await Promise.all([
|
|
1543
1391
|
precondition(fleetAccount, `Fleet ${fleet.name} retains its owner, name, idle state, normalized crew, cargo balances, and destination capacities.`),
|
|
1544
1392
|
precondition(stakeAccount, `Claim Stake ${stake.address} retains its owner, active state, cargo balances, destination capacity, Body, and System generation.`),
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1393
|
+
planetPrecondition(bodyAccount, system),
|
|
1394
|
+
characterPrecondition(characterAccount, profile),
|
|
1395
|
+
systemPrecondition(systemAccount, currentSystem.name),
|
|
1396
|
+
playerPrecondition(playerAccount, player),
|
|
1549
1397
|
...(crewCandidate === undefined
|
|
1550
1398
|
? []
|
|
1551
1399
|
: [
|
|
1552
1400
|
precondition(crewCandidate, `Fleet ${fleet.name}'s optional FleetCrewBinding remains assigned to the Fleet and owner Profile.`),
|
|
1553
1401
|
]),
|
|
1554
1402
|
])),
|
|
1555
|
-
|
|
1556
|
-
kind: 'currency-cache',
|
|
1557
|
-
address: currencyCache,
|
|
1558
|
-
game,
|
|
1559
|
-
describes: 'The canonical CurrencyCache remains assigned to the configured Game; shared accounting may change.',
|
|
1560
|
-
},
|
|
1403
|
+
currencyGuard,
|
|
1561
1404
|
],
|
|
1562
1405
|
steps: [
|
|
1563
1406
|
{
|
|
@@ -1630,10 +1473,7 @@ export async function planRecoverClaimStakeFleetTransfer(ctx, fleet, stake, inpu
|
|
|
1630
1473
|
if (options.atUnixSeconds < fleet.state.endAtUnixSeconds) {
|
|
1631
1474
|
fail(`Fleet ${fleet.name}'s Claim Stake transfer lock has not elapsed at Unix timestamp ${String(options.atUnixSeconds)}; it ends at ${String(fleet.state.endAtUnixSeconds)}. Wait until the lock elapses, provide a current timestamp, and retry recovery.`, fleet.address);
|
|
1632
1475
|
}
|
|
1633
|
-
const character = await
|
|
1634
|
-
if (stake.ownerCharacter.address !== character) {
|
|
1635
|
-
fail(`Claim Stake ${stake.address} does not belong to authorization Profile ${options.authorization.profile}'s canonical Character ${character}. Supply the owned current Claim Stake and retry recovery.`, stake.address);
|
|
1636
|
-
}
|
|
1476
|
+
const character = await requireOwnedStake(stake, game, options.authorization.profile);
|
|
1637
1477
|
const [fleetCandidate, stakeCandidate] = await readContextRawAccounts(ctx, [
|
|
1638
1478
|
fleet.address,
|
|
1639
1479
|
stake.address,
|
|
@@ -1642,40 +1482,17 @@ export async function planRecoverClaimStakeFleetTransfer(ctx, fleet, stake, inpu
|
|
|
1642
1482
|
const stakeAccount = requireAccount(stakeCandidate, stake.address, `Claim Stake ${stake.address} is missing. Refresh it and retry Fleet transfer recovery.`);
|
|
1643
1483
|
const currentFleet = fleetAccountDefinition.decode(fleetAccount);
|
|
1644
1484
|
const currentStake = claimStakeAccountDefinition.decode(stakeAccount);
|
|
1645
|
-
if (currentFleet
|
|
1646
|
-
currentFleet.ownerProfile !== options.authorization.profile ||
|
|
1647
|
-
currentFleet.name !== fleet.name ||
|
|
1485
|
+
if (!sameTransferFleet(currentFleet, fleet, game, options.authorization.profile) ||
|
|
1648
1486
|
currentFleet.state.kind !== 'claimStakeTransfer' ||
|
|
1649
1487
|
currentFleet.state.claimStake !== stake.address ||
|
|
1650
1488
|
currentFleet.state.planet !== stake.body.address ||
|
|
1651
1489
|
currentFleet.state.endAtUnixSeconds !== fleet.state.endAtUnixSeconds ||
|
|
1652
1490
|
!sameCargoAmounts(currentFleet.state.load, fleet.state.load) ||
|
|
1653
1491
|
!sameCargoAmounts(currentFleet.state.unload, fleet.state.unload) ||
|
|
1654
|
-
(await deriveFleetAddress(currentFleet)) !== fleet.address
|
|
1655
|
-
currentFleet.ammoBank.cargoId !== fleet.ammo.id ||
|
|
1656
|
-
currentFleet.ammoBank.amount !== fleet.ammo.amount ||
|
|
1657
|
-
currentFleet.fuelTank.cargoId !== fleet.fuel.id ||
|
|
1658
|
-
currentFleet.fuelTank.amount !== fleet.fuel.amount ||
|
|
1659
|
-
currentFleet.cargoHold.storageCost !== fleet.cargoHold.storageCost ||
|
|
1660
|
-
!sameCargoAmounts(currentFleet.cargoHold.amounts, fleet.cargoHold.items) ||
|
|
1661
|
-
currentFleet.stats.cargo.ammoCapacity !== fleet.capacities.ammo.total ||
|
|
1662
|
-
currentFleet.stats.cargo.fuelCapacity !== fleet.capacities.fuel.total ||
|
|
1663
|
-
currentFleet.stats.cargo.cargoCapacity !== fleet.capacities.cargo.total) {
|
|
1492
|
+
(await deriveFleetAddress(currentFleet)) !== fleet.address) {
|
|
1664
1493
|
fail(`Fleet ${fleet.name} changed or no longer matches its loaded owner, Game, canonical address, Claim Stake transfer snapshot, cargo, or capacities. Refresh it and retry recovery.`, fleet.address);
|
|
1665
1494
|
}
|
|
1666
|
-
if (currentStake
|
|
1667
|
-
currentStake.ownerCharacter !== character ||
|
|
1668
|
-
currentStake.body !== stake.body.address ||
|
|
1669
|
-
currentStake.systemSeqId !== stake.systemSeqId ||
|
|
1670
|
-
currentStake.state.kind !== 'active' ||
|
|
1671
|
-
currentStake.production.cargoPod.storageCost !==
|
|
1672
|
-
stake.resources.capacity.usedRaw ||
|
|
1673
|
-
currentStake.production.resourceCapacityRaw !==
|
|
1674
|
-
stake.resources.capacity.totalRaw ||
|
|
1675
|
-
!sameCargoAmounts(currentStake.production.cargoPod.amounts, stake.resources.inventory.items.map(({ id, quantityRaw }) => ({
|
|
1676
|
-
amount: quantityRaw,
|
|
1677
|
-
id,
|
|
1678
|
-
})))) {
|
|
1495
|
+
if (!sameTransferStake(currentStake, stake, game, character)) {
|
|
1679
1496
|
fail(`Claim Stake ${stake.address} changed or no longer matches its loaded owner, Game, Body, active state, resources, capacity, or System generation. Refresh it and retry Fleet transfer recovery.`, stake.address);
|
|
1680
1497
|
}
|
|
1681
1498
|
let reason;
|