@black-market/sdk 0.14.1 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/lifecycle/authors.js +2 -2
- package/dist/lifecycle/authors.js.map +1 -1
- package/dist/lifecycle/auto-compound.d.ts +21 -0
- package/dist/lifecycle/auto-compound.d.ts.map +1 -0
- package/dist/lifecycle/auto-compound.js +34 -0
- package/dist/lifecycle/auto-compound.js.map +1 -0
- package/dist/lifecycle/calibration.d.ts +2 -4
- package/dist/lifecycle/calibration.d.ts.map +1 -1
- package/dist/lifecycle/calibration.js +1 -26
- package/dist/lifecycle/calibration.js.map +1 -1
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.d.ts +73 -0
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.d.ts.map +1 -0
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.js +75 -0
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.js.map +1 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.d.ts +73 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.d.ts.map +1 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.js +75 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.js.map +1 -0
- package/dist/lifecycle/gas-profile.d.ts +39 -0
- package/dist/lifecycle/gas-profile.d.ts.map +1 -0
- package/dist/lifecycle/gas-profile.js +193 -0
- package/dist/lifecycle/gas-profile.js.map +1 -0
- 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 +2 -0
- package/dist/lifecycle/markets.d.ts.map +1 -1
- package/dist/lifecycle/markets.js +30 -3
- package/dist/lifecycle/markets.js.map +1 -1
- package/dist/lifecycle/opening-buys.d.ts +13 -0
- package/dist/lifecycle/opening-buys.d.ts.map +1 -0
- package/dist/lifecycle/opening-buys.js +171 -0
- package/dist/lifecycle/opening-buys.js.map +1 -0
- package/dist/lifecycle/planner.d.ts +6 -9
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +175 -265
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +10 -3
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +13 -1
- package/dist/lifecycle/presets.js.map +1 -1
- package/dist/lifecycle/progress.d.ts +1 -1
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +21 -20
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.d.ts +25 -30
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +63 -81
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +2 -4
- package/dist/lifecycle/snapshot.js.map +1 -1
- package/dist/lifecycle/transactions.d.ts +29 -6
- package/dist/lifecycle/transactions.d.ts.map +1 -1
- package/dist/lifecycle/transactions.js +62 -27
- package/dist/lifecycle/transactions.js.map +1 -1
- package/dist/lifecycle/types.d.ts +55 -122
- 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 +4 -8
- package/dist/lifecycle/ufg.js.map +1 -1
- package/docs/launch-lifecycle.md +110 -78
- package/docs/lifecycle-gas-profiles.md +90 -0
- package/docs/reference.md +99 -47
- package/examples/.env.example +0 -3
- package/examples/README.md +14 -11
- package/examples/launch-example-support.ts +4 -9
- package/examples/launch-example.ts +30 -36
- package/examples/launch-lifecycle-smoke.ts +11 -21
- package/examples/launch.ts +8 -11
- package/package.json +1 -1
- package/src/lifecycle/authors.ts +2 -2
- package/src/lifecycle/auto-compound.ts +38 -0
- package/src/lifecycle/calibration.ts +3 -23
- package/src/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.ts +77 -0
- package/src/lifecycle/deployments/auto-compound-static-pool-v1-20261028.ts +77 -0
- package/src/lifecycle/gas-profile.ts +186 -0
- package/src/lifecycle/index.ts +6 -4
- package/src/lifecycle/markets.ts +25 -3
- package/src/lifecycle/opening-buys.ts +157 -0
- package/src/lifecycle/planner.ts +168 -247
- package/src/lifecycle/presets.ts +15 -1
- package/src/lifecycle/progress.ts +19 -20
- package/src/lifecycle/rpc.ts +58 -68
- package/src/lifecycle/snapshot.ts +2 -4
- package/src/lifecycle/transactions.ts +69 -27
- package/src/lifecycle/types.ts +60 -99
- package/src/lifecycle/ufg.ts +4 -8
- 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 -18
- package/dist/lifecycle/simulation.d.ts.map +0 -1
- package/dist/lifecycle/simulation.js +0 -754
- package/dist/lifecycle/simulation.js.map +0 -1
- package/src/lifecycle/fork.ts +0 -36
- package/src/lifecycle/simulation.ts +0 -689
|
@@ -5,17 +5,39 @@ import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleErc20Abi } from "./ab
|
|
|
5
5
|
import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, LIFECYCLE_MAX_ERC404_SUPPLY, parseLaunchPlan, serializeLaunchPlan } from "./schema.js";
|
|
6
6
|
import { readLaunchProgress, readLifecycleExecutionProgress, readLifecycleHeadProgress, readLifecycleProfiles, readLifecycleTokenAtBlock, readPoolBoundHookDeployment } from "./progress.js";
|
|
7
7
|
import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion } from "./markets.js";
|
|
8
|
-
import {
|
|
9
|
-
import { prepareLaunchSimulation, simulateLaunchTransactions } from "./simulation.js";
|
|
8
|
+
import { lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleStage, nitroPosterGasBound, readLifecycleChainId, readLifecycleContract, readNitroPrices, registerLifecycleFinalCheck, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
10
9
|
import { LifecyclePlanningError } from "./types.js";
|
|
11
|
-
import { deriveKnownPoolBoundHookDeployment,
|
|
10
|
+
import { deriveKnownPoolBoundHookDeployment, validateBuySlippageBps } from "./calibration.js";
|
|
12
11
|
import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
|
|
13
|
-
import {
|
|
12
|
+
import { buildLifecycleCommand, packPreparationBatches } from "./transactions.js";
|
|
14
13
|
import { assertLifecycleMarketAllocation, assertLifecycleSupplyAllocation } from "./allocations.js";
|
|
15
|
-
import { assertUfgLaunchAccount, hashUfgLaunchConfig, UFG_LAUNCH_PROXY_ADDRESS } from "./ufg-config.js";
|
|
16
|
-
import {
|
|
14
|
+
import { assertUfgLaunchAccount, hashUfgLaunchConfig, ufgPlanPrincipal, UFG_LAUNCH_PROXY_ADDRESS } from "./ufg-config.js";
|
|
15
|
+
import { buildUfgCancelTransaction, validateUfgLaunchPlan } from "./ufg.js";
|
|
17
16
|
import { readLifecyclePlanningBlock } from "./snapshot.js";
|
|
18
17
|
import { mineLifecycleHooks } from "./mining.js";
|
|
18
|
+
import { computeLifecycleOpeningBuys, hasProfiledOpeningMath } from "./opening-buys.js";
|
|
19
|
+
import { assessLifecycleTransactions, deriveLifecycleLaunchCapacity } from "./gas-profile.js";
|
|
20
|
+
const defaultPlanReadContext = {};
|
|
21
|
+
function observedFields(value) {
|
|
22
|
+
const keys = Object.keys(value);
|
|
23
|
+
const values = keys.map((key) => value[key]);
|
|
24
|
+
return () => keys.length === Object.keys(value).length && keys.every((key, index) => value[key] === values[index]);
|
|
25
|
+
}
|
|
26
|
+
async function selectLaunchMode(requested, plan, capacity, limits, client, block) {
|
|
27
|
+
if (requested !== "atomic" || plan.markets.length !== 1 || plan.funding.some((funding) => funding.kind === LifecycleFundingKind.Swap) ||
|
|
28
|
+
capacity?.atomic === undefined || capacity.bounds.atomicComputeGasUpperBound === undefined)
|
|
29
|
+
return "staged";
|
|
30
|
+
const poster = limits.protocol === "nitro" ? nitroPosterGasBound(capacity.atomic, await readNitroPrices(client, block)).posterGas : 0n;
|
|
31
|
+
return capacity.bounds.atomicComputeGasUpperBound + poster <= limits.executionGasCeiling ? "atomic" : "staged";
|
|
32
|
+
}
|
|
33
|
+
async function preparationBatches(plan, prepared, ufg, limits, client, block) {
|
|
34
|
+
if (prepared === plan.markets.length)
|
|
35
|
+
return prepared === 0 ? [] : [prepared];
|
|
36
|
+
const route = ufg === undefined ? undefined : { account: ufgPlanPrincipal(plan), config: ufg.config };
|
|
37
|
+
const sample = buildLifecycleCommand(plan, { kind: "prepare", first: prepared, count: 1 }, route);
|
|
38
|
+
const poster = limits.protocol === "nitro" ? nitroPosterGasBound(sample, await readNitroPrices(client, block)).posterGas : 0n;
|
|
39
|
+
return packPreparationBatches(plan, prepared, limits.executionGasCeiling - poster, limits.headroomBps);
|
|
40
|
+
}
|
|
19
41
|
function validatePlanShape(plan) {
|
|
20
42
|
// Encoding checks all exact integer widths, addresses, byte strings and tuple fields.
|
|
21
43
|
const commitment = hashLaunchPlan(plan);
|
|
@@ -152,28 +174,42 @@ async function readPlanMetadata(client, plan, token, block) {
|
|
|
152
174
|
}))).filter((deployment) => deployment !== undefined);
|
|
153
175
|
return { profiles, hookDeployments, reasons: live.filter((profile) => !profile.admitted).map((profile) => profile.reason ?? "Unlisted lifecycle profile is unavailable") };
|
|
154
176
|
}
|
|
155
|
-
function orderedTransactions(plan, mode, progress, approvals, batches, ufg) {
|
|
177
|
+
function orderedTransactions(plan, mode, progress, approvals, batches, ufg, capacity) {
|
|
156
178
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled)
|
|
157
179
|
return [];
|
|
158
|
-
const prepared = progress.canonical.preparedMarkets;
|
|
159
|
-
const compiled =
|
|
160
|
-
|
|
180
|
+
const prepared = progress.canonical.preparedMarkets, route = ufg === undefined ? undefined : { account: ufgPlanPrincipal(plan), config: ufg.config };
|
|
181
|
+
const compiled = [];
|
|
182
|
+
if (mode === "atomic")
|
|
183
|
+
compiled.push(capacity?.atomic ?? buildLifecycleCommand(plan, { kind: "atomic" }, route));
|
|
184
|
+
else {
|
|
185
|
+
compiled.push(buildLifecycleCommand(plan, { kind: "begin" }, route));
|
|
186
|
+
let first = 0;
|
|
187
|
+
for (const count of batches) {
|
|
188
|
+
compiled.push(buildLifecycleCommand(plan, { kind: "prepare", first, count }, route));
|
|
189
|
+
first += count;
|
|
190
|
+
}
|
|
191
|
+
compiled.push(capacity?.activation ?? buildLifecycleCommand(plan, { kind: "activate" }, route));
|
|
192
|
+
}
|
|
161
193
|
const commands = compiled.filter((transaction) => (transaction.kind !== "begin" && transaction.kind !== "atomic" || progress.canonical.phase === LifecyclePhase.None) &&
|
|
162
194
|
(transaction.kind !== "prepare" || (transaction.marketStart ?? 0) >= prepared));
|
|
163
195
|
const transactions = progress.canonical.phase === LifecyclePhase.None ? [...approvals, ...commands] : commands;
|
|
164
|
-
return transactions.map((transaction, index) => ({ ...transaction,
|
|
196
|
+
return transactions.map((transaction, index) => ({ ...transaction, calldataBytes: (transaction.data.length - 2) / 2, dependencies: index === 0 ? [] : [transactions[index - 1].id] }));
|
|
165
197
|
}
|
|
166
|
-
function attachEstimates(transactions,
|
|
167
|
-
return transactions.map((transaction) => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
198
|
+
function attachEstimates(transactions, assessment) {
|
|
199
|
+
return transactions.map((transaction, index) => {
|
|
200
|
+
if (index !== 0)
|
|
201
|
+
return transaction;
|
|
202
|
+
if (transaction.kind === "approve" || transaction.kind === "approve-reset")
|
|
203
|
+
return { ...transaction, assessment };
|
|
204
|
+
const estimate = assessment.estimate;
|
|
205
|
+
return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate, assessment };
|
|
171
206
|
});
|
|
172
207
|
}
|
|
173
208
|
const reviewedTransactionKinds = ["approve-reset", "approve", "atomic", "begin", "prepare", "activate", "cancel"];
|
|
174
209
|
function readReviewedNextTransaction(transaction) {
|
|
175
210
|
if (transaction === undefined)
|
|
176
211
|
return undefined;
|
|
212
|
+
const walletApproval = transaction?.kind === "approve" || transaction?.kind === "approve-reset";
|
|
177
213
|
if (transaction === null || typeof transaction !== "object" || typeof transaction.id !== "string" ||
|
|
178
214
|
!reviewedTransactionKinds.includes(transaction.kind) ||
|
|
179
215
|
!Number.isSafeInteger(transaction.chainId) || transaction.chainId <= 0 ||
|
|
@@ -181,9 +217,8 @@ function readReviewedNextTransaction(transaction) {
|
|
|
181
217
|
typeof transaction.to !== "string" || !/^0x[\da-fA-F]{40}$/.test(transaction.to) ||
|
|
182
218
|
typeof transaction.data !== "string" || !/^0x(?:[\da-fA-F]{2})*$/.test(transaction.data) ||
|
|
183
219
|
typeof transaction.value !== "bigint" || transaction.value < 0n || transaction.value >= 1n << 256n ||
|
|
184
|
-
typeof transaction.gas !== "bigint" || transaction.gas <= 0n || transaction.gas >= 1n << 64n ||
|
|
185
|
-
|
|
186
|
-
transaction.nonce !== undefined && (typeof transaction.nonce !== "bigint" || transaction.nonce < 0n || transaction.nonce >= 1n << 64n)) {
|
|
220
|
+
!walletApproval && (typeof transaction.gas !== "bigint" || transaction.gas <= 0n || transaction.gas >= 1n << 64n ||
|
|
221
|
+
typeof transaction.gasPrice !== "bigint" || transaction.gasPrice < 0n || transaction.gasPrice >= 1n << 256n)) {
|
|
187
222
|
throw new LifecyclePlanningError("INVALID_REVIEWED_TRANSACTION", "Reviewed transaction must contain a well-formed exact chain/account/target/calldata/value and uint64 gas/uint256 price envelope");
|
|
188
223
|
}
|
|
189
224
|
return transaction;
|
|
@@ -191,23 +226,22 @@ function readReviewedNextTransaction(transaction) {
|
|
|
191
226
|
function matchesReviewedNextTransaction(next, reviewed) {
|
|
192
227
|
return next !== undefined && next.id === reviewed.id && next.kind === reviewed.kind && next.chainId === reviewed.chainId &&
|
|
193
228
|
next.from.toLowerCase() === reviewed.from.toLowerCase() && next.to.toLowerCase() === reviewed.to.toLowerCase() &&
|
|
194
|
-
next.data.toLowerCase() === reviewed.data.toLowerCase() && next.value === reviewed.value
|
|
195
|
-
(reviewed.nonce === undefined || next.nonce === reviewed.nonce);
|
|
229
|
+
next.data.toLowerCase() === reviewed.data.toLowerCase() && next.value === reviewed.value;
|
|
196
230
|
}
|
|
197
231
|
function assertReviewedTransactionUnchanged(options, reviewed, next) {
|
|
198
232
|
if (options.planned.ufg !== undefined)
|
|
199
233
|
assertUfgLaunchAccount(options.planned.plan, options.planned.account);
|
|
200
234
|
if (options.planned.account.toLowerCase() !== next.from.toLowerCase() || options.planned.ufg === undefined && options.planned.plan.creator.toLowerCase() !== next.from.toLowerCase()) {
|
|
201
|
-
throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account changed
|
|
235
|
+
throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account changed while preparing the reviewed transaction");
|
|
202
236
|
}
|
|
203
237
|
if (options.planned.chainId !== BigInt(next.chainId) || options.planned.plan.chainId !== BigInt(next.chainId)) {
|
|
204
|
-
throw new LifecyclePlanningError("CHAIN_MISMATCH", "Execution chain changed
|
|
238
|
+
throw new LifecyclePlanningError("CHAIN_MISMATCH", "Execution chain changed while preparing the reviewed transaction");
|
|
205
239
|
}
|
|
206
240
|
const current = readReviewedNextTransaction(options.reviewedTransaction);
|
|
207
|
-
if (reviewed === undefined ? current !== undefined : current === undefined || !matchesReviewedNextTransaction(current, reviewed) ||
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_CHANGED", "Reviewed transaction changed
|
|
241
|
+
if (reviewed === undefined ? current !== undefined : current === undefined || !matchesReviewedNextTransaction(current, reviewed) || !matchesReviewedNextTransaction(next, reviewed) ||
|
|
242
|
+
reviewed.kind !== "approve" && reviewed.kind !== "approve-reset" &&
|
|
243
|
+
(current.gas !== reviewed.gas || current.gasPrice !== reviewed.gasPrice || next.gas !== reviewed.gas || next.gasPrice !== reviewed.gasPrice)) {
|
|
244
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_CHANGED", "Reviewed transaction changed while preparing it");
|
|
211
245
|
}
|
|
212
246
|
}
|
|
213
247
|
async function readTokenFactoryBinding(client, orchestrator, block) {
|
|
@@ -221,21 +255,6 @@ async function readTokenFactoryBinding(client, orchestrator, block) {
|
|
|
221
255
|
throw new LifecyclePlanningError("TOKEN_FACTORY_BINDING", "Token factory is not live and bound to the exact lifecycle core");
|
|
222
256
|
return { tokenFactory, tokenFactoryCodeHash: keccak256(code) };
|
|
223
257
|
}
|
|
224
|
-
function assertPlannedDeploymentBindings(stored, current) {
|
|
225
|
-
if (hashLaunchPlan(stored.plan).toLowerCase() !== stored.planHash.toLowerCase() || current.planHash.toLowerCase() !== stored.planHash.toLowerCase() || current.launchId.toLowerCase() !== stored.launchId.toLowerCase())
|
|
226
|
-
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its finalized commitment");
|
|
227
|
-
if (stored.ufg === undefined ? current.ufg !== undefined : current.ufg === undefined || hashUfgLaunchConfig(stored.ufg.config) !== stored.ufg.configHash || current.ufg.configHash !== stored.ufg.configHash)
|
|
228
|
-
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored UFG route or treasury config no longer matches its finalized commitment");
|
|
229
|
-
if (current.predictedToken.toLowerCase() !== stored.predictedToken.toLowerCase() || current.tokenFactory.toLowerCase() !== stored.tokenFactory.toLowerCase() || current.tokenFactoryCodeHash.toLowerCase() !== stored.tokenFactoryCodeHash.toLowerCase())
|
|
230
|
-
throw new LifecyclePlanningError("TOKEN_FACTORY_BINDING", "Final token prediction or its immutable factory code binding changed before execution");
|
|
231
|
-
if (!current.simulation.admitted || current.progress.canonical.phase === LifecyclePhase.Active || current.progress.canonical.phase === LifecyclePhase.Cancelled)
|
|
232
|
-
return;
|
|
233
|
-
if (stored.hookDeployments.length !== current.hookDeployments.length || stored.hookDeployments.some((deployment, index) => {
|
|
234
|
-
const fresh = current.hookDeployments[index];
|
|
235
|
-
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();
|
|
236
|
-
}))
|
|
237
|
-
throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Final bound-hook deployer, initcode, salt or prediction changed before wallet execution");
|
|
238
|
-
}
|
|
239
258
|
function assertPlanningActive(signal) {
|
|
240
259
|
if (signal?.aborted)
|
|
241
260
|
throw Object.assign(new Error("Lifecycle launch planning cancelled"), { name: "AbortError" });
|
|
@@ -244,7 +263,7 @@ function validatePlanningIntent(options) {
|
|
|
244
263
|
const { plan, account, mode } = options;
|
|
245
264
|
assertPlanningActive(options.signal);
|
|
246
265
|
if (mode !== "atomic" && mode !== "staged")
|
|
247
|
-
throw new LifecyclePlanningError("EXPLICIT_MODE_REQUIRED", "Choose atomic or
|
|
266
|
+
throw new LifecyclePlanningError("EXPLICIT_MODE_REQUIRED", "Choose an atomic preference or explicit staged mode");
|
|
248
267
|
if (options.ufg !== undefined)
|
|
249
268
|
assertUfgLaunchAccount(plan, account);
|
|
250
269
|
else if (account.toLowerCase() !== plan.creator.toLowerCase())
|
|
@@ -295,21 +314,15 @@ function assertFinalPlan(planned, signal) {
|
|
|
295
314
|
if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash)
|
|
296
315
|
throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed during asynchronous planning");
|
|
297
316
|
}
|
|
298
|
-
/** One owner
|
|
299
|
-
*
|
|
317
|
+
/** One owner mines constructor-bound salts and protects ordered buys with exact
|
|
318
|
+
* local curve math. Launch calldata is never executed during planning. */
|
|
300
319
|
export async function prepareAndPlanLifecycleLaunch(options) {
|
|
301
320
|
const { commitment, ufgConfigHash } = validatePlanningIntent(options);
|
|
302
321
|
if (options.buySlippageBps !== undefined)
|
|
303
322
|
validateBuySlippageBps(options.buySlippageBps);
|
|
304
|
-
const fixed = options.fixedFinalEnvelope;
|
|
305
|
-
if (fixed !== undefined && (fixed === null || typeof fixed !== "object" || options.mode !== "atomic" || typeof fixed.gas !== "bigint" || fixed.gas <= 0n || fixed.gas >= 1n << 64n ||
|
|
306
|
-
fixed.gasPrice !== undefined && (typeof fixed.gasPrice !== "bigint" || fixed.gasPrice < 0n || fixed.gasPrice >= 1n << 256n))) {
|
|
307
|
-
throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Fixed final envelope requires atomic mode, exact positive uint64 gas and optional uint256 price");
|
|
308
|
-
}
|
|
309
|
-
const heldEnvelope = fixed === undefined ? undefined : Object.freeze({ ...fixed });
|
|
310
323
|
const needsMining = options.plan.markets.some((market) => isPoolBoundV4ConfigVersion(market.configVersion));
|
|
311
324
|
const needsProtection = options.buySlippageBps !== undefined && options.plan.buys.length !== 0;
|
|
312
|
-
if (!needsMining && !needsProtection
|
|
325
|
+
if (!needsMining && !needsProtection)
|
|
313
326
|
return planLaunch(options);
|
|
314
327
|
const draft = parseLaunchPlan(serializeLaunchPlan(options.plan));
|
|
315
328
|
const assertDraft = () => {
|
|
@@ -318,9 +331,6 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
318
331
|
throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed during asynchronous preparation; previous construction is invalid");
|
|
319
332
|
if ((options.ufg === undefined ? undefined : hashUfgLaunchConfig(options.ufg.config)) !== ufgConfigHash)
|
|
320
333
|
throw new LifecyclePlanningError("PLAN_MUTATED", "UFG config changed during asynchronous preparation");
|
|
321
|
-
if (fixed === undefined ? options.fixedFinalEnvelope !== undefined : options.fixedFinalEnvelope !== fixed ||
|
|
322
|
-
fixed.gas !== heldEnvelope.gas || fixed.gasPrice !== heldEnvelope.gasPrice)
|
|
323
|
-
throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Fixed final envelope changed during preparation");
|
|
324
334
|
};
|
|
325
335
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
326
336
|
const block = await readLifecyclePlanningBlock(client, draft, options.account, options.ufg !== undefined, true);
|
|
@@ -344,32 +354,25 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
344
354
|
markets[marketIndex] = { ...market, config: encodePoolBoundV4LifecycleMarketConfig({ ...decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion), hookSalt: mined.salt }) };
|
|
345
355
|
}
|
|
346
356
|
assertDraft();
|
|
347
|
-
const executionBlock = block;
|
|
348
|
-
assertDraft();
|
|
349
|
-
const price = heldEnvelope === undefined ? undefined : heldEnvelope.gasPrice ?? rpcQuantity(await readLifecycleGasPrice(client), "gas price");
|
|
350
|
-
const baseFeeEnvelope = (executionBlock.baseFeePerGas ?? 0n) * 2n;
|
|
351
|
-
const finalEnvelope = heldEnvelope === undefined ? undefined : Object.freeze({
|
|
352
|
-
gas: heldEnvelope.gas,
|
|
353
|
-
gasPrice: heldEnvelope.gasPrice ?? (price > baseFeeEnvelope ? price : baseFeeEnvelope),
|
|
354
|
-
});
|
|
355
357
|
let plan = { ...draft, markets };
|
|
356
|
-
let
|
|
357
|
-
let sizing;
|
|
358
|
+
let openingBuys;
|
|
358
359
|
if (needsProtection) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
assertDraft();
|
|
364
|
-
if (diagnostic.progress.canonical.phase !== LifecyclePhase.None || diagnostic.progress.head.phase !== LifecyclePhase.None)
|
|
360
|
+
if (!hasProfiledOpeningMath(plan))
|
|
361
|
+
throw new LifecyclePlanningError("OPENING_BUY_NOT_PROFILED", "Exact opening-buy math is unavailable for this profile. Use a supported profile or update the SDK.");
|
|
362
|
+
const head = await readLifecycleHeadProgress(client, plan, options.account, block);
|
|
363
|
+
if (head.phase !== LifecyclePhase.None)
|
|
365
364
|
throw new LifecyclePlanningError("LAUNCH_ALREADY_STARTED", "Buy protection cannot replace an already committed launch plan");
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
365
|
+
const predictedToken = await readLifecycleTokenAtBlock(client, plan, block);
|
|
366
|
+
openingBuys = computeLifecycleOpeningBuys({ plan, predictedToken });
|
|
367
|
+
const factor = BigInt(10000 - options.buySlippageBps);
|
|
368
|
+
plan = { ...plan, buys: plan.buys.map((buy, index) => {
|
|
369
|
+
const minTokenOut = openingBuys[index].tokenOut * factor / 10000n;
|
|
370
|
+
if (minTokenOut <= 0n)
|
|
371
|
+
throw new LifecyclePlanningError("INVALID_BUY_OUTPUT", "Each opening buy must produce a positive protected minimum");
|
|
372
|
+
return { ...buy, minTokenOut };
|
|
373
|
+
}) };
|
|
371
374
|
}
|
|
372
|
-
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client,
|
|
375
|
+
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, { block, openingBuys }));
|
|
373
376
|
assertDraft();
|
|
374
377
|
assertFinalPlan(planned, options.signal);
|
|
375
378
|
if (planned.progress.canonical.phase !== LifecyclePhase.Active && planned.progress.canonical.phase !== LifecyclePhase.Cancelled) {
|
|
@@ -379,15 +382,15 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
379
382
|
throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Final exact constructor binding differs from the one locally mined candidate");
|
|
380
383
|
}
|
|
381
384
|
}
|
|
382
|
-
if (needsProtection && !planned.
|
|
383
|
-
throw new LifecyclePlanningError("
|
|
385
|
+
if (needsProtection && !planned.assessment.ready)
|
|
386
|
+
throw new LifecyclePlanningError(planned.assessment.code ?? "PLAN_NOT_READY", planned.assessment.reason ?? "The launch could not be prepared. Refresh the review before continuing.", planned.assessment);
|
|
384
387
|
return planned;
|
|
385
388
|
});
|
|
386
389
|
assertDraft();
|
|
387
390
|
assertFinalPlan(result, options.signal);
|
|
388
391
|
return result;
|
|
389
392
|
}
|
|
390
|
-
/**
|
|
393
|
+
/** Fresh pinned input and deterministic gas-budget checks. No simulation or broadcasts. */
|
|
391
394
|
export async function planLaunch(options) {
|
|
392
395
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
393
396
|
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads(options, client));
|
|
@@ -397,8 +400,9 @@ export async function planLaunch(options) {
|
|
|
397
400
|
assertFinalPlan(result, options.signal);
|
|
398
401
|
return result;
|
|
399
402
|
}
|
|
400
|
-
async function planLaunchWithReads(options, client,
|
|
401
|
-
const {
|
|
403
|
+
async function planLaunchWithReads(options, client, readContext = defaultPlanReadContext) {
|
|
404
|
+
const { block: pinnedBlock, openingBuys } = readContext;
|
|
405
|
+
const { plan, account, ufg } = options;
|
|
402
406
|
const { commitment: planHash, marketConfigs, ufgConfigHash } = validatePlanningIntent(options);
|
|
403
407
|
const launchId = hashLaunchIdentity(plan);
|
|
404
408
|
const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
|
|
@@ -406,39 +410,27 @@ async function planLaunchWithReads(options, client, pinnedBlock, ownedProgress,
|
|
|
406
410
|
]));
|
|
407
411
|
if (chainId !== plan.chainId)
|
|
408
412
|
throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed plan");
|
|
409
|
-
// Only preparation/build-next can supply freshly observed progress from this
|
|
410
|
-
// still-open SDK invocation. Stored/query DTOs never enter this continuation.
|
|
411
|
-
if (ownedProgress !== undefined) {
|
|
412
|
-
if (!assertLifecycleReadClientOpen(client) || pinnedBlock !== block || ownedProgress.blockNumber !== block.number ||
|
|
413
|
-
ownedProgress.blockHash.toLowerCase() !== block.hash.toLowerCase() || ownedProgress.chainId !== plan.chainId ||
|
|
414
|
-
ownedProgress.confirmationDepth !== (options.confirmations ?? 1))
|
|
415
|
-
throw new LifecyclePlanningError("INVALID_READ_CONTEXT", "Owned progress differs from its active planning snapshot");
|
|
416
|
-
if (ownedProgress.planHash.toLowerCase() !== planHash.toLowerCase() || ownedProgress.launchId.toLowerCase() !== launchId.toLowerCase())
|
|
417
|
-
throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed after its canonical progress was read");
|
|
418
|
-
}
|
|
419
413
|
assertPlanningActive(options.signal);
|
|
420
414
|
const preset = getKnownLifecycleDeployment({ chainId: plan.chainId, orchestrator: plan.orchestrator });
|
|
421
415
|
const [{ tokenFactory, tokenFactoryCodeHash }, { limits }, { identity, progress }] = await lifecycleStage(client, "plan.domain", () => Promise.all([
|
|
422
416
|
preset ?? readTokenFactoryBinding(client, plan.orchestrator, block),
|
|
423
417
|
resolveLifecycleLimits({ client, account, orchestrator: plan.orchestrator, block, chainId: plan.chainId }, options.limits),
|
|
424
418
|
lifecycleStage(client, "plan.progress", async () => {
|
|
425
|
-
const [predictedToken] = await Promise.all([
|
|
426
|
-
readLifecycleTokenAtBlock(client, plan, block),
|
|
427
|
-
ownedProgress === undefined ? readLifecycleHeadProgress(client, plan, account, block) : undefined,
|
|
419
|
+
const [predictedToken, head] = await Promise.all([
|
|
420
|
+
readLifecycleTokenAtBlock(client, plan, block), readLifecycleHeadProgress(client, plan, account, block),
|
|
428
421
|
]);
|
|
422
|
+
const mode = head.phase === LifecyclePhase.None ? options.mode : head.mode === 1 ? "staged" : "atomic";
|
|
429
423
|
const identity = { plan, planHash, launchId, predictedToken, account, mode, confirmations: options.confirmations ?? 1,
|
|
430
424
|
...(ufg === undefined ? {} : { ufg: { config: ufg.config, configHash: ufgConfigHash } }) };
|
|
431
425
|
if (ufg !== undefined)
|
|
432
426
|
validateUfgLaunchPlan(plan, ufg.config, predictedToken);
|
|
433
|
-
const progress =
|
|
434
|
-
if (ownedProgress !== undefined && progress.token.toLowerCase() !== predictedToken.toLowerCase())
|
|
435
|
-
throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed after its canonical progress was read");
|
|
427
|
+
const progress = await readLaunchProgress({ client, planned: identity, receipts: options.receipts, signal: options.signal }, block);
|
|
436
428
|
return { identity, progress };
|
|
437
429
|
}),
|
|
438
430
|
]));
|
|
439
431
|
assertPlanningActive(options.signal);
|
|
440
|
-
const placeholder = {
|
|
441
|
-
let planned = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress,
|
|
432
|
+
const placeholder = { ready: false, blockNumber: block.number, blockHash: block.hash, account, chainId: plan.chainId, limits };
|
|
433
|
+
let planned = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, assessment: placeholder, limits: options.limits,
|
|
442
434
|
allowTopologyGaps: options.allowTopologyGaps === true, allowMissingPermanentTail: options.allowMissingPermanentTail === true };
|
|
443
435
|
const receiptsPending = progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed");
|
|
444
436
|
if (!progress.confirmationSafe || receiptsPending) {
|
|
@@ -450,14 +442,13 @@ async function planLaunchWithReads(options, client, pinnedBlock, ownedProgress,
|
|
|
450
442
|
const known = deriveKnownPoolBoundHookDeployment({ plan, token: identity.predictedToken, marketIndex });
|
|
451
443
|
hookDeployments.push({ marketIndex, ...(known?.deployment ?? await readPoolBoundHookDeployment({ client, plan, marketIndex }, block)) });
|
|
452
444
|
}
|
|
453
|
-
|
|
454
|
-
|
|
445
|
+
return { ...planned, hookDeployments, assessment: { ...placeholder,
|
|
446
|
+
code: !progress.confirmationSafe ? "UNCONFIRMED_STATE" : "RECEIPT_PENDING",
|
|
447
|
+
reason: !progress.confirmationSafe ? "Wait for the selected launch confirmation depth before continuing." : "Wait for the pending transaction to confirm or resolve its replacement." } };
|
|
455
448
|
}
|
|
456
449
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) {
|
|
457
|
-
return { ...planned,
|
|
450
|
+
return { ...planned, assessment: { ...placeholder, ready: true } };
|
|
458
451
|
}
|
|
459
|
-
// Topology is a creation policy, not a retroactive obstacle to recovery of
|
|
460
|
-
// immutable onchain commitments. Waiting and terminal launches returned above.
|
|
461
452
|
if (progress.canonical.phase === LifecyclePhase.None && progress.head.phase === LifecyclePhase.None)
|
|
462
453
|
validatePlanTopology(options, marketConfigs, identity.predictedToken);
|
|
463
454
|
const [funding, metadata] = await lifecycleStage(client, "plan.inputs", () => Promise.all([
|
|
@@ -465,199 +456,118 @@ async function planLaunchWithReads(options, client, pinnedBlock, ownedProgress,
|
|
|
465
456
|
readPlanMetadata(client, plan, identity.predictedToken, block),
|
|
466
457
|
]));
|
|
467
458
|
assertFinalPlan(planned, options.signal);
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Fixed final envelope requires one immediate atomic launch with no preceding approvals");
|
|
477
|
-
}
|
|
478
|
-
if (metadata.reasons.length > 0) {
|
|
479
|
-
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) };
|
|
480
|
-
}
|
|
481
|
-
const envelope = diagnostic ? undefined : fixedFinalEnvelope;
|
|
482
|
-
const preparedSimulation = prepareLaunchSimulation({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, block, true, envelope, fixedFinalEnvelope?.gasPrice, sizing);
|
|
483
|
-
try {
|
|
484
|
-
const execute = diagnostic ? preparedSimulation.measure : (candidate) => preparedSimulation(candidate, envelope);
|
|
485
|
-
let simulation = await execute(transactions);
|
|
486
|
-
assertFinalPlan(planned, options.signal);
|
|
487
|
-
if (mode === "staged") {
|
|
488
|
-
// Only pressure at the native ceiling can invalidate packing. A smaller
|
|
489
|
-
// affordability/RPC/account envelope is not evidence for another wallet write.
|
|
490
|
-
while (!simulation.admitted && simulation.failedTransactionId?.startsWith("prepare:")) {
|
|
491
|
-
const failedIndex = transactions.findIndex((transaction) => transaction.id === simulation.failedTransactionId);
|
|
492
|
-
const failedTransaction = transactions[failedIndex];
|
|
493
|
-
const step = simulation.steps.find((candidate) => candidate.transactionId === simulation.failedTransactionId);
|
|
494
|
-
const count = failedTransaction?.marketCount ?? 0;
|
|
495
|
-
if (count <= 1 || step?.gasUsed === undefined || step.gasLimit === undefined || step.gasLimit < limits.executionGasCeiling || step.gasUsed * 100n < limits.executionGasCeiling * 95n)
|
|
496
|
-
break;
|
|
497
|
-
let first = 0;
|
|
498
|
-
const batchIndex = batches.findIndex((batch) => { const matches = first === failedTransaction?.marketStart; first += batch; return matches; });
|
|
499
|
-
const left = Math.ceil(count / 2);
|
|
500
|
-
batches.splice(batchIndex, 1, left, count - left);
|
|
501
|
-
transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg);
|
|
502
|
-
simulation = await execute(transactions);
|
|
503
|
-
assertFinalPlan(planned, options.signal);
|
|
504
|
-
}
|
|
505
|
-
if (!simulation.admitted && simulation.failedTransactionId === "activate")
|
|
506
|
-
simulation = { ...simulation, reason: `${simulation.reason ?? "Final activation failed"}; final mint/lock/all-buys/public-opening is indivisible and will not be partitioned` };
|
|
507
|
-
}
|
|
508
|
-
else if (!diagnostic && !simulation.admitted && !(simulation.capacityConstraint === "compute" && simulation.confidence === "provisional")) {
|
|
509
|
-
simulation = { ...simulation, reason: `${simulation.reason ?? "Exact atomic plan is not admitted"}; choose staged explicitly only if empty preparation can be partitioned and complete activation fits` };
|
|
510
|
-
}
|
|
511
|
-
return { ...planned, simulation, transactions: attachEstimates(transactions, simulation), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
|
|
512
|
-
}
|
|
513
|
-
finally {
|
|
514
|
-
preparedSimulation.close();
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
async function preflightNextTransaction(options, next, simulation, reviewed) {
|
|
518
|
-
if (next.gas === undefined || next.gas <= 0n || next.gas >= 1n << 64n || next.gasPrice === undefined || next.gasPrice < 0n ||
|
|
519
|
-
simulation.executionProof !== "proved" || simulation.protocolFit !== "proved" || !simulation.admitted ||
|
|
520
|
-
next.estimate === undefined || next.estimate.gasLimit !== next.gas || next.estimate.gasPrice !== next.gasPrice ||
|
|
521
|
-
simulation.limits.transactionGasCeiling !== undefined && next.gas > simulation.limits.transactionGasCeiling) {
|
|
522
|
-
throw new LifecyclePlanningError("MISSING_GAS_PROOF", "Next transaction has no current-state exact gas/headroom/fee-envelope proof", simulation);
|
|
523
|
-
}
|
|
524
|
-
assertReviewedTransactionUnchanged(options, reviewed, next);
|
|
525
|
-
const submissionClient = options.submissionClient;
|
|
526
|
-
assertPlanningActive(options.signal);
|
|
527
|
-
if (submissionClient === undefined)
|
|
528
|
-
return { ...next, dependencies: [] };
|
|
459
|
+
const capacity = deriveLifecycleLaunchCapacity({ plan, predictedToken: identity.predictedToken, ufg, headroomBps: limits.headroomBps }, openingBuys);
|
|
460
|
+
const mode = progress.canonical.phase === LifecyclePhase.None
|
|
461
|
+
? await selectLaunchMode(options.mode, plan, capacity, limits, client, block) : identity.mode;
|
|
462
|
+
planned = { ...planned, mode, prerequisites: funding.prerequisites, profiles: metadata.profiles, hookDeployments: metadata.hookDeployments };
|
|
463
|
+
if (metadata.reasons.length > 0)
|
|
464
|
+
return { ...planned, assessment: { ...placeholder, code: "PROFILE_UNAVAILABLE", failureCategory: "semantic",
|
|
465
|
+
reason: "The selected launch profile could not be read or is unavailable. Refresh the review before continuing." } };
|
|
466
|
+
let batches;
|
|
529
467
|
try {
|
|
530
|
-
|
|
531
|
-
throw new Error("Active submission RPC chain differs from the reviewed transaction");
|
|
532
|
-
const estimated = rpcQuantity(await lifecycleRpc(submissionClient, "eth_estimateGas", [{
|
|
533
|
-
from: next.from, to: next.to, data: next.data, value: toHex(next.value), gas: toHex(next.gas), gasPrice: toHex(next.gasPrice),
|
|
534
|
-
...(next.nonce === undefined ? {} : { nonce: toHex(next.nonce) }),
|
|
535
|
-
}, "latest"]), "submission gas estimate");
|
|
536
|
-
if (estimated <= 0n || estimated > next.gas)
|
|
537
|
-
throw new Error("Submission RPC gas estimate exceeds the exact reviewed envelope or is zero");
|
|
538
|
-
assertPlanningActive(options.signal);
|
|
468
|
+
batches = mode === "staged" ? await preparationBatches(plan, progress.canonical.preparedMarkets, ufg, limits, client, block) : [];
|
|
539
469
|
}
|
|
540
470
|
catch (failure) {
|
|
541
|
-
if (failure instanceof
|
|
471
|
+
if (!(failure instanceof LifecyclePlanningError) || failure.code !== "PREPARATION_GAS_LIMIT")
|
|
542
472
|
throw failure;
|
|
543
|
-
|
|
544
|
-
throw new LifecyclePlanningError("SUBMISSION_PREFLIGHT_FAILED", reason, { ...simulation, transportPreflight: "failed", reason });
|
|
473
|
+
return { ...planned, assessment: { ...placeholder, code: failure.code, reason: failure.message, failureCategory: "capacity", capacityConstraint: "preparation" } };
|
|
545
474
|
}
|
|
546
|
-
|
|
547
|
-
|
|
475
|
+
const transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
|
|
476
|
+
const assessment = await assessLifecycleTransactions({ client, planned, limits: options.limits, signal: options.signal }, transactions, block, undefined, openingBuys, capacity);
|
|
477
|
+
assertFinalPlan(planned, options.signal);
|
|
478
|
+
return { ...planned, assessment, transactions: attachEstimates(transactions, assessment), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
|
|
479
|
+
}
|
|
480
|
+
function finalizeNextTransaction(options, next, reviewed) {
|
|
548
481
|
assertPlanningActive(options.signal);
|
|
549
482
|
assertReviewedTransactionUnchanged(options, reviewed, next);
|
|
550
|
-
return { ...next, dependencies: []
|
|
483
|
+
return { ...next, dependencies: [] };
|
|
551
484
|
}
|
|
552
|
-
/** Recovery observes
|
|
553
|
-
*
|
|
485
|
+
/** Recovery observes actual canonical receipts, then constructs and budgets only
|
|
486
|
+
* the next command. No launch transaction is executed during planning. */
|
|
554
487
|
export async function buildNextTransaction(options) {
|
|
488
|
+
const original = options.planned, matches = observedFields(original);
|
|
489
|
+
const assertInput = () => {
|
|
490
|
+
if (options.planned !== original || !matches())
|
|
491
|
+
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored launch context changed while constructing its next transaction");
|
|
492
|
+
assertFinalPlan(original, options.signal);
|
|
493
|
+
};
|
|
555
494
|
const supplied = readReviewedNextTransaction(options.reviewedTransaction);
|
|
556
|
-
|
|
557
|
-
// reused, and final checks compare the mutable input with this one local copy.
|
|
495
|
+
const walletApproval = supplied?.kind === "approve" || supplied?.kind === "approve-reset";
|
|
558
496
|
const reviewed = supplied === undefined ? undefined : Object.freeze({ id: supplied.id, kind: supplied.kind, chainId: supplied.chainId,
|
|
559
|
-
from: supplied.from, to: supplied.to, data: supplied.data, value: supplied.value, gas: supplied.gas, gasPrice: supplied.gasPrice
|
|
497
|
+
from: supplied.from, to: supplied.to, data: supplied.data, value: supplied.value, ...(walletApproval ? {} : { gas: supplied.gas, gasPrice: supplied.gasPrice }) });
|
|
498
|
+
const envelope = reviewed === undefined || walletApproval ? undefined : reviewed;
|
|
560
499
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
561
|
-
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
await Promise.all([
|
|
565
|
-
readLifecycleTokenAtBlock(client, options.planned.plan, block),
|
|
566
|
-
readLifecycleHeadProgress(client, options.planned.plan, options.planned.account, block),
|
|
567
|
-
resolveLifecycleLimits({ client, account: options.planned.account, orchestrator: options.planned.plan.orchestrator, block, chainId: options.planned.chainId }, limitSource),
|
|
568
|
-
]);
|
|
569
|
-
}
|
|
500
|
+
registerLifecycleFinalCheck(client, assertInput);
|
|
501
|
+
const block = await readLifecyclePlanningBlock(client, original.plan, original.account, original.ufg !== undefined);
|
|
502
|
+
const limitSource = options.limits === undefined ? original.limits : options.limits;
|
|
570
503
|
const progress = await readLifecycleExecutionProgress({ ...options, client }, block);
|
|
571
|
-
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled)
|
|
572
|
-
// Only the confirmed canonical terminal phase is terminal; a head-only
|
|
573
|
-
// Active observation is not, and the confirmation/nonce/receipt gates below
|
|
574
|
-
// remain in force for every nonterminal case.
|
|
504
|
+
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled)
|
|
575
505
|
return undefined;
|
|
576
|
-
}
|
|
577
506
|
if (!progress.confirmationSafe)
|
|
578
|
-
throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for
|
|
507
|
+
throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for the selected launch confirmation depth before continuing.");
|
|
579
508
|
if (progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed"))
|
|
580
|
-
throw new LifecyclePlanningError("RECEIPT_PENDING", "Wait for
|
|
509
|
+
throw new LifecyclePlanningError("RECEIPT_PENDING", "Wait for the pending transaction to confirm or resolve its replacement.");
|
|
581
510
|
assertPlanningActive(options.signal);
|
|
511
|
+
const { limits } = await resolveLifecycleLimits({ client, account: original.account, orchestrator: original.plan.orchestrator, block, chainId: original.chainId }, limitSource);
|
|
582
512
|
if (options.action === "cancel") {
|
|
583
513
|
if (progress.canonical.phase === LifecyclePhase.None)
|
|
584
514
|
throw new LifecyclePlanningError("NOT_STARTED", "An unstarted plan has no launch escrow to cancel");
|
|
585
|
-
const {
|
|
586
|
-
assertFinalPlan(planned, options.signal);
|
|
515
|
+
const planned = { ...original, progress };
|
|
587
516
|
const transaction = planned.ufg !== undefined ? buildUfgCancelTransaction({ plan: planned.plan, preparedMarkets: progress.canonical.preparedMarkets })
|
|
588
517
|
: { id: "cancel", kind: "cancel", chainId: Number(planned.chainId), from: planned.account, to: planned.plan.orchestrator, data: encodeFunctionData({ abi: launchLifecycleAbi, functionName: "cancelLaunch", args: [planned.plan] }), value: 0n, dependencies: [], postconditions: [{ kind: "launch", phase: "Cancelled", preparedMarkets: progress.canonical.preparedMarkets }] };
|
|
589
|
-
transaction.nonce = progress.pendingAccountNonce;
|
|
590
518
|
if (reviewed !== undefined && !matchesReviewedNextTransaction(transaction, reviewed))
|
|
591
|
-
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current
|
|
592
|
-
const
|
|
593
|
-
if (!
|
|
594
|
-
throw new LifecyclePlanningError("
|
|
595
|
-
|
|
596
|
-
if (next === undefined)
|
|
597
|
-
throw new LifecyclePlanningError("MISSING_GAS_PROOF", "Cancellation omitted its exact simulation admission", simulation);
|
|
598
|
-
return preflightNextTransaction(options, next, simulation, reviewed);
|
|
519
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current cancellation");
|
|
520
|
+
const assessment = await assessLifecycleTransactions({ client, planned, limits: limitSource, signal: options.signal }, [transaction], block, envelope);
|
|
521
|
+
if (!assessment.ready)
|
|
522
|
+
throw new LifecyclePlanningError(assessment.code ?? "CANCEL_NOT_READY", assessment.reason ?? "Cancellation could not be prepared. Refresh before continuing.", assessment);
|
|
523
|
+
return finalizeNextTransaction(options, attachEstimates([transaction], assessment)[0], reviewed);
|
|
599
524
|
}
|
|
600
|
-
const { planned } = options;
|
|
601
|
-
assertFinalPlan(planned, options.signal);
|
|
602
525
|
const funding = progress.canonical.phase === LifecyclePhase.None
|
|
603
|
-
? await readFunding(client,
|
|
526
|
+
? await readFunding(client, original.plan, block, original.account, original.ufg !== undefined)
|
|
604
527
|
: { prerequisites: [], approvals: [] };
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
528
|
+
const planned = { ...original, progress, prerequisites: funding.prerequisites };
|
|
529
|
+
const capacity = deriveLifecycleLaunchCapacity({ plan: planned.plan, predictedToken: planned.predictedToken, ufg: planned.ufg, headroomBps: limits.headroomBps });
|
|
530
|
+
if (progress.canonical.phase === LifecyclePhase.None &&
|
|
531
|
+
await selectLaunchMode(planned.mode, planned.plan, capacity, limits, client, block) !== planned.mode) {
|
|
532
|
+
throw new LifecyclePlanningError("MODE_REVIEW_REQUIRED", "This launch requires staged execution. Refresh the review before continuing.");
|
|
533
|
+
}
|
|
534
|
+
let candidate = funding.approvals[0];
|
|
535
|
+
if (candidate === undefined) {
|
|
536
|
+
const prepared = progress.canonical.preparedMarkets;
|
|
537
|
+
let command;
|
|
538
|
+
if (progress.canonical.phase === LifecyclePhase.None)
|
|
539
|
+
command = { kind: planned.mode === "atomic" ? "atomic" : "begin" };
|
|
540
|
+
else if (prepared < planned.plan.markets.length) {
|
|
541
|
+
let count;
|
|
542
|
+
if (reviewed?.kind === "prepare") {
|
|
543
|
+
const span = /^prepare:(\d+):(\d+)$/.exec(reviewed.id);
|
|
544
|
+
if (Number(span?.[1]) !== prepared)
|
|
545
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation does not start at the unfinished market");
|
|
546
|
+
count = Number(span?.[2]);
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
const batches = await preparationBatches(planned.plan, prepared, planned.ufg, limits, client, block);
|
|
550
|
+
count = batches[prepared > 0 ? 1 : 0];
|
|
551
|
+
}
|
|
552
|
+
if (!Number.isSafeInteger(count) || count <= 0 || prepared + count > planned.plan.markets.length)
|
|
553
|
+
throw new LifecyclePlanningError("INVALID_PREPARATION_BATCHES", "Next preparation must cover a positive contiguous unfinished market span");
|
|
554
|
+
command = { kind: "prepare", first: prepared, count };
|
|
625
555
|
}
|
|
626
|
-
|
|
556
|
+
else
|
|
557
|
+
command = { kind: "activate" };
|
|
558
|
+
candidate = buildLifecycleCommand(planned.plan, command, planned.ufg === undefined ? undefined : { account: ufgPlanPrincipal(planned.plan), config: planned.ufg.config });
|
|
627
559
|
}
|
|
628
|
-
const candidate = orderedTransactions(planned.plan, planned.mode, progress, funding.approvals, batches, planned.ufg)[0];
|
|
629
|
-
if (candidate === undefined)
|
|
630
|
-
return undefined;
|
|
631
560
|
if (reviewed !== undefined && !matchesReviewedNextTransaction(candidate, reviewed))
|
|
632
|
-
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current
|
|
633
|
-
const
|
|
634
|
-
const compute = observed?.computeGasRequired;
|
|
635
|
-
const sizing = reviewed !== undefined || compute === undefined ? undefined : new Map([[candidate.id, compute]]);
|
|
636
|
-
const simulation = await simulateLaunchTransactions({ client, planned, limits: limitSource, fork: options.fork, signal: options.signal }, [candidate], block, reviewed, sizing);
|
|
561
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current next step");
|
|
562
|
+
const assessment = await assessLifecycleTransactions({ client, planned, limits: limitSource, signal: options.signal }, [candidate], block, envelope, undefined, capacity);
|
|
637
563
|
assertFinalPlan(planned, options.signal);
|
|
638
|
-
if (!
|
|
639
|
-
throw new LifecyclePlanningError("
|
|
640
|
-
|
|
641
|
-
return preflightNextTransaction(options, next, simulation, reviewed);
|
|
564
|
+
if (!assessment.ready)
|
|
565
|
+
throw new LifecyclePlanningError(assessment.code ?? "PLAN_NOT_READY", assessment.reason ?? "The next launch step could not be prepared. Refresh before continuing.", assessment);
|
|
566
|
+
return finalizeNextTransaction(options, attachEstimates([candidate], assessment)[0], reviewed);
|
|
642
567
|
});
|
|
643
|
-
|
|
568
|
+
assertInput();
|
|
644
569
|
if (result !== undefined)
|
|
645
570
|
assertReviewedTransactionUnchanged(options, reviewed, result);
|
|
646
571
|
return result;
|
|
647
572
|
}
|
|
648
|
-
/** Re-simulates only canonical unfinished work with the same submitted receipt
|
|
649
|
-
* evidence and confirmation depth as build-next, never silently dropping context. */
|
|
650
|
-
export async function simulateLaunchPlan(options) {
|
|
651
|
-
const { client, planned } = options;
|
|
652
|
-
if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase())
|
|
653
|
-
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its commitment");
|
|
654
|
-
const current = await planLaunch({
|
|
655
|
-
client, plan: planned.plan, account: planned.account, mode: planned.mode,
|
|
656
|
-
limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork,
|
|
657
|
-
receipts: options.receipts, confirmations: options.confirmations ?? planned.confirmations, signal: options.signal,
|
|
658
|
-
ufg: planned.ufg, allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
|
|
659
|
-
});
|
|
660
|
-
assertPlannedDeploymentBindings(planned, current);
|
|
661
|
-
return current.simulation;
|
|
662
|
-
}
|
|
663
573
|
//# sourceMappingURL=planner.js.map
|