@black-market/sdk 0.15.0 → 0.15.1
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.
- package/README.md +9 -8
- package/dist/lifecycle/auto-compound.d.ts +21 -0
- package/dist/lifecycle/auto-compound.d.ts.map +1 -0
- package/dist/lifecycle/auto-compound.js +34 -0
- package/dist/lifecycle/auto-compound.js.map +1 -0
- package/dist/lifecycle/calibration.d.ts +2 -4
- package/dist/lifecycle/calibration.d.ts.map +1 -1
- package/dist/lifecycle/calibration.js +1 -26
- package/dist/lifecycle/calibration.js.map +1 -1
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.d.ts +73 -0
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.d.ts.map +1 -0
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.js +75 -0
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.js.map +1 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.d.ts +73 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.d.ts.map +1 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.js +75 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.js.map +1 -0
- package/dist/lifecycle/gas-profile.d.ts +17 -13
- package/dist/lifecycle/gas-profile.d.ts.map +1 -1
- package/dist/lifecycle/gas-profile.js +125 -115
- package/dist/lifecycle/gas-profile.js.map +1 -1
- package/dist/lifecycle/index.d.ts +4 -4
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -4
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/markets.d.ts +2 -0
- package/dist/lifecycle/markets.d.ts.map +1 -1
- package/dist/lifecycle/markets.js +30 -3
- package/dist/lifecycle/markets.js.map +1 -1
- package/dist/lifecycle/opening-buys.js +1 -1
- package/dist/lifecycle/opening-buys.js.map +1 -1
- package/dist/lifecycle/planner.d.ts +6 -9
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +102 -207
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +7 -3
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +10 -1
- package/dist/lifecycle/presets.js.map +1 -1
- package/dist/lifecycle/rpc.d.ts +3 -13
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +11 -37
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +1 -2
- package/dist/lifecycle/snapshot.js.map +1 -1
- package/dist/lifecycle/transactions.d.ts +10 -3
- package/dist/lifecycle/transactions.d.ts.map +1 -1
- package/dist/lifecycle/transactions.js +40 -10
- package/dist/lifecycle/transactions.js.map +1 -1
- package/dist/lifecycle/types.d.ts +32 -107
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js +3 -3
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/lifecycle/ufg.d.ts +1 -1
- package/dist/lifecycle/ufg.d.ts.map +1 -1
- package/dist/lifecycle/ufg.js +1 -1
- package/dist/lifecycle/ufg.js.map +1 -1
- package/docs/launch-lifecycle.md +43 -0
- package/docs/lifecycle-gas-profiles.md +1 -1
- package/docs/reference.md +56 -2
- package/examples/.env.example +0 -3
- package/examples/README.md +10 -10
- package/examples/launch-example-support.ts +4 -9
- package/examples/launch-example.ts +12 -28
- package/examples/launch-lifecycle-smoke.ts +10 -22
- package/examples/launch.ts +8 -11
- package/package.json +1 -1
- package/src/lifecycle/auto-compound.ts +38 -0
- package/src/lifecycle/calibration.ts +3 -23
- package/src/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.ts +77 -0
- package/src/lifecycle/deployments/auto-compound-static-pool-v1-20261028.ts +77 -0
- package/src/lifecycle/gas-profile.ts +117 -106
- package/src/lifecycle/index.ts +4 -4
- package/src/lifecycle/markets.ts +25 -3
- package/src/lifecycle/opening-buys.ts +1 -1
- package/src/lifecycle/planner.ts +98 -190
- package/src/lifecycle/presets.ts +10 -1
- package/src/lifecycle/rpc.ts +10 -32
- package/src/lifecycle/snapshot.ts +1 -2
- package/src/lifecycle/transactions.ts +39 -9
- package/src/lifecycle/types.ts +38 -87
- package/src/lifecycle/ufg.ts +1 -1
- package/dist/lifecycle/fork.d.ts +0 -15
- package/dist/lifecycle/fork.d.ts.map +0 -1
- package/dist/lifecycle/fork.js +0 -36
- package/dist/lifecycle/fork.js.map +0 -1
- package/dist/lifecycle/simulation.d.ts +0 -17
- package/dist/lifecycle/simulation.d.ts.map +0 -1
- package/dist/lifecycle/simulation.js +0 -605
- package/dist/lifecycle/simulation.js.map +0 -1
- package/src/lifecycle/fork.ts +0 -36
- package/src/lifecycle/simulation.ts +0 -543
|
@@ -5,10 +5,9 @@ import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleErc20Abi } from "./ab
|
|
|
5
5
|
import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, LIFECYCLE_MAX_ERC404_SUPPLY, parseLaunchPlan, serializeLaunchPlan } from "./schema.js";
|
|
6
6
|
import { readLaunchProgress, readLifecycleExecutionProgress, readLifecycleHeadProgress, readLifecycleProfiles, readLifecycleTokenAtBlock, readPoolBoundHookDeployment } from "./progress.js";
|
|
7
7
|
import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion } from "./markets.js";
|
|
8
|
-
import {
|
|
9
|
-
import { observedFields, prepareLaunchSimulation } from "./simulation.js";
|
|
8
|
+
import { lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleStage, nitroPosterGasBound, readLifecycleChainId, readLifecycleContract, readNitroPrices, registerLifecycleFinalCheck, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
10
9
|
import { LifecyclePlanningError } from "./types.js";
|
|
11
|
-
import { deriveKnownPoolBoundHookDeployment,
|
|
10
|
+
import { deriveKnownPoolBoundHookDeployment, validateBuySlippageBps } from "./calibration.js";
|
|
12
11
|
import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
|
|
13
12
|
import { buildLifecycleCommand, packPreparationBatches } from "./transactions.js";
|
|
14
13
|
import { assertLifecycleMarketAllocation, assertLifecycleSupplyAllocation } from "./allocations.js";
|
|
@@ -17,8 +16,28 @@ import { buildUfgCancelTransaction, validateUfgLaunchPlan } from "./ufg.js";
|
|
|
17
16
|
import { readLifecyclePlanningBlock } from "./snapshot.js";
|
|
18
17
|
import { mineLifecycleHooks } from "./mining.js";
|
|
19
18
|
import { computeLifecycleOpeningBuys, hasProfiledOpeningMath } from "./opening-buys.js";
|
|
20
|
-
import {
|
|
19
|
+
import { assessLifecycleTransactions, deriveLifecycleLaunchCapacity } from "./gas-profile.js";
|
|
21
20
|
const defaultPlanReadContext = {};
|
|
21
|
+
function observedFields(value) {
|
|
22
|
+
const keys = Object.keys(value);
|
|
23
|
+
const values = keys.map((key) => value[key]);
|
|
24
|
+
return () => keys.length === Object.keys(value).length && keys.every((key, index) => value[key] === values[index]);
|
|
25
|
+
}
|
|
26
|
+
async function selectLaunchMode(requested, plan, capacity, limits, client, block) {
|
|
27
|
+
if (requested !== "atomic" || plan.markets.length !== 1 || plan.funding.some((funding) => funding.kind === LifecycleFundingKind.Swap) ||
|
|
28
|
+
capacity?.atomic === undefined || capacity.bounds.atomicComputeGasUpperBound === undefined)
|
|
29
|
+
return "staged";
|
|
30
|
+
const poster = limits.protocol === "nitro" ? nitroPosterGasBound(capacity.atomic, await readNitroPrices(client, block)).posterGas : 0n;
|
|
31
|
+
return capacity.bounds.atomicComputeGasUpperBound + poster <= limits.executionGasCeiling ? "atomic" : "staged";
|
|
32
|
+
}
|
|
33
|
+
async function preparationBatches(plan, prepared, ufg, limits, client, block) {
|
|
34
|
+
if (prepared === plan.markets.length)
|
|
35
|
+
return prepared === 0 ? [] : [prepared];
|
|
36
|
+
const route = ufg === undefined ? undefined : { account: ufgPlanPrincipal(plan), config: ufg.config };
|
|
37
|
+
const sample = buildLifecycleCommand(plan, { kind: "prepare", first: prepared, count: 1 }, route);
|
|
38
|
+
const poster = limits.protocol === "nitro" ? nitroPosterGasBound(sample, await readNitroPrices(client, block)).posterGas : 0n;
|
|
39
|
+
return packPreparationBatches(plan, prepared, limits.executionGasCeiling - poster, limits.headroomBps);
|
|
40
|
+
}
|
|
22
41
|
function validatePlanShape(plan) {
|
|
23
42
|
// Encoding checks all exact integer widths, addresses, byte strings and tuple fields.
|
|
24
43
|
const commitment = hashLaunchPlan(plan);
|
|
@@ -176,16 +195,14 @@ function orderedTransactions(plan, mode, progress, approvals, batches, ufg, capa
|
|
|
176
195
|
const transactions = progress.canonical.phase === LifecyclePhase.None ? [...approvals, ...commands] : commands;
|
|
177
196
|
return transactions.map((transaction, index) => ({ ...transaction, calldataBytes: (transaction.data.length - 2) / 2, dependencies: index === 0 ? [] : [transactions[index - 1].id] }));
|
|
178
197
|
}
|
|
179
|
-
function attachEstimates(transactions,
|
|
198
|
+
function attachEstimates(transactions, assessment) {
|
|
180
199
|
return transactions.map((transaction, index) => {
|
|
181
200
|
if (index !== 0)
|
|
182
201
|
return transaction;
|
|
183
|
-
const admission = { admitted: simulation.admitted, confidence: simulation.confidence, executionProof: simulation.executionProof, protocolFit: simulation.protocolFit,
|
|
184
|
-
blockNumber: simulation.blockNumber, blockHash: simulation.blockHash, account: simulation.account, chainId: simulation.chainId, reason: simulation.reason, limits: simulation.limits };
|
|
185
202
|
if (transaction.kind === "approve" || transaction.kind === "approve-reset")
|
|
186
|
-
return { ...transaction,
|
|
187
|
-
const estimate =
|
|
188
|
-
return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate,
|
|
203
|
+
return { ...transaction, assessment };
|
|
204
|
+
const estimate = assessment.estimate;
|
|
205
|
+
return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate, assessment };
|
|
189
206
|
});
|
|
190
207
|
}
|
|
191
208
|
const reviewedTransactionKinds = ["approve-reset", "approve", "atomic", "begin", "prepare", "activate", "cancel"];
|
|
@@ -215,16 +232,16 @@ function assertReviewedTransactionUnchanged(options, reviewed, next) {
|
|
|
215
232
|
if (options.planned.ufg !== undefined)
|
|
216
233
|
assertUfgLaunchAccount(options.planned.plan, options.planned.account);
|
|
217
234
|
if (options.planned.account.toLowerCase() !== next.from.toLowerCase() || options.planned.ufg === undefined && options.planned.plan.creator.toLowerCase() !== next.from.toLowerCase()) {
|
|
218
|
-
throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account changed
|
|
235
|
+
throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account changed while preparing the reviewed transaction");
|
|
219
236
|
}
|
|
220
237
|
if (options.planned.chainId !== BigInt(next.chainId) || options.planned.plan.chainId !== BigInt(next.chainId)) {
|
|
221
|
-
throw new LifecyclePlanningError("CHAIN_MISMATCH", "Execution chain changed
|
|
238
|
+
throw new LifecyclePlanningError("CHAIN_MISMATCH", "Execution chain changed while preparing the reviewed transaction");
|
|
222
239
|
}
|
|
223
240
|
const current = readReviewedNextTransaction(options.reviewedTransaction);
|
|
224
241
|
if (reviewed === undefined ? current !== undefined : current === undefined || !matchesReviewedNextTransaction(current, reviewed) || !matchesReviewedNextTransaction(next, reviewed) ||
|
|
225
242
|
reviewed.kind !== "approve" && reviewed.kind !== "approve-reset" &&
|
|
226
243
|
(current.gas !== reviewed.gas || current.gasPrice !== reviewed.gasPrice || next.gas !== reviewed.gas || next.gasPrice !== reviewed.gasPrice)) {
|
|
227
|
-
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_CHANGED", "Reviewed transaction changed
|
|
244
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_CHANGED", "Reviewed transaction changed while preparing it");
|
|
228
245
|
}
|
|
229
246
|
}
|
|
230
247
|
async function readTokenFactoryBinding(client, orchestrator, block) {
|
|
@@ -238,21 +255,6 @@ async function readTokenFactoryBinding(client, orchestrator, block) {
|
|
|
238
255
|
throw new LifecyclePlanningError("TOKEN_FACTORY_BINDING", "Token factory is not live and bound to the exact lifecycle core");
|
|
239
256
|
return { tokenFactory, tokenFactoryCodeHash: keccak256(code) };
|
|
240
257
|
}
|
|
241
|
-
function assertPlannedDeploymentBindings(stored, current) {
|
|
242
|
-
if (hashLaunchPlan(stored.plan).toLowerCase() !== stored.planHash.toLowerCase() || current.planHash.toLowerCase() !== stored.planHash.toLowerCase() || current.launchId.toLowerCase() !== stored.launchId.toLowerCase())
|
|
243
|
-
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its finalized commitment");
|
|
244
|
-
if (stored.ufg === undefined ? current.ufg !== undefined : current.ufg === undefined || hashUfgLaunchConfig(stored.ufg.config) !== stored.ufg.configHash || current.ufg.configHash !== stored.ufg.configHash)
|
|
245
|
-
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored UFG route or treasury config no longer matches its finalized commitment");
|
|
246
|
-
if (current.predictedToken.toLowerCase() !== stored.predictedToken.toLowerCase() || current.tokenFactory.toLowerCase() !== stored.tokenFactory.toLowerCase() || current.tokenFactoryCodeHash.toLowerCase() !== stored.tokenFactoryCodeHash.toLowerCase())
|
|
247
|
-
throw new LifecyclePlanningError("TOKEN_FACTORY_BINDING", "Final token prediction or its immutable factory code binding changed before execution");
|
|
248
|
-
if (!current.simulation.admitted || current.progress.canonical.phase === LifecyclePhase.Active || current.progress.canonical.phase === LifecyclePhase.Cancelled)
|
|
249
|
-
return;
|
|
250
|
-
if (stored.hookDeployments.length !== current.hookDeployments.length || stored.hookDeployments.some((deployment, index) => {
|
|
251
|
-
const fresh = current.hookDeployments[index];
|
|
252
|
-
return fresh === undefined || fresh.marketIndex !== deployment.marketIndex || fresh.deployer.toLowerCase() !== deployment.deployer.toLowerCase() || fresh.initCodeHash.toLowerCase() !== deployment.initCodeHash.toLowerCase() || fresh.salt.toLowerCase() !== deployment.salt.toLowerCase() || fresh.predictedHook.toLowerCase() !== deployment.predictedHook.toLowerCase();
|
|
253
|
-
}))
|
|
254
|
-
throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Final bound-hook deployer, initcode, salt or prediction changed before wallet execution");
|
|
255
|
-
}
|
|
256
258
|
function assertPlanningActive(signal) {
|
|
257
259
|
if (signal?.aborted)
|
|
258
260
|
throw Object.assign(new Error("Lifecycle launch planning cancelled"), { name: "AbortError" });
|
|
@@ -312,21 +314,8 @@ function assertFinalPlan(planned, signal) {
|
|
|
312
314
|
if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash)
|
|
313
315
|
throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed during asynchronous planning");
|
|
314
316
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (!controls.forceSimulation && profile?.profileOnly)
|
|
318
|
-
return profile.simulation;
|
|
319
|
-
const prepared = prepareLaunchSimulation(options, block, transactions.length !== 0, controls.immediate);
|
|
320
|
-
try {
|
|
321
|
-
const simulation = controls.diagnostic ? await prepared.measure(transactions) : await prepared(transactions, controls.finalEnvelope);
|
|
322
|
-
return profile === undefined ? simulation : { ...simulation, gasProfile: profile.simulation.gasProfile };
|
|
323
|
-
}
|
|
324
|
-
finally {
|
|
325
|
-
prepared.close();
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
/** One owner constructs and mines, optionally measures actual ordered buy outputs,
|
|
329
|
-
* then proves only the final protected commitment. No cached execution certificates. */
|
|
317
|
+
/** One owner mines constructor-bound salts and protects ordered buys with exact
|
|
318
|
+
* local curve math. Launch calldata is never executed during planning. */
|
|
330
319
|
export async function prepareAndPlanLifecycleLaunch(options) {
|
|
331
320
|
const { commitment, ufgConfigHash } = validatePlanningIntent(options);
|
|
332
321
|
if (options.buySlippageBps !== undefined)
|
|
@@ -366,9 +355,13 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
366
355
|
}
|
|
367
356
|
assertDraft();
|
|
368
357
|
let plan = { ...draft, markets };
|
|
369
|
-
let ownedProgress;
|
|
370
358
|
let openingBuys;
|
|
371
|
-
if (needsProtection
|
|
359
|
+
if (needsProtection) {
|
|
360
|
+
if (!hasProfiledOpeningMath(plan))
|
|
361
|
+
throw new LifecyclePlanningError("OPENING_BUY_NOT_PROFILED", "Exact opening-buy math is unavailable for this profile. Use a supported profile or update the SDK.");
|
|
362
|
+
const head = await readLifecycleHeadProgress(client, plan, options.account, block);
|
|
363
|
+
if (head.phase !== LifecyclePhase.None)
|
|
364
|
+
throw new LifecyclePlanningError("LAUNCH_ALREADY_STARTED", "Buy protection cannot replace an already committed launch plan");
|
|
372
365
|
const predictedToken = await readLifecycleTokenAtBlock(client, plan, block);
|
|
373
366
|
openingBuys = computeLifecycleOpeningBuys({ plan, predictedToken });
|
|
374
367
|
const factor = BigInt(10000 - options.buySlippageBps);
|
|
@@ -379,20 +372,7 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
379
372
|
return { ...buy, minTokenOut };
|
|
380
373
|
}) };
|
|
381
374
|
}
|
|
382
|
-
|
|
383
|
-
// A diagnostic minimum is never returned as an admitted or executable plan.
|
|
384
|
-
// Price guards, funding minima, recipients and all ordered budgets stay exact.
|
|
385
|
-
const diagnosticPlan = { ...plan, buys: plan.buys.map((buy) => ({ ...buy, minTokenOut: 1n })) };
|
|
386
|
-
const diagnostic = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan: diagnosticPlan }, client, { block, diagnostic: true }));
|
|
387
|
-
assertDraft();
|
|
388
|
-
if (diagnostic.progress.canonical.phase !== LifecyclePhase.None || diagnostic.progress.head.phase !== LifecyclePhase.None)
|
|
389
|
-
throw new LifecyclePlanningError("LAUNCH_ALREADY_STARTED", "Buy protection cannot replace an already committed launch plan");
|
|
390
|
-
plan = protectOrderedBuyMinimums(diagnostic, options.buySlippageBps);
|
|
391
|
-
// This is the real SDK-owned None observation, not an assumed new-launch
|
|
392
|
-
// state. No onchain commitment existed at this exact snapshot to replace.
|
|
393
|
-
ownedProgress = { ...diagnostic.progress, planHash: hashLaunchPlan(plan) };
|
|
394
|
-
}
|
|
395
|
-
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, { block, progress: ownedProgress, openingBuys }));
|
|
375
|
+
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, { block, openingBuys }));
|
|
396
376
|
assertDraft();
|
|
397
377
|
assertFinalPlan(planned, options.signal);
|
|
398
378
|
if (planned.progress.canonical.phase !== LifecyclePhase.Active && planned.progress.canonical.phase !== LifecyclePhase.Cancelled) {
|
|
@@ -402,15 +382,15 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
402
382
|
throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Final exact constructor binding differs from the one locally mined candidate");
|
|
403
383
|
}
|
|
404
384
|
}
|
|
405
|
-
if (needsProtection && !planned.
|
|
406
|
-
throw new LifecyclePlanningError("
|
|
385
|
+
if (needsProtection && !planned.assessment.ready)
|
|
386
|
+
throw new LifecyclePlanningError(planned.assessment.code ?? "PLAN_NOT_READY", planned.assessment.reason ?? "The launch could not be prepared. Refresh the review before continuing.", planned.assessment);
|
|
407
387
|
return planned;
|
|
408
388
|
});
|
|
409
389
|
assertDraft();
|
|
410
390
|
assertFinalPlan(result, options.signal);
|
|
411
391
|
return result;
|
|
412
392
|
}
|
|
413
|
-
/**
|
|
393
|
+
/** Fresh pinned input and deterministic gas-budget checks. No simulation or broadcasts. */
|
|
414
394
|
export async function planLaunch(options) {
|
|
415
395
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
416
396
|
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads(options, client));
|
|
@@ -421,9 +401,8 @@ export async function planLaunch(options) {
|
|
|
421
401
|
return result;
|
|
422
402
|
}
|
|
423
403
|
async function planLaunchWithReads(options, client, readContext = defaultPlanReadContext) {
|
|
424
|
-
const { block: pinnedBlock,
|
|
425
|
-
const { plan, account,
|
|
426
|
-
let mode = requestedMode;
|
|
404
|
+
const { block: pinnedBlock, openingBuys } = readContext;
|
|
405
|
+
const { plan, account, ufg } = options;
|
|
427
406
|
const { commitment: planHash, marketConfigs, ufgConfigHash } = validatePlanningIntent(options);
|
|
428
407
|
const launchId = hashLaunchIdentity(plan);
|
|
429
408
|
const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
|
|
@@ -431,39 +410,27 @@ async function planLaunchWithReads(options, client, readContext = defaultPlanRea
|
|
|
431
410
|
]));
|
|
432
411
|
if (chainId !== plan.chainId)
|
|
433
412
|
throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed plan");
|
|
434
|
-
// Only preparation/build-next can supply freshly observed progress from this
|
|
435
|
-
// still-open SDK invocation. Stored/query DTOs never enter this continuation.
|
|
436
|
-
if (ownedProgress !== undefined) {
|
|
437
|
-
if (!assertLifecycleReadClientOpen(client) || pinnedBlock !== block || ownedProgress.blockNumber !== block.number ||
|
|
438
|
-
ownedProgress.blockHash.toLowerCase() !== block.hash.toLowerCase() || ownedProgress.chainId !== plan.chainId ||
|
|
439
|
-
ownedProgress.confirmationDepth !== (options.confirmations ?? 1))
|
|
440
|
-
throw new LifecyclePlanningError("INVALID_READ_CONTEXT", "Owned progress differs from its active planning snapshot");
|
|
441
|
-
if (ownedProgress.planHash.toLowerCase() !== planHash.toLowerCase() || ownedProgress.launchId.toLowerCase() !== launchId.toLowerCase())
|
|
442
|
-
throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed after its canonical progress was read");
|
|
443
|
-
}
|
|
444
413
|
assertPlanningActive(options.signal);
|
|
445
414
|
const preset = getKnownLifecycleDeployment({ chainId: plan.chainId, orchestrator: plan.orchestrator });
|
|
446
415
|
const [{ tokenFactory, tokenFactoryCodeHash }, { limits }, { identity, progress }] = await lifecycleStage(client, "plan.domain", () => Promise.all([
|
|
447
416
|
preset ?? readTokenFactoryBinding(client, plan.orchestrator, block),
|
|
448
417
|
resolveLifecycleLimits({ client, account, orchestrator: plan.orchestrator, block, chainId: plan.chainId }, options.limits),
|
|
449
418
|
lifecycleStage(client, "plan.progress", async () => {
|
|
450
|
-
const [predictedToken] = await Promise.all([
|
|
451
|
-
readLifecycleTokenAtBlock(client, plan, block),
|
|
452
|
-
ownedProgress === undefined ? readLifecycleHeadProgress(client, plan, account, block) : undefined,
|
|
419
|
+
const [predictedToken, head] = await Promise.all([
|
|
420
|
+
readLifecycleTokenAtBlock(client, plan, block), readLifecycleHeadProgress(client, plan, account, block),
|
|
453
421
|
]);
|
|
422
|
+
const mode = head.phase === LifecyclePhase.None ? options.mode : head.mode === 1 ? "staged" : "atomic";
|
|
454
423
|
const identity = { plan, planHash, launchId, predictedToken, account, mode, confirmations: options.confirmations ?? 1,
|
|
455
424
|
...(ufg === undefined ? {} : { ufg: { config: ufg.config, configHash: ufgConfigHash } }) };
|
|
456
425
|
if (ufg !== undefined)
|
|
457
426
|
validateUfgLaunchPlan(plan, ufg.config, predictedToken);
|
|
458
|
-
const progress =
|
|
459
|
-
if (ownedProgress !== undefined && progress.token.toLowerCase() !== predictedToken.toLowerCase())
|
|
460
|
-
throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed after its canonical progress was read");
|
|
427
|
+
const progress = await readLaunchProgress({ client, planned: identity, receipts: options.receipts, signal: options.signal }, block);
|
|
461
428
|
return { identity, progress };
|
|
462
429
|
}),
|
|
463
430
|
]));
|
|
464
431
|
assertPlanningActive(options.signal);
|
|
465
|
-
const placeholder = {
|
|
466
|
-
let planned = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress,
|
|
432
|
+
const placeholder = { ready: false, blockNumber: block.number, blockHash: block.hash, account, chainId: plan.chainId, limits };
|
|
433
|
+
let planned = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, assessment: placeholder, limits: options.limits,
|
|
467
434
|
allowTopologyGaps: options.allowTopologyGaps === true, allowMissingPermanentTail: options.allowMissingPermanentTail === true };
|
|
468
435
|
const receiptsPending = progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed");
|
|
469
436
|
if (!progress.confirmationSafe || receiptsPending) {
|
|
@@ -475,14 +442,13 @@ async function planLaunchWithReads(options, client, readContext = defaultPlanRea
|
|
|
475
442
|
const known = deriveKnownPoolBoundHookDeployment({ plan, token: identity.predictedToken, marketIndex });
|
|
476
443
|
hookDeployments.push({ marketIndex, ...(known?.deployment ?? await readPoolBoundHookDeployment({ client, plan, marketIndex }, block)) });
|
|
477
444
|
}
|
|
478
|
-
|
|
479
|
-
|
|
445
|
+
return { ...planned, hookDeployments, assessment: { ...placeholder,
|
|
446
|
+
code: !progress.confirmationSafe ? "UNCONFIRMED_STATE" : "RECEIPT_PENDING",
|
|
447
|
+
reason: !progress.confirmationSafe ? "Wait for the selected launch confirmation depth before continuing." : "Wait for the pending transaction to confirm or resolve its replacement." } };
|
|
480
448
|
}
|
|
481
449
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) {
|
|
482
|
-
return { ...planned,
|
|
450
|
+
return { ...planned, assessment: { ...placeholder, ready: true } };
|
|
483
451
|
}
|
|
484
|
-
// Topology is a creation policy, not a retroactive obstacle to recovery of
|
|
485
|
-
// immutable onchain commitments. Waiting and terminal launches returned above.
|
|
486
452
|
if (progress.canonical.phase === LifecyclePhase.None && progress.head.phase === LifecyclePhase.None)
|
|
487
453
|
validatePlanTopology(options, marketConfigs, identity.predictedToken);
|
|
488
454
|
const [funding, metadata] = await lifecycleStage(client, "plan.inputs", () => Promise.all([
|
|
@@ -490,129 +456,81 @@ async function planLaunchWithReads(options, client, readContext = defaultPlanRea
|
|
|
490
456
|
readPlanMetadata(client, plan, identity.predictedToken, block),
|
|
491
457
|
]));
|
|
492
458
|
assertFinalPlan(planned, options.signal);
|
|
493
|
-
const capacity =
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const prices = await readNitroPrices(client, block);
|
|
497
|
-
if (capacity.bounds.atomicComputeGasUpperBound + nitroPosterGasBound(capacity.atomic, prices).posterGas > limits.executionGasCeiling)
|
|
498
|
-
mode = "staged";
|
|
499
|
-
}
|
|
459
|
+
const capacity = deriveLifecycleLaunchCapacity({ plan, predictedToken: identity.predictedToken, ufg, headroomBps: limits.headroomBps }, openingBuys);
|
|
460
|
+
const mode = progress.canonical.phase === LifecyclePhase.None
|
|
461
|
+
? await selectLaunchMode(options.mode, plan, capacity, limits, client, block) : identity.mode;
|
|
500
462
|
planned = { ...planned, mode, prerequisites: funding.prerequisites, profiles: metadata.profiles, hookDeployments: metadata.hookDeployments };
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const execute = (candidate, quotes = openingBuys) => evaluateLaunchTransactions({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, candidate, block, { diagnostic, openingBuys: quotes, capacity, forceSimulation });
|
|
508
|
-
let simulation = await execute(transactions);
|
|
509
|
-
assertFinalPlan(planned, options.signal);
|
|
510
|
-
if (!diagnostic && !forceSimulation && requestedMode === "atomic" && mode === "atomic" && progress.canonical.phase === LifecyclePhase.None &&
|
|
511
|
-
simulation.executionProof === "failed" && simulation.failureCategory === "capacity" && simulation.capacityConstraint === "compute") {
|
|
512
|
-
mode = "staged";
|
|
513
|
-
planned = { ...planned, mode };
|
|
514
|
-
batches = packPreparationBatches(plan, prepared, computeCap);
|
|
515
|
-
transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
|
|
516
|
-
simulation = await execute(transactions);
|
|
517
|
-
assertFinalPlan(planned, options.signal);
|
|
518
|
-
}
|
|
519
|
-
if (mode === "staged") {
|
|
520
|
-
// Only pressure at the native ceiling can invalidate packing. A smaller
|
|
521
|
-
// affordability/RPC/account envelope is not evidence for another wallet write.
|
|
522
|
-
while (!simulation.admitted && simulation.failedTransactionId?.startsWith("prepare:")) {
|
|
523
|
-
const failedIndex = transactions.findIndex((transaction) => transaction.id === simulation.failedTransactionId);
|
|
524
|
-
const failedTransaction = transactions[failedIndex];
|
|
525
|
-
const step = simulation.steps.find((candidate) => candidate.transactionId === simulation.failedTransactionId);
|
|
526
|
-
const count = failedTransaction?.marketCount ?? 0;
|
|
527
|
-
if (count <= 1 || step?.gasUsed === undefined || step.gasLimit === undefined || step.gasLimit < limits.executionGasCeiling || step.gasUsed * 100n < limits.executionGasCeiling * 95n)
|
|
528
|
-
break;
|
|
529
|
-
let first = 0;
|
|
530
|
-
const batchIndex = batches.findIndex((batch) => { const matches = first === failedTransaction?.marketStart; first += batch; return matches; });
|
|
531
|
-
const left = Math.ceil(count / 2);
|
|
532
|
-
batches.splice(batchIndex, 1, left, count - left);
|
|
533
|
-
transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
|
|
534
|
-
simulation = await execute(transactions, simulation.gasProfile?.initialBuys);
|
|
535
|
-
assertFinalPlan(planned, options.signal);
|
|
536
|
-
}
|
|
537
|
-
if (!simulation.admitted && simulation.failedTransactionId === "activate" && simulation.failureCategory === "capacity")
|
|
538
|
-
simulation = {
|
|
539
|
-
...simulation, capacityConstraint: "activation", reason: "Too many positions or pools for one activation. Reduce them to fit the native transaction ceiling."
|
|
540
|
-
};
|
|
463
|
+
if (metadata.reasons.length > 0)
|
|
464
|
+
return { ...planned, assessment: { ...placeholder, code: "PROFILE_UNAVAILABLE", failureCategory: "semantic",
|
|
465
|
+
reason: "The selected launch profile could not be read or is unavailable. Refresh the review before continuing." } };
|
|
466
|
+
let batches;
|
|
467
|
+
try {
|
|
468
|
+
batches = mode === "staged" ? await preparationBatches(plan, progress.canonical.preparedMarkets, ufg, limits, client, block) : [];
|
|
541
469
|
}
|
|
542
|
-
|
|
543
|
-
|
|
470
|
+
catch (failure) {
|
|
471
|
+
if (!(failure instanceof LifecyclePlanningError) || failure.code !== "PREPARATION_GAS_LIMIT")
|
|
472
|
+
throw failure;
|
|
473
|
+
return { ...planned, assessment: { ...placeholder, code: failure.code, reason: failure.message, failureCategory: "capacity", capacityConstraint: "preparation" } };
|
|
544
474
|
}
|
|
545
|
-
|
|
475
|
+
const transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
|
|
476
|
+
const assessment = await assessLifecycleTransactions({ client, planned, limits: options.limits, signal: options.signal }, transactions, block, undefined, openingBuys, capacity);
|
|
477
|
+
assertFinalPlan(planned, options.signal);
|
|
478
|
+
return { ...planned, assessment, transactions: attachEstimates(transactions, assessment), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
|
|
546
479
|
}
|
|
547
480
|
function finalizeNextTransaction(options, next, reviewed) {
|
|
548
481
|
assertPlanningActive(options.signal);
|
|
549
482
|
assertReviewedTransactionUnchanged(options, reviewed, next);
|
|
550
483
|
return { ...next, dependencies: [] };
|
|
551
484
|
}
|
|
552
|
-
/** Recovery observes
|
|
553
|
-
*
|
|
485
|
+
/** Recovery observes actual canonical receipts, then constructs and budgets only
|
|
486
|
+
* the next command. No launch transaction is executed during planning. */
|
|
554
487
|
export async function buildNextTransaction(options) {
|
|
555
488
|
const original = options.planned, matches = observedFields(original);
|
|
556
|
-
const fullReviewAdmitted = original.simulation?.admitted === true;
|
|
557
489
|
const assertInput = () => {
|
|
558
490
|
if (options.planned !== original || !matches())
|
|
559
491
|
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored launch context changed while constructing its next transaction");
|
|
560
492
|
assertFinalPlan(original, options.signal);
|
|
561
493
|
};
|
|
562
494
|
const supplied = readReviewedNextTransaction(options.reviewedTransaction);
|
|
563
|
-
// Snapshot only the reviewed wire fields; caller proofs and commands are never
|
|
564
|
-
// reused, and final checks compare the mutable input with this one local copy.
|
|
565
495
|
const walletApproval = supplied?.kind === "approve" || supplied?.kind === "approve-reset";
|
|
566
496
|
const reviewed = supplied === undefined ? undefined : Object.freeze({ id: supplied.id, kind: supplied.kind, chainId: supplied.chainId,
|
|
567
497
|
from: supplied.from, to: supplied.to, data: supplied.data, value: supplied.value, ...(walletApproval ? {} : { gas: supplied.gas, gasPrice: supplied.gasPrice }) });
|
|
568
498
|
const envelope = reviewed === undefined || walletApproval ? undefined : reviewed;
|
|
569
499
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
570
500
|
registerLifecycleFinalCheck(client, assertInput);
|
|
571
|
-
const block = await readLifecyclePlanningBlock(client,
|
|
572
|
-
const limitSource = options.limits === undefined ?
|
|
573
|
-
if (getKnownLifecycleDeployment({ chainId: options.planned.chainId, orchestrator: options.planned.plan.orchestrator }) !== undefined && typeof limitSource !== "function") {
|
|
574
|
-
await Promise.all([
|
|
575
|
-
readLifecycleTokenAtBlock(client, options.planned.plan, block),
|
|
576
|
-
readLifecycleHeadProgress(client, options.planned.plan, options.planned.account, block),
|
|
577
|
-
resolveLifecycleLimits({ client, account: options.planned.account, orchestrator: options.planned.plan.orchestrator, block, chainId: options.planned.chainId }, limitSource),
|
|
578
|
-
]);
|
|
579
|
-
}
|
|
501
|
+
const block = await readLifecyclePlanningBlock(client, original.plan, original.account, original.ufg !== undefined);
|
|
502
|
+
const limitSource = options.limits === undefined ? original.limits : options.limits;
|
|
580
503
|
const progress = await readLifecycleExecutionProgress({ ...options, client }, block);
|
|
581
|
-
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled)
|
|
582
|
-
// Only the confirmed canonical terminal phase is terminal; a head-only
|
|
583
|
-
// Active observation is not, and the launch-state/receipt confirmation
|
|
584
|
-
// policy below remains in force for every nonterminal case.
|
|
504
|
+
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled)
|
|
585
505
|
return undefined;
|
|
586
|
-
}
|
|
587
506
|
if (!progress.confirmationSafe)
|
|
588
|
-
throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for the selected
|
|
507
|
+
throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for the selected launch confirmation depth before continuing.");
|
|
589
508
|
if (progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed"))
|
|
590
|
-
throw new LifecyclePlanningError("RECEIPT_PENDING", "Wait for
|
|
509
|
+
throw new LifecyclePlanningError("RECEIPT_PENDING", "Wait for the pending transaction to confirm or resolve its replacement.");
|
|
591
510
|
assertPlanningActive(options.signal);
|
|
511
|
+
const { limits } = await resolveLifecycleLimits({ client, account: original.account, orchestrator: original.plan.orchestrator, block, chainId: original.chainId }, limitSource);
|
|
592
512
|
if (options.action === "cancel") {
|
|
593
513
|
if (progress.canonical.phase === LifecyclePhase.None)
|
|
594
514
|
throw new LifecyclePlanningError("NOT_STARTED", "An unstarted plan has no launch escrow to cancel");
|
|
595
|
-
const {
|
|
596
|
-
assertFinalPlan(planned, options.signal);
|
|
515
|
+
const planned = { ...original, progress };
|
|
597
516
|
const transaction = planned.ufg !== undefined ? buildUfgCancelTransaction({ plan: planned.plan, preparedMarkets: progress.canonical.preparedMarkets })
|
|
598
517
|
: { id: "cancel", kind: "cancel", chainId: Number(planned.chainId), from: planned.account, to: planned.plan.orchestrator, data: encodeFunctionData({ abi: launchLifecycleAbi, functionName: "cancelLaunch", args: [planned.plan] }), value: 0n, dependencies: [], postconditions: [{ kind: "launch", phase: "Cancelled", preparedMarkets: progress.canonical.preparedMarkets }] };
|
|
599
518
|
if (reviewed !== undefined && !matchesReviewedNextTransaction(transaction, reviewed))
|
|
600
|
-
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current
|
|
601
|
-
const
|
|
602
|
-
if (!
|
|
603
|
-
throw new LifecyclePlanningError("
|
|
604
|
-
|
|
605
|
-
if (next === undefined)
|
|
606
|
-
throw new LifecyclePlanningError("MISSING_GAS_PROOF", "Cancellation omitted its exact simulation admission", simulation);
|
|
607
|
-
return finalizeNextTransaction(options, next, reviewed);
|
|
519
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current cancellation");
|
|
520
|
+
const assessment = await assessLifecycleTransactions({ client, planned, limits: limitSource, signal: options.signal }, [transaction], block, envelope);
|
|
521
|
+
if (!assessment.ready)
|
|
522
|
+
throw new LifecyclePlanningError(assessment.code ?? "CANCEL_NOT_READY", assessment.reason ?? "Cancellation could not be prepared. Refresh before continuing.", assessment);
|
|
523
|
+
return finalizeNextTransaction(options, attachEstimates([transaction], assessment)[0], reviewed);
|
|
608
524
|
}
|
|
609
|
-
const { planned } = options;
|
|
610
|
-
assertFinalPlan(planned, options.signal);
|
|
611
|
-
if (progress.canonical.phase === LifecyclePhase.None && !fullReviewAdmitted)
|
|
612
|
-
throw new LifecyclePlanningError("PLAN_NOT_ADMITTED", "Complete an admitted full launch review before starting or approving funding; next-only construction does not replan the remaining sequence");
|
|
613
525
|
const funding = progress.canonical.phase === LifecyclePhase.None
|
|
614
|
-
? await readFunding(client,
|
|
526
|
+
? await readFunding(client, original.plan, block, original.account, original.ufg !== undefined)
|
|
615
527
|
: { prerequisites: [], approvals: [] };
|
|
528
|
+
const planned = { ...original, progress, prerequisites: funding.prerequisites };
|
|
529
|
+
const capacity = deriveLifecycleLaunchCapacity({ plan: planned.plan, predictedToken: planned.predictedToken, ufg: planned.ufg, headroomBps: limits.headroomBps });
|
|
530
|
+
if (progress.canonical.phase === LifecyclePhase.None &&
|
|
531
|
+
await selectLaunchMode(planned.mode, planned.plan, capacity, limits, client, block) !== planned.mode) {
|
|
532
|
+
throw new LifecyclePlanningError("MODE_REVIEW_REQUIRED", "This launch requires staged execution. Refresh the review before continuing.");
|
|
533
|
+
}
|
|
616
534
|
let candidate = funding.approvals[0];
|
|
617
535
|
if (candidate === undefined) {
|
|
618
536
|
const prepared = progress.canonical.preparedMarkets;
|
|
@@ -623,23 +541,16 @@ export async function buildNextTransaction(options) {
|
|
|
623
541
|
let count;
|
|
624
542
|
if (reviewed?.kind === "prepare") {
|
|
625
543
|
const span = /^prepare:(\d+):(\d+)$/.exec(reviewed.id);
|
|
626
|
-
|
|
544
|
+
if (Number(span?.[1]) !== prepared)
|
|
545
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation does not start at the unfinished market");
|
|
627
546
|
count = Number(span?.[2]);
|
|
628
|
-
if (first !== prepared)
|
|
629
|
-
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation does not start at the canonical unfinished market");
|
|
630
547
|
}
|
|
631
548
|
else {
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
count = stored.marketStart + stored.marketCount - prepared;
|
|
635
|
-
else {
|
|
636
|
-
const { limits } = await resolveLifecycleLimits({ client, account: planned.account, orchestrator: planned.plan.orchestrator, block, chainId: planned.chainId }, limitSource);
|
|
637
|
-
const cap = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
|
|
638
|
-
count = packPreparationBatches(planned.plan, prepared, cap)[prepared > 0 ? 1 : 0];
|
|
639
|
-
}
|
|
549
|
+
const batches = await preparationBatches(planned.plan, prepared, planned.ufg, limits, client, block);
|
|
550
|
+
count = batches[prepared > 0 ? 1 : 0];
|
|
640
551
|
}
|
|
641
552
|
if (!Number.isSafeInteger(count) || count <= 0 || prepared + count > planned.plan.markets.length)
|
|
642
|
-
throw new LifecyclePlanningError("INVALID_PREPARATION_BATCHES", "Next preparation must
|
|
553
|
+
throw new LifecyclePlanningError("INVALID_PREPARATION_BATCHES", "Next preparation must cover a positive contiguous unfinished market span");
|
|
643
554
|
command = { kind: "prepare", first: prepared, count };
|
|
644
555
|
}
|
|
645
556
|
else
|
|
@@ -647,32 +558,16 @@ export async function buildNextTransaction(options) {
|
|
|
647
558
|
candidate = buildLifecycleCommand(planned.plan, command, planned.ufg === undefined ? undefined : { account: ufgPlanPrincipal(planned.plan), config: planned.ufg.config });
|
|
648
559
|
}
|
|
649
560
|
if (reviewed !== undefined && !matchesReviewedNextTransaction(candidate, reviewed))
|
|
650
|
-
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current
|
|
651
|
-
const
|
|
561
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current next step");
|
|
562
|
+
const assessment = await assessLifecycleTransactions({ client, planned, limits: limitSource, signal: options.signal }, [candidate], block, envelope, undefined, capacity);
|
|
652
563
|
assertFinalPlan(planned, options.signal);
|
|
653
|
-
if (!
|
|
654
|
-
throw new LifecyclePlanningError("
|
|
655
|
-
|
|
656
|
-
return finalizeNextTransaction(options, next, reviewed);
|
|
564
|
+
if (!assessment.ready)
|
|
565
|
+
throw new LifecyclePlanningError(assessment.code ?? "PLAN_NOT_READY", assessment.reason ?? "The next launch step could not be prepared. Refresh before continuing.", assessment);
|
|
566
|
+
return finalizeNextTransaction(options, attachEstimates([candidate], assessment)[0], reviewed);
|
|
657
567
|
});
|
|
658
568
|
assertInput();
|
|
659
569
|
if (result !== undefined)
|
|
660
570
|
assertReviewedTransactionUnchanged(options, reviewed, result);
|
|
661
571
|
return result;
|
|
662
572
|
}
|
|
663
|
-
/** Re-simulates only canonical unfinished work with the same submitted receipt
|
|
664
|
-
* evidence and confirmation depth as build-next, never silently dropping context. */
|
|
665
|
-
export async function simulateLaunchPlan(options) {
|
|
666
|
-
const { client, planned } = options;
|
|
667
|
-
if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase())
|
|
668
|
-
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its commitment");
|
|
669
|
-
const current = await withLifecycleReadClient(client, (readClient) => planLaunchWithReads({
|
|
670
|
-
client, plan: planned.plan, account: planned.account, mode: planned.mode,
|
|
671
|
-
limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork,
|
|
672
|
-
receipts: options.receipts, confirmations: options.confirmations ?? planned.confirmations, signal: options.signal,
|
|
673
|
-
ufg: planned.ufg, allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
|
|
674
|
-
}, readClient, { forceSimulation: true }));
|
|
675
|
-
assertPlannedDeploymentBindings(planned, current);
|
|
676
|
-
return current.simulation;
|
|
677
|
-
}
|
|
678
573
|
//# sourceMappingURL=planner.js.map
|