@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.
Files changed (113) hide show
  1. package/README.md +10 -8
  2. package/dist/lifecycle/auto-compound.d.ts +21 -0
  3. package/dist/lifecycle/auto-compound.d.ts.map +1 -0
  4. package/dist/lifecycle/auto-compound.js +34 -0
  5. package/dist/lifecycle/auto-compound.js.map +1 -0
  6. package/dist/lifecycle/calibration.d.ts +2 -4
  7. package/dist/lifecycle/calibration.d.ts.map +1 -1
  8. package/dist/lifecycle/calibration.js +1 -26
  9. package/dist/lifecycle/calibration.js.map +1 -1
  10. package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.d.ts +73 -0
  11. package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.d.ts.map +1 -0
  12. package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.js +75 -0
  13. package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.js.map +1 -0
  14. package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.d.ts +73 -0
  15. package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.d.ts.map +1 -0
  16. package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.js +75 -0
  17. package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.js.map +1 -0
  18. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.d.ts +71 -0
  19. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.d.ts.map +1 -0
  20. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.js +73 -0
  21. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.js.map +1 -0
  22. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.d.ts +71 -0
  23. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.d.ts.map +1 -0
  24. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.js +73 -0
  25. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.js.map +1 -0
  26. package/dist/lifecycle/gas-profile.d.ts +17 -13
  27. package/dist/lifecycle/gas-profile.d.ts.map +1 -1
  28. package/dist/lifecycle/gas-profile.js +133 -113
  29. package/dist/lifecycle/gas-profile.js.map +1 -1
  30. package/dist/lifecycle/index.d.ts +6 -4
  31. package/dist/lifecycle/index.d.ts.map +1 -1
  32. package/dist/lifecycle/index.js +6 -4
  33. package/dist/lifecycle/index.js.map +1 -1
  34. package/dist/lifecycle/markets.d.ts +4 -0
  35. package/dist/lifecycle/markets.d.ts.map +1 -1
  36. package/dist/lifecycle/markets.js +58 -3
  37. package/dist/lifecycle/markets.js.map +1 -1
  38. package/dist/lifecycle/opening-buys.js +3 -3
  39. package/dist/lifecycle/opening-buys.js.map +1 -1
  40. package/dist/lifecycle/planner.d.ts +6 -9
  41. package/dist/lifecycle/planner.d.ts.map +1 -1
  42. package/dist/lifecycle/planner.js +122 -208
  43. package/dist/lifecycle/planner.js.map +1 -1
  44. package/dist/lifecycle/presets.d.ts +9 -3
  45. package/dist/lifecycle/presets.d.ts.map +1 -1
  46. package/dist/lifecycle/presets.js +17 -1
  47. package/dist/lifecycle/presets.js.map +1 -1
  48. package/dist/lifecycle/raeto-abi.d.ts +175 -0
  49. package/dist/lifecycle/raeto-abi.d.ts.map +1 -0
  50. package/dist/lifecycle/raeto-abi.js +224 -0
  51. package/dist/lifecycle/raeto-abi.js.map +1 -0
  52. package/dist/lifecycle/raeto.d.ts +21 -0
  53. package/dist/lifecycle/raeto.d.ts.map +1 -0
  54. package/dist/lifecycle/raeto.js +59 -0
  55. package/dist/lifecycle/raeto.js.map +1 -0
  56. package/dist/lifecycle/rpc.d.ts +3 -13
  57. package/dist/lifecycle/rpc.d.ts.map +1 -1
  58. package/dist/lifecycle/rpc.js +11 -37
  59. package/dist/lifecycle/rpc.js.map +1 -1
  60. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  61. package/dist/lifecycle/snapshot.js +1 -2
  62. package/dist/lifecycle/snapshot.js.map +1 -1
  63. package/dist/lifecycle/transactions.d.ts +10 -3
  64. package/dist/lifecycle/transactions.d.ts.map +1 -1
  65. package/dist/lifecycle/transactions.js +47 -10
  66. package/dist/lifecycle/transactions.js.map +1 -1
  67. package/dist/lifecycle/types.d.ts +32 -107
  68. package/dist/lifecycle/types.d.ts.map +1 -1
  69. package/dist/lifecycle/types.js +3 -3
  70. package/dist/lifecycle/types.js.map +1 -1
  71. package/dist/lifecycle/ufg.d.ts +1 -1
  72. package/dist/lifecycle/ufg.d.ts.map +1 -1
  73. package/dist/lifecycle/ufg.js +1 -1
  74. package/dist/lifecycle/ufg.js.map +1 -1
  75. package/docs/launch-lifecycle.md +113 -1
  76. package/docs/lifecycle-gas-profiles.md +141 -24
  77. package/docs/reference.md +112 -3
  78. package/examples/.env.example +0 -3
  79. package/examples/README.md +11 -11
  80. package/examples/launch-example-support.ts +4 -9
  81. package/examples/launch-example.ts +12 -28
  82. package/examples/launch-lifecycle-smoke.ts +10 -22
  83. package/examples/launch.ts +8 -11
  84. package/package.json +1 -1
  85. package/src/lifecycle/auto-compound.ts +38 -0
  86. package/src/lifecycle/calibration.ts +3 -23
  87. package/src/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.ts +77 -0
  88. package/src/lifecycle/deployments/auto-compound-static-pool-v1-20261028.ts +77 -0
  89. package/src/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.ts +74 -0
  90. package/src/lifecycle/deployments/raeto-fixed-pool-v1-20261029.ts +74 -0
  91. package/src/lifecycle/gas-profile.ts +125 -104
  92. package/src/lifecycle/index.ts +6 -4
  93. package/src/lifecycle/markets.ts +47 -3
  94. package/src/lifecycle/opening-buys.ts +3 -3
  95. package/src/lifecycle/planner.ts +116 -191
  96. package/src/lifecycle/presets.ts +17 -1
  97. package/src/lifecycle/raeto-abi.ts +223 -0
  98. package/src/lifecycle/raeto.ts +61 -0
  99. package/src/lifecycle/rpc.ts +10 -32
  100. package/src/lifecycle/snapshot.ts +1 -2
  101. package/src/lifecycle/transactions.ts +46 -9
  102. package/src/lifecycle/types.ts +38 -87
  103. package/src/lifecycle/ufg.ts +1 -1
  104. package/dist/lifecycle/fork.d.ts +0 -15
  105. package/dist/lifecycle/fork.d.ts.map +0 -1
  106. package/dist/lifecycle/fork.js +0 -36
  107. package/dist/lifecycle/fork.js.map +0 -1
  108. package/dist/lifecycle/simulation.d.ts +0 -17
  109. package/dist/lifecycle/simulation.d.ts.map +0 -1
  110. package/dist/lifecycle/simulation.js +0 -605
  111. package/dist/lifecycle/simulation.js.map +0 -1
  112. package/src/lifecycle/fork.ts +0 -36
  113. package/src/lifecycle/simulation.ts +0 -543
@@ -2,13 +2,12 @@ import { encodeFunctionData, keccak256, toHex, zeroAddress, type Address, type H
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, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, LIFECYCLE_MAX_ERC404_SUPPLY, parseLaunchPlan, serializeLaunchPlan, type LaunchPlanV1 } from "./schema.js";
5
+ import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LIFECYCLE_MAX_ERC404_SUPPLY, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, parseLaunchPlan, serializeLaunchPlan, type LaunchPlanV1, type MarketConfigV1 } from "./schema.js";
6
6
  import { readLaunchProgress, readLifecycleExecutionProgress, readLifecycleHeadProgress, readLifecycleProfiles, readLifecycleTokenAtBlock, readPoolBoundHookDeployment } from "./progress.js";
