@black-market/sdk 0.15.0 → 0.16.0
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 +10 -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/deployments/raeto-dynamic-pool-v1-20261029.d.ts +71 -0
- package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.d.ts.map +1 -0
- package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.js +73 -0
- package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.js.map +1 -0
- package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.d.ts +71 -0
- package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.d.ts.map +1 -0
- package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.js +73 -0
- package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.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 +133 -113
- package/dist/lifecycle/gas-profile.js.map +1 -1
- package/dist/lifecycle/index.d.ts +6 -4
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +6 -4
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/markets.d.ts +4 -0
- package/dist/lifecycle/markets.d.ts.map +1 -1
- package/dist/lifecycle/markets.js +58 -3
- package/dist/lifecycle/markets.js.map +1 -1
- package/dist/lifecycle/opening-buys.js +3 -3
- 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 +122 -208
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +9 -3
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +17 -1
- package/dist/lifecycle/presets.js.map +1 -1
- package/dist/lifecycle/raeto-abi.d.ts +175 -0
- package/dist/lifecycle/raeto-abi.d.ts.map +1 -0
- package/dist/lifecycle/raeto-abi.js +224 -0
- package/dist/lifecycle/raeto-abi.js.map +1 -0
- package/dist/lifecycle/raeto.d.ts +21 -0
- package/dist/lifecycle/raeto.d.ts.map +1 -0
- package/dist/lifecycle/raeto.js +59 -0
- package/dist/lifecycle/raeto.js.map +1 -0
- 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 +47 -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 +113 -1
- package/docs/lifecycle-gas-profiles.md +141 -24
- package/docs/reference.md +112 -3
- package/examples/.env.example +0 -3
- package/examples/README.md +11 -11
- 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/deployments/raeto-dynamic-pool-v1-20261029.ts +74 -0
- package/src/lifecycle/deployments/raeto-fixed-pool-v1-20261029.ts +74 -0
- package/src/lifecycle/gas-profile.ts +125 -104
- package/src/lifecycle/index.ts +6 -4
- package/src/lifecycle/markets.ts +47 -3
- package/src/lifecycle/opening-buys.ts +3 -3
- package/src/lifecycle/planner.ts +116 -191
- package/src/lifecycle/presets.ts +17 -1
- package/src/lifecycle/raeto-abi.ts +223 -0
- package/src/lifecycle/raeto.ts +61 -0
- package/src/lifecycle/rpc.ts +10 -32
- package/src/lifecycle/snapshot.ts +1 -2
- package/src/lifecycle/transactions.ts +46 -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
|
@@ -2,13 +2,12 @@ import { encodeFunctionData, keccak256, toHex, zeroAddress } from "viem";
|
|
|
2
2
|
import { ABYSS_FEE_TIERS } from "../abyss.js";
|
|
3
3
|
import { assertLaunchTopologyCoverage, getTickAtSqrtRatio } from "../launch.js";
|
|
4
4
|
import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleErc20Abi } from "./abi.js";
|
|
5
|
-
import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind,
|
|
5
|
+
import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LIFECYCLE_MAX_ERC404_SUPPLY, LIFECYCLE_MAX_REWARD_ERC20_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,43 @@ 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";
|
|
20
|
+
import { isRaetoProfile, RAETO_QUOTE_ASSETS } from "./raeto.js";
|
|
21
21
|
const defaultPlanReadContext = {};
|
|
22
|
+
function observedFields(value) {
|
|
23
|
+
const keys = Object.keys(value);
|
|
24
|
+
const values = keys.map((key) => value[key]);
|
|
25
|
+
return () => keys.length === Object.keys(value).length && keys.every((key, index) => value[key] === values[index]);
|
|
26
|
+
}
|
|
27
|
+
async function selectLaunchMode(requested, plan, capacity, limits, client, block) {
|
|
28
|
+
if (requested !== "atomic" || plan.markets.length !== 1 || plan.funding.some((funding) => funding.kind === LifecycleFundingKind.Swap) ||
|
|
29
|
+
capacity?.atomic === undefined || capacity.bounds.atomicComputeGasUpperBound === undefined)
|
|
30
|
+
return "staged";
|
|
31
|
+
const poster = limits.protocol === "nitro" ? nitroPosterGasBound(capacity.atomic, await readNitroPrices(client, block)).posterGas : 0n;
|
|
32
|
+
return capacity.bounds.atomicComputeGasUpperBound + poster <= limits.executionGasCeiling ? "atomic" : "staged";
|
|
33
|
+
}
|
|
34
|
+
async function preparationBatches(plan, prepared, ufg, limits, client, block) {
|
|
35
|
+
if (prepared === plan.markets.length)
|
|
36
|
+
return prepared === 0 ? [] : [prepared];
|
|
37
|
+
const route = ufg === undefined ? undefined : { account: ufgPlanPrincipal(plan), config: ufg.config };
|
|
38
|
+
const sample = buildLifecycleCommand(plan, { kind: "prepare", first: prepared, count: 1 }, route);
|
|
39
|
+
const poster = limits.protocol === "nitro" ? nitroPosterGasBound(sample, await readNitroPrices(client, block)).posterGas : 0n;
|
|
40
|
+
return packPreparationBatches(plan, prepared, limits.executionGasCeiling - poster, limits.headroomBps);
|
|
41
|
+
}
|
|
42
|
+
/** Preserve Raeto's outer quote/profile binding even on known-preset recovery. */
|
|
43
|
+
function validateRaetoMarketContext(market, config) {
|
|
44
|
+
if (!isRaetoProfile(market.profileId))
|
|
45
|
+
return;
|
|
46
|
+
if (market.configVersion !== 6 || !RAETO_QUOTE_ASSETS.some((quote) => quote.toLowerCase() === market.quoteAsset.toLowerCase())) {
|
|
47
|
+
throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto requires config6 and a WETH or USDG quote");
|
|
48
|
+
}
|
|
49
|
+
// Normal planning supplies its already decoded config. Continuation must also
|
|
50
|
+
// reject malformed fee/share tails before reading or building its next command.
|
|
51
|
+
const decoded = config ?? decodePoolBoundV4LifecycleMarketConfig(market.config, 6);
|
|
52
|
+
if (decoded.version !== 6 || decoded.profileId.toLowerCase() !== market.profileId.toLowerCase()) {
|
|
53
|
+
throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto config must bind its market's Raeto profile");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
22
56
|
function validatePlanShape(plan) {
|
|
23
57
|
// Encoding checks all exact integer widths, addresses, byte strings and tuple fields.
|
|
24
58
|
const commitment = hashLaunchPlan(plan);
|
|
@@ -64,6 +98,7 @@ function validatePlanShape(plan) {
|
|
|
64
98
|
config = decodeV4LifecycleMarketConfig(market.config);
|
|
65
99
|
else if (isPoolBoundV4ConfigVersion(market.configVersion))
|
|
66
100
|
config = decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion);
|
|
101
|
+
validateRaetoMarketContext(market, config);
|
|
67
102
|
if (config !== undefined) {
|
|
68
103
|
if (config.profileId.toLowerCase() !== market.profileId.toLowerCase())
|
|
69
104
|
throw new LifecyclePlanningError("PROFILE_TERMS_MISMATCH", "Inner config profile differs from the committed market profile");
|
|
@@ -176,16 +211,14 @@ function orderedTransactions(plan, mode, progress, approvals, batches, ufg, capa
|
|
|
176
211
|
const transactions = progress.canonical.phase === LifecyclePhase.None ? [...approvals, ...commands] : commands;
|
|
177
212
|
return transactions.map((transaction, index) => ({ ...transaction, calldataBytes: (transaction.data.length - 2) / 2, dependencies: index === 0 ? [] : [transactions[index - 1].id] }));
|
|
178
213
|
}
|
|
179
|
-
function attachEstimates(transactions,
|
|
214
|
+
function attachEstimates(transactions, assessment) {
|
|
180
215
|
return transactions.map((transaction, index) => {
|
|
181
216
|
if (index !== 0)
|
|
182
217
|
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
218
|
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,
|
|
219
|
+
return { ...transaction, assessment };
|
|
220
|
+
const estimate = assessment.estimate;
|
|
221
|
+
return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate, assessment };
|
|
189
222
|
});
|
|
190
223
|
}
|
|
191
224
|
const reviewedTransactionKinds = ["approve-reset", "approve", "atomic", "begin", "prepare", "activate", "cancel"];
|
|
@@ -215,16 +248,16 @@ function assertReviewedTransactionUnchanged(options, reviewed, next) {
|
|
|
215
248
|
if (options.planned.ufg !== undefined)
|
|
216
249
|
assertUfgLaunchAccount(options.planned.plan, options.planned.account);
|
|
217
250
|
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
|
|
251
|
+
throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account changed while preparing the reviewed transaction");
|
|
219
252
|
}
|
|
220
253
|
if (options.planned.chainId !== BigInt(next.chainId) || options.planned.plan.chainId !== BigInt(next.chainId)) {
|
|
221
|
-
throw new LifecyclePlanningError("CHAIN_MISMATCH", "Execution chain changed
|
|
254
|
+
throw new LifecyclePlanningError("CHAIN_MISMATCH", "Execution chain changed while preparing the reviewed transaction");
|
|
222
255
|
}
|
|
223
256
|
const current = readReviewedNextTransaction(options.reviewedTransaction);
|
|
224
257
|
if (reviewed === undefined ? current !== undefined : current === undefined || !matchesReviewedNextTransaction(current, reviewed) || !matchesReviewedNextTransaction(next, reviewed) ||
|
|
225
258
|
reviewed.kind !== "approve" && reviewed.kind !== "approve-reset" &&
|
|
226
259
|
(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
|
|
260
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_CHANGED", "Reviewed transaction changed while preparing it");
|
|
228
261
|
}
|
|
229
262
|
}
|
|
230
263
|
async function readTokenFactoryBinding(client, orchestrator, block) {
|
|
@@ -238,21 +271,6 @@ async function readTokenFactoryBinding(client, orchestrator, block) {
|
|
|
238
271
|
throw new LifecyclePlanningError("TOKEN_FACTORY_BINDING", "Token factory is not live and bound to the exact lifecycle core");
|
|
239
272
|
return { tokenFactory, tokenFactoryCodeHash: keccak256(code) };
|
|
240
273
|
}
|
|
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
274
|
function assertPlanningActive(signal) {
|
|
257
275
|
if (signal?.aborted)
|
|
258
276
|
throw Object.assign(new Error("Lifecycle launch planning cancelled"), { name: "AbortError" });
|
|
@@ -312,21 +330,8 @@ function assertFinalPlan(planned, signal) {
|
|
|
312
330
|
if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash)
|
|
313
331
|
throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed during asynchronous planning");
|
|
314
332
|
}
|
|
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. */
|
|
333
|
+
/** One owner mines constructor-bound salts and protects ordered buys with exact
|
|
334
|
+
* local curve math. Launch calldata is never executed during planning. */
|
|
330
335
|
export async function prepareAndPlanLifecycleLaunch(options) {
|
|
331
336
|
const { commitment, ufgConfigHash } = validatePlanningIntent(options);
|
|
332
337
|
if (options.buySlippageBps !== undefined)
|
|
@@ -366,9 +371,13 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
366
371
|
}
|
|
367
372
|
assertDraft();
|
|
368
373
|
let plan = { ...draft, markets };
|
|
369
|
-
let ownedProgress;
|
|
370
374
|
let openingBuys;
|
|
371
|
-
if (needsProtection
|
|
375
|
+
if (needsProtection) {
|
|
376
|
+
if (!hasProfiledOpeningMath(plan))
|
|
377
|
+
throw new LifecyclePlanningError("OPENING_BUY_NOT_PROFILED", "Exact opening-buy math is unavailable for this profile. Use a supported profile or update the SDK.");
|
|
378
|
+
const head = await readLifecycleHeadProgress(client, plan, options.account, block);
|
|
379
|
+
if (head.phase !== LifecyclePhase.None)
|
|
380
|
+
throw new LifecyclePlanningError("LAUNCH_ALREADY_STARTED", "Buy protection cannot replace an already committed launch plan");
|
|
372
381
|
const predictedToken = await readLifecycleTokenAtBlock(client, plan, block);
|
|
373
382
|
openingBuys = computeLifecycleOpeningBuys({ plan, predictedToken });
|
|
374
383
|
const factor = BigInt(10000 - options.buySlippageBps);
|
|
@@ -379,20 +388,7 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
379
388
|
return { ...buy, minTokenOut };
|
|
380
389
|
}) };
|
|
381
390
|
}
|
|
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 }));
|
|
391
|
+
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, { block, openingBuys }));
|
|
396
392
|
assertDraft();
|
|
397
393
|
assertFinalPlan(planned, options.signal);
|
|
398
394
|
if (planned.progress.canonical.phase !== LifecyclePhase.Active && planned.progress.canonical.phase !== LifecyclePhase.Cancelled) {
|
|
@@ -402,15 +398,15 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
402
398
|
throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Final exact constructor binding differs from the one locally mined candidate");
|
|
403
399
|
}
|
|
404
400
|
}
|
|
405
|
-
if (needsProtection && !planned.
|
|
406
|
-
throw new LifecyclePlanningError("
|
|
401
|
+
if (needsProtection && !planned.assessment.ready)
|
|
402
|
+
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
403
|
return planned;
|
|
408
404
|
});
|
|
409
405
|
assertDraft();
|
|
410
406
|
assertFinalPlan(result, options.signal);
|
|
411
407
|
return result;
|
|
412
408
|
}
|
|
413
|
-
/**
|
|
409
|
+
/** Fresh pinned input and deterministic gas-budget checks. No simulation or broadcasts. */
|
|
414
410
|
export async function planLaunch(options) {
|
|
415
411
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
416
412
|
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads(options, client));
|
|
@@ -421,9 +417,8 @@ export async function planLaunch(options) {
|
|
|
421
417
|
return result;
|
|
422
418
|
}
|
|
423
419
|
async function planLaunchWithReads(options, client, readContext = defaultPlanReadContext) {
|
|
424
|
-
const { block: pinnedBlock,
|
|
425
|
-
const { plan, account,
|
|
426
|
-
let mode = requestedMode;
|
|
420
|
+
const { block: pinnedBlock, openingBuys } = readContext;
|
|
421
|
+
const { plan, account, ufg } = options;
|
|
427
422
|
const { commitment: planHash, marketConfigs, ufgConfigHash } = validatePlanningIntent(options);
|
|
428
423
|
const launchId = hashLaunchIdentity(plan);
|
|
429
424
|
const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
|
|
@@ -431,39 +426,27 @@ async function planLaunchWithReads(options, client, readContext = defaultPlanRea
|
|
|
431
426
|
]));
|
|
432
427
|
if (chainId !== plan.chainId)
|
|
433
428
|
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
429
|
assertPlanningActive(options.signal);
|
|
445
430
|
const preset = getKnownLifecycleDeployment({ chainId: plan.chainId, orchestrator: plan.orchestrator });
|
|
446
431
|
const [{ tokenFactory, tokenFactoryCodeHash }, { limits }, { identity, progress }] = await lifecycleStage(client, "plan.domain", () => Promise.all([
|
|
447
432
|
preset ?? readTokenFactoryBinding(client, plan.orchestrator, block),
|
|
448
433
|
resolveLifecycleLimits({ client, account, orchestrator: plan.orchestrator, block, chainId: plan.chainId }, options.limits),
|
|
449
434
|
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,
|
|
435
|
+
const [predictedToken, head] = await Promise.all([
|
|
436
|
+
readLifecycleTokenAtBlock(client, plan, block), readLifecycleHeadProgress(client, plan, account, block),
|
|
453
437
|
]);
|
|
438
|
+
const mode = head.phase === LifecyclePhase.None ? options.mode : head.mode === 1 ? "staged" : "atomic";
|
|
454
439
|
const identity = { plan, planHash, launchId, predictedToken, account, mode, confirmations: options.confirmations ?? 1,
|
|
455
440
|
...(ufg === undefined ? {} : { ufg: { config: ufg.config, configHash: ufgConfigHash } }) };
|
|
456
441
|
if (ufg !== undefined)
|
|
457
442
|
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");
|
|
443
|
+
const progress = await readLaunchProgress({ client, planned: identity, receipts: options.receipts, signal: options.signal }, block);
|
|
461
444
|
return { identity, progress };
|
|
462
445
|
}),
|
|
463
446
|
]));
|
|
464
447
|
assertPlanningActive(options.signal);
|
|
465
|
-
const placeholder = {
|
|
466
|
-
let planned = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress,
|
|
448
|
+
const placeholder = { ready: false, blockNumber: block.number, blockHash: block.hash, account, chainId: plan.chainId, limits };
|
|
449
|
+
let planned = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, assessment: placeholder, limits: options.limits,
|
|
467
450
|
allowTopologyGaps: options.allowTopologyGaps === true, allowMissingPermanentTail: options.allowMissingPermanentTail === true };
|
|
468
451
|
const receiptsPending = progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed");
|
|
469
452
|
if (!progress.confirmationSafe || receiptsPending) {
|
|
@@ -475,14 +458,13 @@ async function planLaunchWithReads(options, client, readContext = defaultPlanRea
|
|
|
475
458
|
const known = deriveKnownPoolBoundHookDeployment({ plan, token: identity.predictedToken, marketIndex });
|
|
476
459
|
hookDeployments.push({ marketIndex, ...(known?.deployment ?? await readPoolBoundHookDeployment({ client, plan, marketIndex }, block)) });
|
|
477
460
|
}
|
|
478
|
-
|
|
479
|
-
|
|
461
|
+
return { ...planned, hookDeployments, assessment: { ...placeholder,
|
|
462
|
+
code: !progress.confirmationSafe ? "UNCONFIRMED_STATE" : "RECEIPT_PENDING",
|
|
463
|
+
reason: !progress.confirmationSafe ? "Wait for the selected launch confirmation depth before continuing." : "Wait for the pending transaction to confirm or resolve its replacement." } };
|
|
480
464
|
}
|
|
481
465
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) {
|
|
482
|
-
return { ...planned,
|
|
466
|
+
return { ...planned, assessment: { ...placeholder, ready: true } };
|
|
483
467
|
}
|
|
484
|
-
// Topology is a creation policy, not a retroactive obstacle to recovery of
|
|
485
|
-
// immutable onchain commitments. Waiting and terminal launches returned above.
|
|
486
468
|
if (progress.canonical.phase === LifecyclePhase.None && progress.head.phase === LifecyclePhase.None)
|
|
487
469
|
validatePlanTopology(options, marketConfigs, identity.predictedToken);
|
|
488
470
|
const [funding, metadata] = await lifecycleStage(client, "plan.inputs", () => Promise.all([
|
|
@@ -490,129 +472,84 @@ async function planLaunchWithReads(options, client, readContext = defaultPlanRea
|
|
|
490
472
|
readPlanMetadata(client, plan, identity.predictedToken, block),
|
|
491
473
|
]));
|
|
492
474
|
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
|
-
}
|
|
475
|
+
const capacity = deriveLifecycleLaunchCapacity({ plan, predictedToken: identity.predictedToken, ufg, headroomBps: limits.headroomBps }, openingBuys);
|
|
476
|
+
const mode = progress.canonical.phase === LifecyclePhase.None
|
|
477
|
+
? await selectLaunchMode(options.mode, plan, capacity, limits, client, block) : identity.mode;
|
|
500
478
|
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
|
-
};
|
|
479
|
+
if (metadata.reasons.length > 0)
|
|
480
|
+
return { ...planned, assessment: { ...placeholder, code: "PROFILE_UNAVAILABLE", failureCategory: "semantic",
|
|
481
|
+
reason: "The selected launch profile could not be read or is unavailable. Refresh the review before continuing." } };
|
|
482
|
+
let batches;
|
|
483
|
+
try {
|
|
484
|
+
batches = mode === "staged" ? await preparationBatches(plan, progress.canonical.preparedMarkets, ufg, limits, client, block) : [];
|
|
541
485
|
}
|
|
542
|
-
|
|
543
|
-
|
|
486
|
+
catch (failure) {
|
|
487
|
+
if (!(failure instanceof LifecyclePlanningError) || failure.code !== "PREPARATION_GAS_LIMIT")
|
|
488
|
+
throw failure;
|
|
489
|
+
return { ...planned, assessment: { ...placeholder, code: failure.code, reason: failure.message, failureCategory: "capacity", capacityConstraint: "preparation" } };
|
|
544
490
|
}
|
|
545
|
-
|
|
491
|
+
const transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
|
|
492
|
+
const assessment = await assessLifecycleTransactions({ client, planned, limits: options.limits, signal: options.signal }, transactions, block, undefined, openingBuys, capacity);
|
|
493
|
+
assertFinalPlan(planned, options.signal);
|
|
494
|
+
return { ...planned, assessment, transactions: attachEstimates(transactions, assessment), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
|
|
546
495
|
}
|
|
547
496
|
function finalizeNextTransaction(options, next, reviewed) {
|
|
548
497
|
assertPlanningActive(options.signal);
|
|
549
498
|
assertReviewedTransactionUnchanged(options, reviewed, next);
|
|
550
499
|
return { ...next, dependencies: [] };
|
|
551
500
|
}
|
|
552
|
-
/** Recovery observes
|
|
553
|
-
*
|
|
501
|
+
/** Recovery observes actual canonical receipts, then constructs and budgets only
|
|
502
|
+
* the next command. No launch transaction is executed during planning. */
|
|
554
503
|
export async function buildNextTransaction(options) {
|
|
504
|
+
if (options.action !== "cancel")
|
|
505
|
+
for (const market of options.planned.plan.markets)
|
|
506
|
+
validateRaetoMarketContext(market);
|
|
555
507
|
const original = options.planned, matches = observedFields(original);
|
|
556
|
-
const fullReviewAdmitted = original.simulation?.admitted === true;
|
|
557
508
|
const assertInput = () => {
|
|
558
509
|
if (options.planned !== original || !matches())
|
|
559
510
|
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored launch context changed while constructing its next transaction");
|
|
560
511
|
assertFinalPlan(original, options.signal);
|
|
561
512
|
};
|
|
562
513
|
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
514
|
const walletApproval = supplied?.kind === "approve" || supplied?.kind === "approve-reset";
|
|
566
515
|
const reviewed = supplied === undefined ? undefined : Object.freeze({ id: supplied.id, kind: supplied.kind, chainId: supplied.chainId,
|
|
567
516
|
from: supplied.from, to: supplied.to, data: supplied.data, value: supplied.value, ...(walletApproval ? {} : { gas: supplied.gas, gasPrice: supplied.gasPrice }) });
|
|
568
517
|
const envelope = reviewed === undefined || walletApproval ? undefined : reviewed;
|
|
569
518
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
570
519
|
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
|
-
}
|
|
520
|
+
const block = await readLifecyclePlanningBlock(client, original.plan, original.account, original.ufg !== undefined);
|
|
521
|
+
const limitSource = options.limits === undefined ? original.limits : options.limits;
|
|
580
522
|
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.
|
|
523
|
+
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled)
|
|
585
524
|
return undefined;
|
|
586
|
-
}
|
|
587
525
|
if (!progress.confirmationSafe)
|
|
588
|
-
throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for the selected
|
|
526
|
+
throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for the selected launch confirmation depth before continuing.");
|
|
589
527
|
if (progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed"))
|
|
590
|
-
throw new LifecyclePlanningError("RECEIPT_PENDING", "Wait for
|
|
528
|
+
throw new LifecyclePlanningError("RECEIPT_PENDING", "Wait for the pending transaction to confirm or resolve its replacement.");
|
|
591
529
|
assertPlanningActive(options.signal);
|
|
530
|
+
const { limits } = await resolveLifecycleLimits({ client, account: original.account, orchestrator: original.plan.orchestrator, block, chainId: original.chainId }, limitSource);
|
|
592
531
|
if (options.action === "cancel") {
|
|
593
532
|
if (progress.canonical.phase === LifecyclePhase.None)
|
|
594
533
|
throw new LifecyclePlanningError("NOT_STARTED", "An unstarted plan has no launch escrow to cancel");
|
|
595
|
-
const {
|
|
596
|
-
assertFinalPlan(planned, options.signal);
|
|
534
|
+
const planned = { ...original, progress };
|
|
597
535
|
const transaction = planned.ufg !== undefined ? buildUfgCancelTransaction({ plan: planned.plan, preparedMarkets: progress.canonical.preparedMarkets })
|
|
598
536
|
: { 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
537
|
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);
|
|
538
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current cancellation");
|
|
539
|
+
const assessment = await assessLifecycleTransactions({ client, planned, limits: limitSource, signal: options.signal }, [transaction], block, envelope);
|
|
540
|
+
if (!assessment.ready)
|
|
541
|
+
throw new LifecyclePlanningError(assessment.code ?? "CANCEL_NOT_READY", assessment.reason ?? "Cancellation could not be prepared. Refresh before continuing.", assessment);
|
|
542
|
+
return finalizeNextTransaction(options, attachEstimates([transaction], assessment)[0], reviewed);
|
|
608
543
|
}
|
|
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
544
|
const funding = progress.canonical.phase === LifecyclePhase.None
|
|
614
|
-
? await readFunding(client,
|
|
545
|
+
? await readFunding(client, original.plan, block, original.account, original.ufg !== undefined)
|
|
615
546
|
: { prerequisites: [], approvals: [] };
|
|
547
|
+
const planned = { ...original, progress, prerequisites: funding.prerequisites };
|
|
548
|
+
const capacity = deriveLifecycleLaunchCapacity({ plan: planned.plan, predictedToken: planned.predictedToken, ufg: planned.ufg, headroomBps: limits.headroomBps });
|
|
549
|
+
if (progress.canonical.phase === LifecyclePhase.None &&
|
|
550
|
+
await selectLaunchMode(planned.mode, planned.plan, capacity, limits, client, block) !== planned.mode) {
|
|
551
|
+
throw new LifecyclePlanningError("MODE_REVIEW_REQUIRED", "This launch requires staged execution. Refresh the review before continuing.");
|
|
552
|
+
}
|
|
616
553
|
let candidate = funding.approvals[0];
|
|
617
554
|
if (candidate === undefined) {
|
|
618
555
|
const prepared = progress.canonical.preparedMarkets;
|
|
@@ -623,23 +560,16 @@ export async function buildNextTransaction(options) {
|
|
|
623
560
|
let count;
|
|
624
561
|
if (reviewed?.kind === "prepare") {
|
|
625
562
|
const span = /^prepare:(\d+):(\d+)$/.exec(reviewed.id);
|
|
626
|
-
|
|
563
|
+
if (Number(span?.[1]) !== prepared)
|
|
564
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation does not start at the unfinished market");
|
|
627
565
|
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
566
|
}
|
|
631
567
|
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
|
-
}
|
|
568
|
+
const batches = await preparationBatches(planned.plan, prepared, planned.ufg, limits, client, block);
|
|
569
|
+
count = batches[prepared > 0 ? 1 : 0];
|
|
640
570
|
}
|
|
641
571
|
if (!Number.isSafeInteger(count) || count <= 0 || prepared + count > planned.plan.markets.length)
|
|
642
|
-
throw new LifecyclePlanningError("INVALID_PREPARATION_BATCHES", "Next preparation must
|
|
572
|
+
throw new LifecyclePlanningError("INVALID_PREPARATION_BATCHES", "Next preparation must cover a positive contiguous unfinished market span");
|
|
643
573
|
command = { kind: "prepare", first: prepared, count };
|
|
644
574
|
}
|
|
645
575
|
else
|
|
@@ -647,32 +577,16 @@ export async function buildNextTransaction(options) {
|
|
|
647
577
|
candidate = buildLifecycleCommand(planned.plan, command, planned.ufg === undefined ? undefined : { account: ufgPlanPrincipal(planned.plan), config: planned.ufg.config });
|
|
648
578
|
}
|
|
649
579
|
if (reviewed !== undefined && !matchesReviewedNextTransaction(candidate, reviewed))
|
|
650
|
-
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current
|
|
651
|
-
const
|
|
580
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current next step");
|
|
581
|
+
const assessment = await assessLifecycleTransactions({ client, planned, limits: limitSource, signal: options.signal }, [candidate], block, envelope, undefined, capacity);
|
|
652
582
|
assertFinalPlan(planned, options.signal);
|
|
653
|
-
if (!
|
|
654
|
-
throw new LifecyclePlanningError("
|
|
655
|
-
|
|
656
|
-
return finalizeNextTransaction(options, next, reviewed);
|
|
583
|
+
if (!assessment.ready)
|
|
584
|
+
throw new LifecyclePlanningError(assessment.code ?? "PLAN_NOT_READY", assessment.reason ?? "The next launch step could not be prepared. Refresh before continuing.", assessment);
|
|
585
|
+
return finalizeNextTransaction(options, attachEstimates([candidate], assessment)[0], reviewed);
|
|
657
586
|
});
|
|
658
587
|
assertInput();
|
|
659
588
|
if (result !== undefined)
|
|
660
589
|
assertReviewedTransactionUnchanged(options, reviewed, result);
|
|
661
590
|
return result;
|
|
662
591
|
}
|
|
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
592
|
//# sourceMappingURL=planner.js.map
|