@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
package/src/lifecycle/planner.ts
CHANGED
|
@@ -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,
|
|
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 {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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,
|
|
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;
|
|
30
|
-
|
|
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[],
|
|
204
|
+
function attachEstimates(transactions: readonly LifecycleTransaction[], assessment: LifecyclePlanningAssessment): LifecycleTransaction[] {
|
|
168
205
|
return transactions.map((transaction, index) => {
|
|
169
206
|
if (index !== 0) return transaction;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
303
|
-
*
|
|
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
|
|
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,
|
|
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.
|
|
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
|
-
/**
|
|
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,
|
|
390
|
-
const { plan, account,
|
|
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 =
|
|
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:
|
|
426
|
-
let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress,
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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 =
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
let
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
|
468
|
-
|
|
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
|
-
|
|
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
|
|
512
|
-
*
|
|
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,
|
|
530
|
-
const limitSource = options.limits === undefined ?
|
|
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
|
-
|
|
541
|
-
|
|
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 {
|
|
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
|
|
556
|
-
const
|
|
557
|
-
if (!
|
|
558
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
582
|
-
|
|
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
|
|
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
|
|
595
|
-
const
|
|
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 (!
|
|
598
|
-
|
|
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
|
-
}
|
package/src/lifecycle/presets.ts
CHANGED
|
@@ -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
|
|
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
|
}
|