7
7
  import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion, type AbyssLifecycleMarketConfig, type V4LifecycleMarketConfig, type V4PoolBoundLifecycleMarketConfig } from "./markets.js";
8
- import { assertLifecycleReadClientOpen, lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleStage, nitroPosterGasBound, readLifecycleChainId, readLifecycleContract, readNitroPrices, registerLifecycleFinalCheck, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
9
- import { observedFields, prepareLaunchSimulation, type NextTransactionEnvelope } from "./simulation.js";
10
- import { LifecyclePlanningError, type BuildNextTransactionOptions, type CanonicalLaunchProgress, type LifecycleBlock, type LifecycleConstructionProfile, type LifecycleFundingPrerequisite, type LifecyclePoolBoundHookDeployment, type LifecycleProfile, type LifecycleRpcClient, type LifecycleSimulation, type LifecycleTransaction, type PlannedLaunch, type PlanLaunchOptions, type PrepareAndPlanLifecycleLaunchOptions, type SimulateLaunchPlanOptions } from "./types.js";
11
- import { deriveKnownPoolBoundHookDeployment, protectOrderedBuyMinimums, validateBuySlippageBps } from "./calibration.js";
8
+ import { lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleStage, nitroPosterGasBound, readLifecycleChainId, readLifecycleContract, readNitroPrices, registerLifecycleFinalCheck, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
9
+ import { LifecyclePlanningError, type BuildNextTransactionOptions, type CanonicalLaunchProgress, type LifecycleBlock, type LifecycleConstructionProfile, type LifecycleFundingPrerequisite, type LifecyclePoolBoundHookDeployment, type LifecycleProfile, type LifecycleRpcClient, type LifecyclePlanningAssessment, type LifecycleTransaction, type PlannedLaunch, type PlanLaunchOptions, type PrepareAndPlanLifecycleLaunchOptions, type ResolvedLifecycleLimits } from "./types.js";
10
+ import { deriveKnownPoolBoundHookDeployment, validateBuySlippageBps } from "./calibration.js";
12
11
  import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
13
12
  import { buildLifecycleCommand, packPreparationBatches, type LifecycleCommand } from "./transactions.js";
14
13
  import { assertLifecycleMarketAllocation, assertLifecycleSupplyAllocation } from "./allocations.js";
@@ -17,8 +16,9 @@ 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 { deriveLifecycleLaunchCapacity, profileLifecycleTransactions, type ProfiledLaunchCapacity } from "./gas-profile.js";
19
+ import { assessLifecycleTransactions, deriveLifecycleLaunchCapacity, type NextTransactionEnvelope, type ProfiledLaunchCapacity } from "./gas-profile.js";
21
20
  import type { LifecycleOpeningBuyQuote } from "./types.js";
21
+ import { isRaetoProfile, RAETO_QUOTE_ASSETS } from "./raeto.js";
22
22
 
23
23
  type ValidatedPlanShape = {
24
24
  commitment: Hex;
@@ -26,12 +26,48 @@ type ValidatedPlanShape = {
26
26
  marketConfigs: (AbyssLifecycleMarketConfig | V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig)[];
27
27
  };
28
28
  type PlanReadContext = {
29
- block?: LifecycleBlock; progress?: CanonicalLaunchProgress; diagnostic?: boolean;
30
- finalEnvelope?: NextTransactionEnvelope; capacity?: ProfiledLaunchCapacity;
31
- openingBuys?: readonly LifecycleOpeningBuyQuote[]; forceSimulation?: boolean; immediate?: boolean;
29
+ block?: LifecycleBlock;
30
+ openingBuys?: readonly LifecycleOpeningBuyQuote[];
32
31
  };
33
32
  const defaultPlanReadContext: PlanReadContext = {};
34
33
 
34
+ function observedFields<T extends object>(value: T): () => boolean {
35
+ const keys = Object.keys(value) as (keyof T)[];
36
+ const values = keys.map((key) => value[key]);
37
+ return () => keys.length === Object.keys(value).length && keys.every((key, index) => value[key] === values[index]);
38
+ }
39
+
40
+ async function selectLaunchMode(requested: "atomic" | "staged", plan: LaunchPlanV1, capacity: ProfiledLaunchCapacity | undefined,
41
+ limits: ResolvedLifecycleLimits, client: LifecycleRpcClient, block: LifecycleBlock): Promise<"atomic" | "staged"> {
42
+ if (requested !== "atomic" || plan.markets.length !== 1 || plan.funding.some((funding) => funding.kind === LifecycleFundingKind.Swap) ||
43
+ capacity?.atomic === undefined || capacity.bounds.atomicComputeGasUpperBound === undefined) return "staged";
44
+ const poster = limits.protocol === "nitro" ? nitroPosterGasBound(capacity.atomic, await readNitroPrices(client, block)).posterGas : 0n;
45
+ return capacity.bounds.atomicComputeGasUpperBound + poster <= limits.executionGasCeiling ? "atomic" : "staged";
46
+ }
47
+
48
+ async function preparationBatches(plan: LaunchPlanV1, prepared: number, ufg: PlanLaunchOptions["ufg"], limits: ResolvedLifecycleLimits,
49
+ client: LifecycleRpcClient, block: LifecycleBlock): Promise<number[]> {
50
+ if (prepared === plan.markets.length) return prepared === 0 ? [] : [prepared];
51
+ const route = ufg === undefined ? undefined : { account: ufgPlanPrincipal(plan), config: ufg.config };
52
+ const sample = buildLifecycleCommand(plan, { kind: "prepare", first: prepared, count: 1 }, route);
53
+ const poster = limits.protocol === "nitro" ? nitroPosterGasBound(sample, await readNitroPrices(client, block)).posterGas : 0n;
54
+ return packPreparationBatches(plan, prepared, limits.executionGasCeiling - poster, limits.headroomBps);
55
+ }
56
+
57
+ /** Preserve Raeto's outer quote/profile binding even on known-preset recovery. */
58
+ function validateRaetoMarketContext(market: MarketConfigV1, config?: V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig): void {
59
+ if (!isRaetoProfile(market.profileId)) return;
60
+ if (market.configVersion !== 6 || !RAETO_QUOTE_ASSETS.some((quote) => quote.toLowerCase() === market.quoteAsset.toLowerCase())) {
61
+ throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto requires config6 and a WETH or USDG quote");
62
+ }
63
+ // Normal planning supplies its already decoded config. Continuation must also
64
+ // reject malformed fee/share tails before reading or building its next command.
65
+ const decoded = config ?? decodePoolBoundV4LifecycleMarketConfig(market.config, 6);
66
+ if (decoded.version !== 6 || decoded.profileId.toLowerCase() !== market.profileId.toLowerCase()) {
67
+ throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto config must bind its market's Raeto profile");
68
+ }
69
+ }
70
+
35
71
  function validatePlanShape(plan: LaunchPlanV1): ValidatedPlanShape {
36
72
  // Encoding checks all exact integer widths, addresses, byte strings and tuple fields.
37
73
  const commitment = hashLaunchPlan(plan);
@@ -64,6 +100,7 @@ function validatePlanShape(plan: LaunchPlanV1): ValidatedPlanShape {
64
100
  if (market.configVersion !== 1 && market.configVersion !== 4 && !isPoolBoundV4ConfigVersion(market.configVersion)) throw new LifecyclePlanningError("UNSUPPORTED_CONFIG_VERSION", "Encode reviewed SharedV4 version 4 or pool-bound version 5/6 only on its exact matching deployment");
65
101
  if (market.configVersion === 4) config = decodeV4LifecycleMarketConfig(market.config);
66
102
  else if (isPoolBoundV4ConfigVersion(market.configVersion)) config = decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion);
103
+ validateRaetoMarketContext(market, config);
67
104
  if (config !== undefined) {
68
105
  if (config.profileId.toLowerCase() !== market.profileId.toLowerCase()) throw new LifecyclePlanningError("PROFILE_TERMS_MISMATCH", "Inner config profile differs from the committed market profile");
69
106
  const quote = market.quoteAsset.toLowerCase();
@@ -164,14 +201,12 @@ function orderedTransactions(plan: LaunchPlanV1, mode: "atomic" | "staged", prog
164
201
  return transactions.map((transaction, index) => ({ ...transaction, calldataBytes: (transaction.data.length - 2) / 2, dependencies: index === 0 ? [] : [transactions[index - 1]!.id] }));
165
202
  }
166
203
 
167
- function attachEstimates(transactions: readonly LifecycleTransaction[], simulation: LifecycleSimulation): LifecycleTransaction[] {
204
+ function attachEstimates(transactions: readonly LifecycleTransaction[], assessment: LifecyclePlanningAssessment): LifecycleTransaction[] {
168
205
  return transactions.map((transaction, index) => {
169
206
  if (index !== 0) return transaction;
170
- const admission = { admitted: simulation.admitted, confidence: simulation.confidence, executionProof: simulation.executionProof, protocolFit: simulation.protocolFit,
171
- blockNumber: simulation.blockNumber, blockHash: simulation.blockHash, account: simulation.account, chainId: simulation.chainId, reason: simulation.reason, limits: simulation.limits };
172
- if (transaction.kind === "approve" || transaction.kind === "approve-reset") return { ...transaction, admission };
173
- const estimate = simulation.steps.find((step) => step.transactionId === transaction.id)?.estimate ?? simulation.estimate;
174
- return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate, admission };
207
+ if (transaction.kind === "approve" || transaction.kind === "approve-reset") return { ...transaction, assessment };
208
+ const estimate = assessment.estimate;
209
+ return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate, assessment };
175
210
  });
176
211
  }
177
212
  type ReviewedNextTransaction = Pick<LifecycleTransaction, "id" | "kind" | "chainId" | "from" | "to" | "data" | "value"> & Partial<NextTransactionEnvelope>;
@@ -203,16 +238,16 @@ function matchesReviewedNextTransaction(next: LifecycleTransaction | ReviewedNex
203
238
  function assertReviewedTransactionUnchanged(options: BuildNextTransactionOptions, reviewed: ReviewedNextTransaction | undefined, next: LifecycleTransaction): void {
204
239
  if (options.planned.ufg !== undefined) assertUfgLaunchAccount(options.planned.plan, options.planned.account);
205
240
  if (options.planned.account.toLowerCase() !== next.from.toLowerCase() || options.planned.ufg === undefined && options.planned.plan.creator.toLowerCase() !== next.from.toLowerCase()) {
206
- throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account changed during fresh execution or submission proof");
241
+ throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account changed while preparing the reviewed transaction");
207
242
  }
208
243
  if (options.planned.chainId !== BigInt(next.chainId) || options.planned.plan.chainId !== BigInt(next.chainId)) {
209
- throw new LifecyclePlanningError("CHAIN_MISMATCH", "Execution chain changed during fresh execution or submission proof");
244
+ throw new LifecyclePlanningError("CHAIN_MISMATCH", "Execution chain changed while preparing the reviewed transaction");
210
245
  }
211
246
  const current = readReviewedNextTransaction(options.reviewedTransaction);
212
247
  if (reviewed === undefined ? current !== undefined : current === undefined || !matchesReviewedNextTransaction(current, reviewed) || !matchesReviewedNextTransaction(next, reviewed) ||
213
248
  reviewed.kind !== "approve" && reviewed.kind !== "approve-reset" &&
214
249
  (current.gas !== reviewed.gas || current.gasPrice !== reviewed.gasPrice || next.gas !== reviewed.gas || next.gasPrice !== reviewed.gasPrice)) {
215
- throw new LifecyclePlanningError("REVIEWED_TRANSACTION_CHANGED", "Reviewed transaction changed during fresh execution or submission proof");
250
+ throw new LifecyclePlanningError("REVIEWED_TRANSACTION_CHANGED", "Reviewed transaction changed while preparing it");
216
251
  }
217
252
  }
218
253
 
@@ -227,16 +262,6 @@ async function readTokenFactoryBinding(client: LifecycleRpcClient, orchestrator:
227
262
  return { tokenFactory, tokenFactoryCodeHash: keccak256(code) };
228
263
  }
229
264
 
230
- function assertPlannedDeploymentBindings(stored: PlannedLaunch, current: PlannedLaunch): void {
231
- if (hashLaunchPlan(stored.plan).toLowerCase() !== stored.planHash.toLowerCase() || current.planHash.toLowerCase() !== stored.planHash.toLowerCase() || current.launchId.toLowerCase() !== stored.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its finalized commitment");
232
- if (stored.ufg === undefined ? current.ufg !== undefined : current.ufg === undefined || hashUfgLaunchConfig(stored.ufg.config) !== stored.ufg.configHash || current.ufg.configHash !== stored.ufg.configHash) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored UFG route or treasury config no longer matches its finalized commitment");
233
- if (current.predictedToken.toLowerCase() !== stored.predictedToken.toLowerCase() || current.tokenFactory.toLowerCase() !== stored.tokenFactory.toLowerCase() || current.tokenFactoryCodeHash.toLowerCase() !== stored.tokenFactoryCodeHash.toLowerCase()) throw new LifecyclePlanningError("TOKEN_FACTORY_BINDING", "Final token prediction or its immutable factory code binding changed before execution");
234
- if (!current.simulation.admitted || current.progress.canonical.phase === LifecyclePhase.Active || current.progress.canonical.phase === LifecyclePhase.Cancelled) return;
235
- if (stored.hookDeployments.length !== current.hookDeployments.length || stored.hookDeployments.some((deployment, index) => {
236
- const fresh = current.hookDeployments[index];
237
- 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();
238
- })) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Final bound-hook deployer, initcode, salt or prediction changed before wallet execution");
239
- }
240
265
 
241
266
  function assertPlanningActive(signal?: AbortSignal): void {
242
267
  if (signal?.aborted) throw Object.assign(new Error("Lifecycle launch planning cancelled"), { name: "AbortError" });
@@ -289,18 +314,9 @@ function assertFinalPlan(planned: PlannedLaunch, signal?: AbortSignal): void {
289
314
  if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash) throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed during asynchronous planning");
290
315
  }
291
316
 
292
- async function evaluateLaunchTransactions(options: SimulateLaunchPlanOptions, transactions: readonly LifecycleTransaction[], block: LifecycleBlock, controls: PlanReadContext = defaultPlanReadContext): Promise<LifecycleSimulation> {
293
- const profile = controls.diagnostic ? undefined : await profileLifecycleTransactions(options, transactions, block, controls.finalEnvelope, controls.openingBuys, controls.capacity);
294
- if (!controls.forceSimulation && profile?.profileOnly) return profile.simulation;
295
- const prepared = prepareLaunchSimulation(options, block, transactions.length !== 0, controls.immediate);
296
- try {
297
- const simulation = controls.diagnostic ? await prepared.measure(transactions) : await prepared(transactions, controls.finalEnvelope);
298
- return profile === undefined ? simulation : { ...simulation, gasProfile: profile.simulation.gasProfile };
299
- } finally { prepared.close(); }
300
- }
301
317
 
302
- /** One owner constructs and mines, optionally measures actual ordered buy outputs,
303
- * then proves only the final protected commitment. No cached execution certificates. */
318
+ /** One owner mines constructor-bound salts and protects ordered buys with exact
319
+ * local curve math. Launch calldata is never executed during planning. */
304
320
  export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifecycleLaunchOptions): Promise<PlannedLaunch> {
305
321
  const { commitment, ufgConfigHash } = validatePlanningIntent(options);
306
322
  if (options.buySlippageBps !== undefined) validateBuySlippageBps(options.buySlippageBps);
@@ -334,9 +350,11 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
334
350
  }
335
351
  assertDraft();
336
352
  let plan: LaunchPlanV1 = { ...draft, markets };
337
- let ownedProgress: CanonicalLaunchProgress | undefined;
338
353
  let openingBuys: PlanReadContext["openingBuys"];
339
- if (needsProtection && hasProfiledOpeningMath(plan)) {
354
+ if (needsProtection) {
355
+ if (!hasProfiledOpeningMath(plan)) throw new LifecyclePlanningError("OPENING_BUY_NOT_PROFILED", "Exact opening-buy math is unavailable for this profile. Use a supported profile or update the SDK.");
356
+ const head = await readLifecycleHeadProgress(client, plan, options.account, block);
357
+ if (head.phase !== LifecyclePhase.None) throw new LifecyclePlanningError("LAUNCH_ALREADY_STARTED", "Buy protection cannot replace an already committed launch plan");
340
358
  const predictedToken = await readLifecycleTokenAtBlock(client, plan, block);
341
359
  openingBuys = computeLifecycleOpeningBuys({ plan, predictedToken });
342
360
  const factor = BigInt(10000 - options.buySlippageBps!);
@@ -345,19 +363,8 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
345
363
  if (minTokenOut <= 0n) throw new LifecyclePlanningError("INVALID_BUY_OUTPUT", "Each opening buy must produce a positive protected minimum");
346
364
  return { ...buy, minTokenOut };
347
365
  }) };
348
- } else if (needsProtection) {
349
- // A diagnostic minimum is never returned as an admitted or executable plan.
350
- // Price guards, funding minima, recipients and all ordered budgets stay exact.
351
- const diagnosticPlan = { ...plan, buys: plan.buys.map((buy) => ({ ...buy, minTokenOut: 1n })) };
352
- const diagnostic = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan: diagnosticPlan }, client, { block, diagnostic: true }));
353
- assertDraft();
354
- if (diagnostic.progress.canonical.phase !== LifecyclePhase.None || diagnostic.progress.head.phase !== LifecyclePhase.None) throw new LifecyclePlanningError("LAUNCH_ALREADY_STARTED", "Buy protection cannot replace an already committed launch plan");
355
- plan = protectOrderedBuyMinimums(diagnostic, options.buySlippageBps!);
356
- // This is the real SDK-owned None observation, not an assumed new-launch
357
- // state. No onchain commitment existed at this exact snapshot to replace.
358
- ownedProgress = { ...diagnostic.progress, planHash: hashLaunchPlan(plan) };
359
366
  }
360
- const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, { block, progress: ownedProgress, openingBuys }));
367
+ const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, { block, openingBuys }));
361
368
  assertDraft();
362
369
  assertFinalPlan(planned, options.signal);
363
370
  if (planned.progress.canonical.phase !== LifecyclePhase.Active && planned.progress.canonical.phase !== LifecyclePhase.Cancelled) {
@@ -366,7 +373,7 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
366
373
  if (final === undefined || final.deployer.toLowerCase() !== mined.deployer.toLowerCase() || final.initCodeHash.toLowerCase() !== mined.initCodeHash.toLowerCase() || final.salt.toLowerCase() !== mined.salt.toLowerCase() || final.predictedHook.toLowerCase() !== mined.predictedHook.toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Final exact constructor binding differs from the one locally mined candidate");
367
374
  }
368
375
  }
369
- if (needsProtection && !planned.simulation.admitted) throw new LifecyclePlanningError("PROTECTED_PLAN_NOT_ADMITTED", planned.simulation.reason ?? "The final protected launch did not establish exact execution proof", planned.simulation);
376
+ if (needsProtection && !planned.assessment.ready) 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);
370
377
  return planned;
371
378
  });
372
379
  assertDraft();
@@ -374,7 +381,7 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
374
381
  return result;
375
382
  }
376
383
 
377
- /** Independently fresh current-state admission of an already finalized commitment. No broadcasts. */
384
+ /** Fresh pinned input and deterministic gas-budget checks. No simulation or broadcasts. */
378
385
  export async function planLaunch(options: PlanLaunchOptions): Promise<PlannedLaunch> {
379
386
  const result = await withLifecycleReadClient(options.client, async (client) => {
380
387
  const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads(options, client));
@@ -386,44 +393,34 @@ export async function planLaunch(options: PlanLaunchOptions): Promise<PlannedLau
386
393
  }
387
394
 
388
395
  async function planLaunchWithReads(options: PlanLaunchOptions, client: LifecycleRpcClient, readContext: PlanReadContext = defaultPlanReadContext): Promise<PlannedLaunch> {
389
- const { block: pinnedBlock, progress: ownedProgress, diagnostic = false, openingBuys, forceSimulation = false } = readContext;
390
- const { plan, account, mode: requestedMode, ufg } = options;
391
- let mode = requestedMode;
396
+ const { block: pinnedBlock, openingBuys } = readContext;
397
+ const { plan, account, ufg } = options;
392
398
  const { commitment: planHash, marketConfigs, ufgConfigHash } = validatePlanningIntent(options);
393
399
  const launchId = hashLaunchIdentity(plan);
394
400
  const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
395
401
  pinnedBlock ?? readLifecyclePlanningBlock(client, plan, account, ufg !== undefined), readLifecycleChainId(client),
396
402
  ]));
397
403
  if (chainId !== plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed plan");
398
- // Only preparation/build-next can supply freshly observed progress from this
399
- // still-open SDK invocation. Stored/query DTOs never enter this continuation.
400
- if (ownedProgress !== undefined) {
401
- if (!assertLifecycleReadClientOpen(client) || pinnedBlock !== block || ownedProgress.blockNumber !== block.number ||
402
- ownedProgress.blockHash.toLowerCase() !== block.hash.toLowerCase() || ownedProgress.chainId !== plan.chainId ||
403
- ownedProgress.confirmationDepth !== (options.confirmations ?? 1)) throw new LifecyclePlanningError("INVALID_READ_CONTEXT", "Owned progress differs from its active planning snapshot");
404
- if (ownedProgress.planHash.toLowerCase() !== planHash.toLowerCase() || ownedProgress.launchId.toLowerCase() !== launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed after its canonical progress was read");
405
- }
406
404
  assertPlanningActive(options.signal);
407
405
  const preset = getKnownLifecycleDeployment({ chainId: plan.chainId, orchestrator: plan.orchestrator });
408
406
  const [{ tokenFactory, tokenFactoryCodeHash }, { limits }, { identity, progress }] = await lifecycleStage(client, "plan.domain", () => Promise.all([
409
407
  preset ?? readTokenFactoryBinding(client, plan.orchestrator, block),
410
408
  resolveLifecycleLimits({ client, account, orchestrator: plan.orchestrator, block, chainId: plan.chainId }, options.limits),
411
409
  lifecycleStage(client, "plan.progress", async () => {
412
- const [predictedToken] = await Promise.all([
413
- readLifecycleTokenAtBlock(client, plan, block),
414
- ownedProgress === undefined ? readLifecycleHeadProgress(client, plan, account, block) : undefined,
410
+ const [predictedToken, head] = await Promise.all([
411
+ readLifecycleTokenAtBlock(client, plan, block), readLifecycleHeadProgress(client, plan, account, block),
415
412
  ]);
413
+ const mode = head.phase === LifecyclePhase.None ? options.mode : head.mode === 1 ? "staged" as const : "atomic" as const;
416
414
  const identity = { plan, planHash, launchId, predictedToken, account, mode, confirmations: options.confirmations ?? 1,
417
415
  ...(ufg === undefined ? {} : { ufg: { config: ufg.config, configHash: ufgConfigHash! } }) };
418
416
  if (ufg !== undefined) validateUfgLaunchPlan(plan, ufg.config, predictedToken);
419
- const progress = ownedProgress ?? await readLaunchProgress({ client, planned: identity, receipts: options.receipts, signal: options.signal }, block);
420
- if (ownedProgress !== undefined && progress.token.toLowerCase() !== predictedToken.toLowerCase()) throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed after its canonical progress was read");
417
+ const progress = await readLaunchProgress({ client, planned: identity, receipts: options.receipts, signal: options.signal }, block);
421
418
  return { identity, progress };
422
419
  }),
423
420
  ]));
424
421
  assertPlanningActive(options.signal);
425
- const placeholder: LifecycleSimulation = { backend: "unavailable", confidence: "provisional", admitted: false, executionProof: "unavailable", protocolFit: "unknown", blockNumber: block.number, blockHash: block.hash, account, chainId: plan.chainId, limits, steps: [], deferredTransactionIds: [] };
426
- let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, simulation: placeholder, limits: options.limits,
422
+ const placeholder: LifecyclePlanningAssessment = { ready: false, blockNumber: block.number, blockHash: block.hash, account, chainId: plan.chainId, limits };
423
+ let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, assessment: placeholder, limits: options.limits,
427
424
  allowTopologyGaps: options.allowTopologyGaps === true, allowMissingPermanentTail: options.allowMissingPermanentTail === true };
428
425
  const receiptsPending = progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed");
429
426
  if (!progress.confirmationSafe || receiptsPending) {
@@ -434,72 +431,35 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
434
431
  const known = deriveKnownPoolBoundHookDeployment({ plan, token: identity.predictedToken, marketIndex });
435
432
  hookDeployments.push({ marketIndex, ...(known?.deployment ?? await readPoolBoundHookDeployment({ client, plan, marketIndex }, block)) });
436
433
  }
437
- const waiting = { ...placeholder, confidence: "stateful" as const, reason: !progress.confirmationSafe ? "Canonical launch progress has not reached the selected confirmation depth" : "Wait for canonical receipt confirmations or resolve replacement before requesting another transaction" };
438
-
439
- return { ...planned, hookDeployments, simulation: waiting };
434
+ return { ...planned, hookDeployments, assessment: { ...placeholder,
435
+ code: !progress.confirmationSafe ? "UNCONFIRMED_STATE" : "RECEIPT_PENDING",
436
+ reason: !progress.confirmationSafe ? "Wait for the selected launch confirmation depth before continuing." : "Wait for the pending transaction to confirm or resolve its replacement." } };
440
437
  }
441
438
  if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) {
442
-
443
- return { ...planned, simulation: { ...placeholder, confidence: "stateful", executionProof: "proved", protocolFit: "proved", admitted: true, reason: "Canonical launch is terminal; no transaction remains" } };
439
+ return { ...planned, assessment: { ...placeholder, ready: true } };
444
440
  }
445
- // Topology is a creation policy, not a retroactive obstacle to recovery of
446
- // immutable onchain commitments. Waiting and terminal launches returned above.
447
441
  if (progress.canonical.phase === LifecyclePhase.None && progress.head.phase === LifecyclePhase.None) validatePlanTopology(options, marketConfigs, identity.predictedToken);
448
442
  const [funding, metadata] = await lifecycleStage(client, "plan.inputs", () => Promise.all([
449
443
  progress.canonical.phase === LifecyclePhase.None ? readFunding(client, plan, block, ufg === undefined ? plan.creator : account, ufg !== undefined) : { prerequisites: [], approvals: [] },
450
444
  readPlanMetadata(client, plan, identity.predictedToken, block),
451
445
  ]));
452
446
  assertFinalPlan(planned, options.signal);
453
- const capacity = limits.protocol === "nitro" && limits.arbOSVersion === 61n
454
- ? deriveLifecycleLaunchCapacity({ plan, predictedToken: identity.predictedToken, ufg, headroomBps: limits.headroomBps }, openingBuys) : undefined;
455
- if (!forceSimulation && progress.canonical.phase === LifecyclePhase.None && mode === "atomic" && capacity !== undefined) {
456
- const prices = await readNitroPrices(client, block);
457
- if (capacity.bounds.atomicComputeGasUpperBound + nitroPosterGasBound(capacity.atomic, prices).posterGas > limits.executionGasCeiling) mode = "staged";
458
- }
447
+ const capacity = deriveLifecycleLaunchCapacity({ plan, predictedToken: identity.predictedToken, ufg, headroomBps: limits.headroomBps }, openingBuys);
448
+ const mode = progress.canonical.phase === LifecyclePhase.None
449
+ ? await selectLaunchMode(options.mode, plan, capacity, limits, client, block) : identity.mode;
459
450
  planned = { ...planned, mode, prerequisites: funding.prerequisites, profiles: metadata.profiles, hookDeployments: metadata.hookDeployments };
460
- const prepared = progress.canonical.preparedMarkets, computeCap = limits.executionGasCeiling;
461
- let batches: number[] = mode === "staged" ? packPreparationBatches(plan, prepared, computeCap) : [];
462
- let transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
463
- if (metadata.reasons.length > 0) {
464
-
465
- return { ...planned, simulation: { ...placeholder, confidence: "stateful", reason: metadata.reasons.join("; "), failureCategory: "semantic" }, transactions, totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
451
+ if (metadata.reasons.length > 0) return { ...planned, assessment: { ...placeholder, code: "PROFILE_UNAVAILABLE", failureCategory: "semantic",
452
+ reason: "The selected launch profile could not be read or is unavailable. Refresh the review before continuing." } };
453
+ let batches: number[];
454
+ try { batches = mode === "staged" ? await preparationBatches(plan, progress.canonical.preparedMarkets, ufg, limits, client, block) : []; }
455
+ catch (failure) {
456
+ if (!(failure instanceof LifecyclePlanningError) || failure.code !== "PREPARATION_GAS_LIMIT") throw failure;
457
+ return { ...planned, assessment: { ...placeholder, code: failure.code, reason: failure.message, failureCategory: "capacity", capacityConstraint: "preparation" } };
466
458
  }
467
- const execute = (candidate: readonly LifecycleTransaction[], quotes = openingBuys) => evaluateLaunchTransactions(
468
- { client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, candidate, block,
469
- { diagnostic, openingBuys: quotes, capacity, forceSimulation });
470
- let simulation = await execute(transactions);
459
+ const transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
460
+ const assessment = await assessLifecycleTransactions({ client, planned, limits: options.limits, signal: options.signal }, transactions, block, undefined, openingBuys, capacity);
471
461
  assertFinalPlan(planned, options.signal);
472
- if (!diagnostic && !forceSimulation && requestedMode === "atomic" && mode === "atomic" && progress.canonical.phase === LifecyclePhase.None &&
473
- simulation.executionProof === "failed" && simulation.failureCategory === "capacity" && simulation.capacityConstraint === "compute") {
474
- mode = "staged"; planned = { ...planned, mode };
475
- batches = packPreparationBatches(plan, prepared, computeCap);
476
- transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
477
- simulation = await execute(transactions);
478
- assertFinalPlan(planned, options.signal);
479
- }
480
- if (mode === "staged") {
481
- // Only pressure at the native ceiling can invalidate packing. A smaller
482
- // affordability/RPC/account envelope is not evidence for another wallet write.
483
- while (!simulation.admitted && simulation.failedTransactionId?.startsWith("prepare:")) {
484
- const failedIndex = transactions.findIndex((transaction) => transaction.id === simulation.failedTransactionId);
485
- const failedTransaction = transactions[failedIndex];
486
- const step = simulation.steps.find((candidate) => candidate.transactionId === simulation.failedTransactionId);
487
- const count = failedTransaction?.marketCount ?? 0;
488
- if (count <= 1 || step?.gasUsed === undefined || step.gasLimit === undefined || step.gasLimit < limits.executionGasCeiling || step.gasUsed * 100n < limits.executionGasCeiling * 95n) break;
489
- let first = 0;
490
- const batchIndex = batches.findIndex((batch) => { const matches = first === failedTransaction?.marketStart; first += batch; return matches; });
491
- const left = Math.ceil(count / 2);
492
- batches.splice(batchIndex, 1, left, count - left);
493
- transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
494
- simulation = await execute(transactions, simulation.gasProfile?.initialBuys);
495
- assertFinalPlan(planned, options.signal);
496
- }
497
- if (!simulation.admitted && simulation.failedTransactionId === "activate" && simulation.failureCategory === "capacity") simulation = {
498
- ...simulation, capacityConstraint: "activation", reason: "Too many positions or pools for one activation. Reduce them to fit the native transaction ceiling." };
499
- } else if (!diagnostic && !simulation.admitted) {
500
- simulation = { ...simulation, reason: simulation.reason ?? "The exact atomic launch is not admitted" };
501
- }
502
- return { ...planned, simulation, transactions: attachEstimates(transactions, simulation), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
462
+ return { ...planned, assessment, transactions: attachEstimates(transactions, assessment), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
503
463
  }
504
464
 
505
465
  function finalizeNextTransaction(options: BuildNextTransactionOptions, next: LifecycleTransaction, reviewed?: ReviewedNextTransaction): LifecycleTransaction {
@@ -508,63 +468,49 @@ function finalizeNextTransaction(options: BuildNextTransactionOptions, next: Lif
508
468
  return { ...next, dependencies: [] };
509
469
  }
510
470
 
511
- /** Recovery observes real canonical receipts, then evaluates only unfinished
512
- * execution; the wallet owns its nonce and assignment at send time. */
471
+ /** Recovery observes actual canonical receipts, then constructs and budgets only
472
+ * the next command. No launch transaction is executed during planning. */
513
473
  export async function buildNextTransaction(options: BuildNextTransactionOptions): Promise<LifecycleTransaction | undefined> {
474
+ if (options.action !== "cancel") for (const market of options.planned.plan.markets) validateRaetoMarketContext(market);
514
475
  const original = options.planned, matches = observedFields(original);
515
- const fullReviewAdmitted = original.simulation?.admitted === true;
516
476
  const assertInput = () => {
517
477
  if (options.planned !== original || !matches()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored launch context changed while constructing its next transaction");
518
478
  assertFinalPlan(original, options.signal);
519
479
  };
520
480
  const supplied = readReviewedNextTransaction(options.reviewedTransaction);
521
- // Snapshot only the reviewed wire fields; caller proofs and commands are never
522
- // reused, and final checks compare the mutable input with this one local copy.
523
481
  const walletApproval = supplied?.kind === "approve" || supplied?.kind === "approve-reset";
524
482
  const reviewed = supplied === undefined ? undefined : Object.freeze({ id: supplied.id, kind: supplied.kind, chainId: supplied.chainId,
525
483
  from: supplied.from, to: supplied.to, data: supplied.data, value: supplied.value, ...(walletApproval ? {} : { gas: supplied.gas, gasPrice: supplied.gasPrice }) });
526
484
  const envelope = reviewed === undefined || walletApproval ? undefined : reviewed as NextTransactionEnvelope;
527
485
  const result = await withLifecycleReadClient(options.client, async (client) => {
528
486
  registerLifecycleFinalCheck(client, assertInput);
529
- const block = await readLifecyclePlanningBlock(client, options.planned.plan, options.planned.account, options.planned.ufg !== undefined);
530
- const limitSource = options.limits === undefined ? options.planned.limits : options.limits;
531
- if (getKnownLifecycleDeployment({ chainId: options.planned.chainId, orchestrator: options.planned.plan.orchestrator }) !== undefined && typeof limitSource !== "function") {
532
- await Promise.all([
533
- readLifecycleTokenAtBlock(client, options.planned.plan, block),
534
- readLifecycleHeadProgress(client, options.planned.plan, options.planned.account, block),
535
- resolveLifecycleLimits({ client, account: options.planned.account, orchestrator: options.planned.plan.orchestrator, block, chainId: options.planned.chainId }, limitSource),
536
- ]);
537
- }
487
+ const block = await readLifecyclePlanningBlock(client, original.plan, original.account, original.ufg !== undefined);
488
+ const limitSource = options.limits === undefined ? original.limits : options.limits;
538
489
  const progress = await readLifecycleExecutionProgress({ ...options, client }, block);
539
- if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) {
540
- // Only the confirmed canonical terminal phase is terminal; a head-only
541
- // Active observation is not, and the launch-state/receipt confirmation
542
- // policy below remains in force for every nonterminal case.
543
-
544
- return undefined;
545
- }
546
- if (!progress.confirmationSafe) throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for the selected canonical launch confirmation depth before constructing the next transaction");
547
- if (progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed")) throw new LifecyclePlanningError("RECEIPT_PENDING", "Wait for canonical receipt confirmations or resolve replacement before requesting another transaction");
490
+ if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) return undefined;
491
+ if (!progress.confirmationSafe) throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for the selected launch confirmation depth before continuing.");
492
+ if (progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed")) throw new LifecyclePlanningError("RECEIPT_PENDING", "Wait for the pending transaction to confirm or resolve its replacement.");
548
493
  assertPlanningActive(options.signal);
494
+ const { limits } = await resolveLifecycleLimits({ client, account: original.account, orchestrator: original.plan.orchestrator, block, chainId: original.chainId }, limitSource);
549
495
  if (options.action === "cancel") {
550
496
  if (progress.canonical.phase === LifecyclePhase.None) throw new LifecyclePlanningError("NOT_STARTED", "An unstarted plan has no launch escrow to cancel");
551
- const { planned } = options;
552
- assertFinalPlan(planned, options.signal);
497
+ const planned = { ...original, progress };
553
498
  const transaction: LifecycleTransaction = planned.ufg !== undefined ? buildUfgCancelTransaction({ plan: planned.plan, preparedMarkets: progress.canonical.preparedMarkets })
554
499
  : { 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 }] };
555
- if (reviewed !== undefined && !matchesReviewedNextTransaction(transaction, reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical cancellation");
556
- const simulation = await evaluateLaunchTransactions({ client, planned, limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork, signal: options.signal }, [transaction], block, { finalEnvelope: envelope, immediate: true });
557
- if (!simulation.admitted) throw new LifecyclePlanningError("CANCEL_NOT_ADMITTED", simulation.reason ?? "Cancellation could not be proved against current canonical state", simulation);
558
- const next = attachEstimates([transaction], simulation)[0];
559
- if (next === undefined) throw new LifecyclePlanningError("MISSING_GAS_PROOF", "Cancellation omitted its exact simulation admission", simulation);
560
- return finalizeNextTransaction(options, next, reviewed);
500
+ if (reviewed !== undefined && !matchesReviewedNextTransaction(transaction, reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current cancellation");
501
+ const assessment = await assessLifecycleTransactions({ client, planned, limits: limitSource, signal: options.signal }, [transaction], block, envelope);
502
+ if (!assessment.ready) throw new LifecyclePlanningError(assessment.code ?? "CANCEL_NOT_READY", assessment.reason ?? "Cancellation could not be prepared. Refresh before continuing.", assessment);
503
+ return finalizeNextTransaction(options, attachEstimates([transaction], assessment)[0]!, reviewed);
561
504
  }
562
- const { planned } = options;
563
- assertFinalPlan(planned, options.signal);
564
- if (progress.canonical.phase === LifecyclePhase.None && !fullReviewAdmitted) 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");
565
505
  const funding = progress.canonical.phase === LifecyclePhase.None
566
- ? await readFunding(client, planned.plan, block, planned.account, planned.ufg !== undefined)
506
+ ? await readFunding(client, original.plan, block, original.account, original.ufg !== undefined)
567
507
  : { prerequisites: [], approvals: [] };
508
+ const planned = { ...original, progress, prerequisites: funding.prerequisites };
509
+ const capacity = deriveLifecycleLaunchCapacity({ plan: planned.plan, predictedToken: planned.predictedToken, ufg: planned.ufg, headroomBps: limits.headroomBps });
510
+ if (progress.canonical.phase === LifecyclePhase.None &&
511
+ await selectLaunchMode(planned.mode, planned.plan, capacity, limits, client, block) !== planned.mode) {
512
+ throw new LifecyclePlanningError("MODE_REVIEW_REQUIRED", "This launch requires staged execution. Refresh the review before continuing.");
513
+ }
568
514
  let candidate = funding.approvals[0];
569
515
  if (candidate === undefined) {
570
516
  const prepared = progress.canonical.preparedMarkets;
@@ -574,46 +520,25 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
574
520
  let count: number;
575
521
  if (reviewed?.kind === "prepare") {
576
522
  const span = /^prepare:(\d+):(\d+)$/.exec(reviewed.id);
577
- const first = Number(span?.[1]);
523
+ if (Number(span?.[1]) !== prepared) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation does not start at the unfinished market");
578
524
  count = Number(span?.[2]);
579
- if (first !== prepared) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation does not start at the canonical unfinished market");
580
525
  } else {
581
- const stored = planned.transactions.find((transaction) => transaction.kind === "prepare" && transaction.marketStart! <= prepared && transaction.marketStart! + transaction.marketCount! > prepared);
582
- if (stored !== undefined) count = stored.marketStart! + stored.marketCount! - prepared;
583
- else {
584
- const { limits } = await resolveLifecycleLimits({ client, account: planned.account, orchestrator: planned.plan.orchestrator, block, chainId: planned.chainId }, limitSource);
585
- const cap = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
586
- count = packPreparationBatches(planned.plan, prepared, cap)[prepared > 0 ? 1 : 0]!;
587
- }
526
+ const batches = await preparationBatches(planned.plan, prepared, planned.ufg, limits, client, block);
527
+ count = batches[prepared > 0 ? 1 : 0]!;
588
528
  }
589
- if (!Number.isSafeInteger(count) || count <= 0 || prepared + count > planned.plan.markets.length) throw new LifecyclePlanningError("INVALID_PREPARATION_BATCHES", "Next preparation must be a positive contiguous canonical market span");
529
+ if (!Number.isSafeInteger(count) || count <= 0 || prepared + count > planned.plan.markets.length) throw new LifecyclePlanningError("INVALID_PREPARATION_BATCHES", "Next preparation must cover a positive contiguous unfinished market span");
590
530
  command = { kind: "prepare", first: prepared, count };
591
531
  } else command = { kind: "activate" };
592
532
  candidate = buildLifecycleCommand(planned.plan, command, planned.ufg === undefined ? undefined : { account: ufgPlanPrincipal(planned.plan), config: planned.ufg.config });
593
533
  }
594
- if (reviewed !== undefined && !matchesReviewedNextTransaction(candidate, reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
595
- const simulation = await evaluateLaunchTransactions({ client, planned, limits: limitSource, fork: options.fork, signal: options.signal }, [candidate], block, { finalEnvelope: envelope, immediate: true });
534
+ if (reviewed !== undefined && !matchesReviewedNextTransaction(candidate, reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current next step");
535
+ const assessment = await assessLifecycleTransactions({ client, planned, limits: limitSource, signal: options.signal }, [candidate], block, envelope, undefined, capacity);
596
536
  assertFinalPlan(planned, options.signal);
597
- if (!simulation.admitted) throw new LifecyclePlanningError("PLAN_NOT_ADMITTED", simulation.reason ?? "Next exact launch transaction is not admitted", simulation);
598
- const next = attachEstimates([candidate], simulation)[0]!;
599
- return finalizeNextTransaction(options, next, reviewed);
537
+ if (!assessment.ready) throw new LifecyclePlanningError(assessment.code ?? "PLAN_NOT_READY", assessment.reason ?? "The next launch step could not be prepared. Refresh before continuing.", assessment);
538
+ return finalizeNextTransaction(options, attachEstimates([candidate], assessment)[0]!, reviewed);
600
539
  });
601
540
  assertInput();
602
541
  if (result !== undefined) assertReviewedTransactionUnchanged(options, reviewed, result);
603
542
  return result;
604
543
  }
605
544
 
606
- /** Re-simulates only canonical unfinished work with the same submitted receipt
607
- * evidence and confirmation depth as build-next, never silently dropping context. */
608
- export async function simulateLaunchPlan(options: SimulateLaunchPlanOptions): Promise<LifecycleSimulation> {
609
- const { client, planned } = options;
610
- if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its commitment");
611
- const current = await withLifecycleReadClient(client, (readClient) => planLaunchWithReads({
612
- client, plan: planned.plan, account: planned.account, mode: planned.mode,
613
- limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork,
614
- receipts: options.receipts, confirmations: options.confirmations ?? planned.confirmations, signal: options.signal,
615
- ufg: planned.ufg, allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
616
- }, readClient, { forceSimulation: true }));
617
- assertPlannedDeploymentBindings(planned, current);
618
- return current.simulation;
619
- }
@@ -11,6 +11,12 @@ import { HMM_FIXED_PROFILE_ID, HMM_DYNAMIC_PROFILE_ID, isHmmProfile } from "./hm
11
11
  import { ufgFixedEnvelope, ufgFixedAdapter, ufgFixedRegistration } from "./deployments/ufg-fixed-pool-20261021.js";
12
12
  import { ufgDynamicEnvelope, ufgDynamicAdapter, ufgDynamicRegistration } from "./deployments/ufg-dynamic-pool-20261021.js";
13
13
  import { UFG_FIXED_PROFILE_ID, UFG_DYNAMIC_PROFILE_ID } from "./ufg-config.js";
14
+ import { autoCompoundStaticEnvelope, autoCompoundStaticAdapter, autoCompoundStaticRegistration, autoCompoundStaticCreationCode } from "./deployments/auto-compound-static-pool-v1-20261028.js";
15
+ import { autoCompoundDynamicEnvelope, autoCompoundDynamicAdapter, autoCompoundDynamicRegistration, autoCompoundDynamicCreationCode } from "./deployments/auto-compound-dynamic-pool-v1-20261028.js";
16
+ import { AUTO_COMPOUND_STATIC_V1_PROFILE_ID, AUTO_COMPOUND_DYNAMIC_V1_PROFILE_ID } from "./auto-compound.js";
17
+ import { raetoFixedEnvelope, raetoFixedAdapter, raetoFixedRegistration } from "./deployments/raeto-fixed-pool-v1-20261029.js";
18
+ import { raetoDynamicEnvelope, raetoDynamicAdapter, raetoDynamicRegistration } from "./deployments/raeto-dynamic-pool-v1-20261029.js";
19
+ import { RAETO_FIXED_PROFILE_ID, RAETO_DYNAMIC_PROFILE_ID } from "./raeto.js";
14
20
 
15
21
  export enum KnownLifecycleProfile {
16
22
  Abyss0 = "abyss-0",
@@ -23,6 +29,10 @@ export enum KnownLifecycleProfile {
23
29
  V4HmmDynamicPool = "v4-hmm-dynamic-pool",
24
30
  V4UfgFixedPool = "v4-ufg-fixed-pool",
25
31
  V4UfgDynamicPool = "v4-ufg-dynamic-pool",
32
+ V4AutoCompoundStaticPool = "v4-auto-compound-static-pool",
33
+ V4AutoCompoundDynamicPool = "v4-auto-compound-dynamic-pool",
34
+ V4RaetoFixedPool = "v4-raeto-fixed-pool",
35
+ V4RaetoDynamicPool = "v4-raeto-dynamic-pool",
26
36
  }
27
37
 
28
38
  export enum KnownLifecycleHook {
@@ -30,6 +40,8 @@ export enum KnownLifecycleHook {
30
40
  DynamicFeePoolV1 = "dynamic-fee-pool-v1",
31
41
  HmmFixedPoolV2 = "hmm-fixed-pool-v2",
32
42
  HmmDynamicPoolV2 = "hmm-dynamic-pool-v2",
43
+ AutoCompoundStaticV1 = "auto-compound-static-v1",
44
+ AutoCompoundDynamicV1 = "auto-compound-dynamic-v1",
33
45
  }
34
46
 
35
47
  Object.freeze(KnownLifecycleProfile);
@@ -351,6 +363,10 @@ const knownDeployment: LifecycleDeploymentPreset = Object.freeze({
351
363
  poolBoundPreset(KnownLifecycleProfile.V4HmmDynamicPool, HMM_DYNAMIC_PROFILE_ID, hmmDynamicEnvelope, hmmDynamicAdapter, hmmDynamicRegistration, hmmDynamicCreationCode),
352
364
  poolBoundPreset(KnownLifecycleProfile.V4UfgFixedPool, UFG_FIXED_PROFILE_ID, ufgFixedEnvelope, ufgFixedAdapter, ufgFixedRegistration, fixedFeePoolHookV1CreationCode),
353
365
  poolBoundPreset(KnownLifecycleProfile.V4UfgDynamicPool, UFG_DYNAMIC_PROFILE_ID, ufgDynamicEnvelope, ufgDynamicAdapter, ufgDynamicRegistration, dynamicFeePoolHookV1CreationCode),
366
+ poolBoundPreset(KnownLifecycleProfile.V4AutoCompoundStaticPool, AUTO_COMPOUND_STATIC_V1_PROFILE_ID, autoCompoundStaticEnvelope, autoCompoundStaticAdapter, autoCompoundStaticRegistration, autoCompoundStaticCreationCode),
367
+ poolBoundPreset(KnownLifecycleProfile.V4AutoCompoundDynamicPool, AUTO_COMPOUND_DYNAMIC_V1_PROFILE_ID, autoCompoundDynamicEnvelope, autoCompoundDynamicAdapter, autoCompoundDynamicRegistration, autoCompoundDynamicCreationCode),
368
+ poolBoundPreset(KnownLifecycleProfile.V4RaetoFixedPool, RAETO_FIXED_PROFILE_ID, raetoFixedEnvelope, raetoFixedAdapter, raetoFixedRegistration, fixedFeePoolHookV1CreationCode),
369
+ poolBoundPreset(KnownLifecycleProfile.V4RaetoDynamicPool, RAETO_DYNAMIC_PROFILE_ID, raetoDynamicEnvelope, raetoDynamicAdapter, raetoDynamicRegistration, dynamicFeePoolHookV1CreationCode),
354
370
  ]),
355
371
  provenance: Object.freeze({ sourceCommit: "e174ce695c7010f50ca9211b07b9f178c990beac", deploymentBlock: 82880546n }),
356
372
  });
@@ -380,7 +396,7 @@ export function getKnownLifecycleProfile(options: KnownLifecycleDeploymentIdenti
380
396
  return undefined;
381
397
  }
382
398
 
383
- /** Stable, frozen order: Abyss variants, fixed/dynamic V4, HMM, then UFG fixed/dynamic. */
399
+ /** Stable, frozen order: Abyss, fixed/dynamic V4, HMM, UFG, auto-compounding, then Raeto. */
384
400
  export function listKnownLifecycleProfiles(options: KnownLifecycleDeploymentIdentity): readonly LifecycleProfilePreset[] {
385
401
  return getKnownLifecycleDeployment(options)?.profiles ?? noProfiles;
386
402
  }