@black-market/sdk 0.14.0 → 0.15.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 +4 -3
- package/dist/lifecycle/authors.js +2 -2
- package/dist/lifecycle/authors.js.map +1 -1
- package/dist/lifecycle/gas-profile.d.ts +35 -0
- package/dist/lifecycle/gas-profile.d.ts.map +1 -0
- package/dist/lifecycle/gas-profile.js +183 -0
- package/dist/lifecycle/gas-profile.js.map +1 -0
- package/dist/lifecycle/index.d.ts +3 -1
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +3 -1
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/lens.d.ts +1 -1
- package/dist/lifecycle/lens.d.ts.map +1 -1
- package/dist/lifecycle/lens.js +1 -1
- package/dist/lifecycle/lens.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 +2 -2
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +178 -163
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +6 -1
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +7 -0
- 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 +24 -19
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +58 -50
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/simulation.d.ts +3 -4
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +98 -247
- package/dist/lifecycle/simulation.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 +19 -3
- package/dist/lifecycle/transactions.d.ts.map +1 -1
- package/dist/lifecycle/transactions.js +25 -20
- package/dist/lifecycle/transactions.js.map +1 -1
- package/dist/lifecycle/types.d.ts +53 -45
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/lifecycle/ufg.d.ts.map +1 -1
- package/dist/lifecycle/ufg.js +3 -7
- package/dist/lifecycle/ufg.js.map +1 -1
- package/docs/launch-lifecycle.md +71 -82
- package/docs/lifecycle-gas-profiles.md +90 -0
- package/docs/reference.md +53 -51
- package/examples/README.md +13 -10
- package/examples/launch-example.ts +35 -25
- package/examples/launch-lifecycle-smoke.ts +5 -3
- package/package.json +1 -1
- package/src/lifecycle/authors.ts +2 -2
- package/src/lifecycle/gas-profile.ts +175 -0
- package/src/lifecycle/index.ts +3 -1
- package/src/lifecycle/lens.ts +1 -1
- package/src/lifecycle/opening-buys.ts +157 -0
- package/src/lifecycle/planner.ts +164 -151
- package/src/lifecycle/presets.ts +12 -1
- package/src/lifecycle/progress.ts +19 -20
- package/src/lifecycle/rpc.ts +56 -44
- package/src/lifecycle/simulation.ts +85 -231
- package/src/lifecycle/snapshot.ts +1 -2
- package/src/lifecycle/transactions.ts +33 -21
- package/src/lifecycle/types.ts +52 -42
- package/src/lifecycle/ufg.ts +3 -7
|
@@ -5,17 +5,20 @@ 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 { assertLifecycleReadClientOpen,
|
|
9
|
-
import {
|
|
8
|
+
import { assertLifecycleReadClientOpen, lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleStage, nitroPosterGasBound, readLifecycleChainId, readLifecycleContract, readNitroPrices, registerLifecycleFinalCheck, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
9
|
+
import { observedFields, prepareLaunchSimulation } from "./simulation.js";
|
|
10
10
|
import { LifecyclePlanningError } from "./types.js";
|
|
11
11
|
import { deriveKnownPoolBoundHookDeployment, protectOrderedBuyMinimums, validateBuySlippageBps } from "./calibration.js";
|
|
12
12
|
import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
|
|
13
|
-
import {
|
|
13
|
+
import { buildLifecycleCommand, packPreparationBatches } from "./transactions.js";
|
|
14
14
|
import { assertLifecycleMarketAllocation, assertLifecycleSupplyAllocation } from "./allocations.js";
|
|
15
|
-
import { assertUfgLaunchAccount, hashUfgLaunchConfig, UFG_LAUNCH_PROXY_ADDRESS } from "./ufg-config.js";
|
|
16
|
-
import {
|
|
15
|
+
import { assertUfgLaunchAccount, hashUfgLaunchConfig, ufgPlanPrincipal, UFG_LAUNCH_PROXY_ADDRESS } from "./ufg-config.js";
|
|
16
|
+
import { buildUfgCancelTransaction, validateUfgLaunchPlan } from "./ufg.js";
|
|
17
17
|
import { readLifecyclePlanningBlock } from "./snapshot.js";
|
|
18
18
|
import { mineLifecycleHooks } from "./mining.js";
|
|
19
|
+
import { computeLifecycleOpeningBuys, hasProfiledOpeningMath } from "./opening-buys.js";
|
|
20
|
+
import { deriveLifecycleLaunchCapacity, profileLifecycleTransactions } from "./gas-profile.js";
|
|
21
|
+
const defaultPlanReadContext = {};
|
|
19
22
|
function validatePlanShape(plan) {
|
|
20
23
|
// Encoding checks all exact integer widths, addresses, byte strings and tuple fields.
|
|
21
24
|
const commitment = hashLaunchPlan(plan);
|
|
@@ -152,28 +155,44 @@ async function readPlanMetadata(client, plan, token, block) {
|
|
|
152
155
|
}))).filter((deployment) => deployment !== undefined);
|
|
153
156
|
return { profiles, hookDeployments, reasons: live.filter((profile) => !profile.admitted).map((profile) => profile.reason ?? "Unlisted lifecycle profile is unavailable") };
|
|
154
157
|
}
|
|
155
|
-
function orderedTransactions(plan, mode, progress, approvals, batches, ufg) {
|
|
158
|
+
function orderedTransactions(plan, mode, progress, approvals, batches, ufg, capacity) {
|
|
156
159
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled)
|
|
157
160
|
return [];
|
|
158
|
-
const prepared = progress.canonical.preparedMarkets;
|
|
159
|
-
const compiled =
|
|
160
|
-
|
|
161
|
+
const prepared = progress.canonical.preparedMarkets, route = ufg === undefined ? undefined : { account: ufgPlanPrincipal(plan), config: ufg.config };
|
|
162
|
+
const compiled = [];
|
|
163
|
+
if (mode === "atomic")
|
|
164
|
+
compiled.push(capacity?.atomic ?? buildLifecycleCommand(plan, { kind: "atomic" }, route));
|
|
165
|
+
else {
|
|
166
|
+
compiled.push(buildLifecycleCommand(plan, { kind: "begin" }, route));
|
|
167
|
+
let first = 0;
|
|
168
|
+
for (const count of batches) {
|
|
169
|
+
compiled.push(buildLifecycleCommand(plan, { kind: "prepare", first, count }, route));
|
|
170
|
+
first += count;
|
|
171
|
+
}
|
|
172
|
+
compiled.push(capacity?.activation ?? buildLifecycleCommand(plan, { kind: "activate" }, route));
|
|
173
|
+
}
|
|
161
174
|
const commands = compiled.filter((transaction) => (transaction.kind !== "begin" && transaction.kind !== "atomic" || progress.canonical.phase === LifecyclePhase.None) &&
|
|
162
175
|
(transaction.kind !== "prepare" || (transaction.marketStart ?? 0) >= prepared));
|
|
163
176
|
const transactions = progress.canonical.phase === LifecyclePhase.None ? [...approvals, ...commands] : commands;
|
|
164
|
-
return transactions.map((transaction, index) => ({ ...transaction,
|
|
177
|
+
return transactions.map((transaction, index) => ({ ...transaction, calldataBytes: (transaction.data.length - 2) / 2, dependencies: index === 0 ? [] : [transactions[index - 1].id] }));
|
|
165
178
|
}
|
|
166
179
|
function attachEstimates(transactions, simulation) {
|
|
167
|
-
return transactions.map((transaction) => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
180
|
+
return transactions.map((transaction, index) => {
|
|
181
|
+
if (index !== 0)
|
|
182
|
+
return transaction;
|
|
183
|
+
const admission = { admitted: simulation.admitted, confidence: simulation.confidence, executionProof: simulation.executionProof, protocolFit: simulation.protocolFit,
|
|
184
|
+
blockNumber: simulation.blockNumber, blockHash: simulation.blockHash, account: simulation.account, chainId: simulation.chainId, reason: simulation.reason, limits: simulation.limits };
|
|
185
|
+
if (transaction.kind === "approve" || transaction.kind === "approve-reset")
|
|
186
|
+
return { ...transaction, admission };
|
|
187
|
+
const estimate = simulation.steps.find((step) => step.transactionId === transaction.id)?.estimate ?? simulation.estimate;
|
|
188
|
+
return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate, admission };
|
|
171
189
|
});
|
|
172
190
|
}
|
|
173
191
|
const reviewedTransactionKinds = ["approve-reset", "approve", "atomic", "begin", "prepare", "activate", "cancel"];
|
|
174
192
|
function readReviewedNextTransaction(transaction) {
|
|
175
193
|
if (transaction === undefined)
|
|
176
194
|
return undefined;
|
|
195
|
+
const walletApproval = transaction?.kind === "approve" || transaction?.kind === "approve-reset";
|
|
177
196
|
if (transaction === null || typeof transaction !== "object" || typeof transaction.id !== "string" ||
|
|
178
197
|
!reviewedTransactionKinds.includes(transaction.kind) ||
|
|
179
198
|
!Number.isSafeInteger(transaction.chainId) || transaction.chainId <= 0 ||
|
|
@@ -181,9 +200,8 @@ function readReviewedNextTransaction(transaction) {
|
|
|
181
200
|
typeof transaction.to !== "string" || !/^0x[\da-fA-F]{40}$/.test(transaction.to) ||
|
|
182
201
|
typeof transaction.data !== "string" || !/^0x(?:[\da-fA-F]{2})*$/.test(transaction.data) ||
|
|
183
202
|
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)) {
|
|
203
|
+
!walletApproval && (typeof transaction.gas !== "bigint" || transaction.gas <= 0n || transaction.gas >= 1n << 64n ||
|
|
204
|
+
typeof transaction.gasPrice !== "bigint" || transaction.gasPrice < 0n || transaction.gasPrice >= 1n << 256n)) {
|
|
187
205
|
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
206
|
}
|
|
189
207
|
return transaction;
|
|
@@ -191,8 +209,7 @@ function readReviewedNextTransaction(transaction) {
|
|
|
191
209
|
function matchesReviewedNextTransaction(next, reviewed) {
|
|
192
210
|
return next !== undefined && next.id === reviewed.id && next.kind === reviewed.kind && next.chainId === reviewed.chainId &&
|
|
193
211
|
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);
|
|
212
|
+
next.data.toLowerCase() === reviewed.data.toLowerCase() && next.value === reviewed.value;
|
|
196
213
|
}
|
|
197
214
|
function assertReviewedTransactionUnchanged(options, reviewed, next) {
|
|
198
215
|
if (options.planned.ufg !== undefined)
|
|
@@ -204,9 +221,9 @@ function assertReviewedTransactionUnchanged(options, reviewed, next) {
|
|
|
204
221
|
throw new LifecyclePlanningError("CHAIN_MISMATCH", "Execution chain changed during fresh execution or submission proof");
|
|
205
222
|
}
|
|
206
223
|
const current = readReviewedNextTransaction(options.reviewedTransaction);
|
|
207
|
-
if (reviewed === undefined ? current !== undefined : current === undefined || !matchesReviewedNextTransaction(current, reviewed) ||
|
|
208
|
-
|
|
209
|
-
|
|
224
|
+
if (reviewed === undefined ? current !== undefined : current === undefined || !matchesReviewedNextTransaction(current, reviewed) || !matchesReviewedNextTransaction(next, reviewed) ||
|
|
225
|
+
reviewed.kind !== "approve" && reviewed.kind !== "approve-reset" &&
|
|
226
|
+
(current.gas !== reviewed.gas || current.gasPrice !== reviewed.gasPrice || next.gas !== reviewed.gas || next.gasPrice !== reviewed.gasPrice)) {
|
|
210
227
|
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_CHANGED", "Reviewed transaction changed during fresh execution or submission proof");
|
|
211
228
|
}
|
|
212
229
|
}
|
|
@@ -244,7 +261,7 @@ function validatePlanningIntent(options) {
|
|
|
244
261
|
const { plan, account, mode } = options;
|
|
245
262
|
assertPlanningActive(options.signal);
|
|
246
263
|
if (mode !== "atomic" && mode !== "staged")
|
|
247
|
-
throw new LifecyclePlanningError("EXPLICIT_MODE_REQUIRED", "Choose atomic or
|
|
264
|
+
throw new LifecyclePlanningError("EXPLICIT_MODE_REQUIRED", "Choose an atomic preference or explicit staged mode");
|
|
248
265
|
if (options.ufg !== undefined)
|
|
249
266
|
assertUfgLaunchAccount(plan, account);
|
|
250
267
|
else if (account.toLowerCase() !== plan.creator.toLowerCase())
|
|
@@ -295,21 +312,28 @@ function assertFinalPlan(planned, signal) {
|
|
|
295
312
|
if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash)
|
|
296
313
|
throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed during asynchronous planning");
|
|
297
314
|
}
|
|
315
|
+
async function evaluateLaunchTransactions(options, transactions, block, controls = defaultPlanReadContext) {
|
|
316
|
+
const profile = controls.diagnostic ? undefined : await profileLifecycleTransactions(options, transactions, block, controls.finalEnvelope, controls.openingBuys, controls.capacity);
|
|
317
|
+
if (!controls.forceSimulation && profile?.profileOnly)
|
|
318
|
+
return profile.simulation;
|
|
319
|
+
const prepared = prepareLaunchSimulation(options, block, transactions.length !== 0, controls.immediate);
|
|
320
|
+
try {
|
|
321
|
+
const simulation = controls.diagnostic ? await prepared.measure(transactions) : await prepared(transactions, controls.finalEnvelope);
|
|
322
|
+
return profile === undefined ? simulation : { ...simulation, gasProfile: profile.simulation.gasProfile };
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
prepared.close();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
298
328
|
/** One owner constructs and mines, optionally measures actual ordered buy outputs,
|
|
299
329
|
* then proves only the final protected commitment. No cached execution certificates. */
|
|
300
330
|
export async function prepareAndPlanLifecycleLaunch(options) {
|
|
301
331
|
const { commitment, ufgConfigHash } = validatePlanningIntent(options);
|
|
302
332
|
if (options.buySlippageBps !== undefined)
|
|
303
333
|
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
334
|
const needsMining = options.plan.markets.some((market) => isPoolBoundV4ConfigVersion(market.configVersion));
|
|
311
335
|
const needsProtection = options.buySlippageBps !== undefined && options.plan.buys.length !== 0;
|
|
312
|
-
if (!needsMining && !needsProtection
|
|
336
|
+
if (!needsMining && !needsProtection)
|
|
313
337
|
return planLaunch(options);
|
|
314
338
|
const draft = parseLaunchPlan(serializeLaunchPlan(options.plan));
|
|
315
339
|
const assertDraft = () => {
|
|
@@ -318,9 +342,6 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
318
342
|
throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed during asynchronous preparation; previous construction is invalid");
|
|
319
343
|
if ((options.ufg === undefined ? undefined : hashUfgLaunchConfig(options.ufg.config)) !== ufgConfigHash)
|
|
320
344
|
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
345
|
};
|
|
325
346
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
326
347
|
const block = await readLifecyclePlanningBlock(client, draft, options.account, options.ufg !== undefined, true);
|
|
@@ -344,22 +365,25 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
344
365
|
markets[marketIndex] = { ...market, config: encodePoolBoundV4LifecycleMarketConfig({ ...decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion), hookSalt: mined.salt }) };
|
|
345
366
|
}
|
|
346
367
|
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
368
|
let plan = { ...draft, markets };
|
|
356
369
|
let ownedProgress;
|
|
357
|
-
let
|
|
358
|
-
if (needsProtection) {
|
|
370
|
+
let openingBuys;
|
|
371
|
+
if (needsProtection && hasProfiledOpeningMath(plan)) {
|
|
372
|
+
const predictedToken = await readLifecycleTokenAtBlock(client, plan, block);
|
|
373
|
+
openingBuys = computeLifecycleOpeningBuys({ plan, predictedToken });
|
|
374
|
+
const factor = BigInt(10000 - options.buySlippageBps);
|
|
375
|
+
plan = { ...plan, buys: plan.buys.map((buy, index) => {
|
|
376
|
+
const minTokenOut = openingBuys[index].tokenOut * factor / 10000n;
|
|
377
|
+
if (minTokenOut <= 0n)
|
|
378
|
+
throw new LifecyclePlanningError("INVALID_BUY_OUTPUT", "Each opening buy must produce a positive protected minimum");
|
|
379
|
+
return { ...buy, minTokenOut };
|
|
380
|
+
}) };
|
|
381
|
+
}
|
|
382
|
+
else if (needsProtection) {
|
|
359
383
|
// A diagnostic minimum is never returned as an admitted or executable plan.
|
|
360
384
|
// Price guards, funding minima, recipients and all ordered budgets stay exact.
|
|
361
385
|
const diagnosticPlan = { ...plan, buys: plan.buys.map((buy) => ({ ...buy, minTokenOut: 1n })) };
|
|
362
|
-
const diagnostic = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan: diagnosticPlan }, client,
|
|
386
|
+
const diagnostic = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan: diagnosticPlan }, client, { block, diagnostic: true }));
|
|
363
387
|
assertDraft();
|
|
364
388
|
if (diagnostic.progress.canonical.phase !== LifecyclePhase.None || diagnostic.progress.head.phase !== LifecyclePhase.None)
|
|
365
389
|
throw new LifecyclePlanningError("LAUNCH_ALREADY_STARTED", "Buy protection cannot replace an already committed launch plan");
|
|
@@ -367,9 +391,8 @@ export async function prepareAndPlanLifecycleLaunch(options) {
|
|
|
367
391
|
// This is the real SDK-owned None observation, not an assumed new-launch
|
|
368
392
|
// state. No onchain commitment existed at this exact snapshot to replace.
|
|
369
393
|
ownedProgress = { ...diagnostic.progress, planHash: hashLaunchPlan(plan) };
|
|
370
|
-
sizing = new Map(diagnostic.simulation.steps.map((step) => [step.transactionId, step.gasRequired ?? step.gasUsed]));
|
|
371
394
|
}
|
|
372
|
-
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client,
|
|
395
|
+
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, { block, progress: ownedProgress, openingBuys }));
|
|
373
396
|
assertDraft();
|
|
374
397
|
assertFinalPlan(planned, options.signal);
|
|
375
398
|
if (planned.progress.canonical.phase !== LifecyclePhase.Active && planned.progress.canonical.phase !== LifecyclePhase.Cancelled) {
|
|
@@ -397,8 +420,10 @@ export async function planLaunch(options) {
|
|
|
397
420
|
assertFinalPlan(result, options.signal);
|
|
398
421
|
return result;
|
|
399
422
|
}
|
|
400
|
-
async function planLaunchWithReads(options, client,
|
|
401
|
-
const {
|
|
423
|
+
async function planLaunchWithReads(options, client, readContext = defaultPlanReadContext) {
|
|
424
|
+
const { block: pinnedBlock, progress: ownedProgress, diagnostic = false, openingBuys, forceSimulation = false } = readContext;
|
|
425
|
+
const { plan, account, mode: requestedMode, ufg } = options;
|
|
426
|
+
let mode = requestedMode;
|
|
402
427
|
const { commitment: planHash, marketConfigs, ufgConfigHash } = validatePlanningIntent(options);
|
|
403
428
|
const launchId = hashLaunchIdentity(plan);
|
|
404
429
|
const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
|
|
@@ -437,7 +462,7 @@ async function planLaunchWithReads(options, client, pinnedBlock, ownedProgress,
|
|
|
437
462
|
}),
|
|
438
463
|
]));
|
|
439
464
|
assertPlanningActive(options.signal);
|
|
440
|
-
const placeholder = { backend: "unavailable", confidence: "provisional", admitted: false, executionProof: "unavailable", protocolFit: "unknown",
|
|
465
|
+
const placeholder = { backend: "unavailable", confidence: "provisional", admitted: false, executionProof: "unavailable", protocolFit: "unknown", blockNumber: block.number, blockHash: block.hash, account, chainId: plan.chainId, limits, steps: [], deferredTransactionIds: [] };
|
|
441
466
|
let planned = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, simulation: placeholder, limits: options.limits,
|
|
442
467
|
allowTopologyGaps: options.allowTopologyGaps === true, allowMissingPermanentTail: options.allowMissingPermanentTail === true };
|
|
443
468
|
const receiptsPending = progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed");
|
|
@@ -450,7 +475,7 @@ async function planLaunchWithReads(options, client, pinnedBlock, ownedProgress,
|
|
|
450
475
|
const known = deriveKnownPoolBoundHookDeployment({ plan, token: identity.predictedToken, marketIndex });
|
|
451
476
|
hookDeployments.push({ marketIndex, ...(known?.deployment ?? await readPoolBoundHookDeployment({ client, plan, marketIndex }, block)) });
|
|
452
477
|
}
|
|
453
|
-
const waiting = { ...placeholder, confidence: "stateful", reason: !progress.confirmationSafe ? "Canonical
|
|
478
|
+
const waiting = { ...placeholder, confidence: "stateful", reason: !progress.confirmationSafe ? "Canonical launch progress has not reached the selected confirmation depth" : "Wait for canonical receipt confirmations or resolve replacement before requesting another transaction" };
|
|
454
479
|
return { ...planned, hookDeployments, simulation: waiting };
|
|
455
480
|
}
|
|
456
481
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) {
|
|
@@ -465,99 +490,84 @@ async function planLaunchWithReads(options, client, pinnedBlock, ownedProgress,
|
|
|
465
490
|
readPlanMetadata(client, plan, identity.predictedToken, block),
|
|
466
491
|
]));
|
|
467
492
|
assertFinalPlan(planned, options.signal);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
let transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg);
|
|
475
|
-
if (fixedFinalEnvelope !== undefined && (transactions.length !== 1 || transactions[0].kind !== "atomic")) {
|
|
476
|
-
throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Fixed final envelope requires one immediate atomic launch with no preceding approvals");
|
|
493
|
+
const capacity = limits.protocol === "nitro" && limits.arbOSVersion === 61n
|
|
494
|
+
? deriveLifecycleLaunchCapacity({ plan, predictedToken: identity.predictedToken, ufg, headroomBps: limits.headroomBps }, openingBuys) : undefined;
|
|
495
|
+
if (!forceSimulation && progress.canonical.phase === LifecyclePhase.None && mode === "atomic" && capacity !== undefined) {
|
|
496
|
+
const prices = await readNitroPrices(client, block);
|
|
497
|
+
if (capacity.bounds.atomicComputeGasUpperBound + nitroPosterGasBound(capacity.atomic, prices).posterGas > limits.executionGasCeiling)
|
|
498
|
+
mode = "staged";
|
|
477
499
|
}
|
|
500
|
+
planned = { ...planned, mode, prerequisites: funding.prerequisites, profiles: metadata.profiles, hookDeployments: metadata.hookDeployments };
|
|
501
|
+
const prepared = progress.canonical.preparedMarkets, computeCap = limits.executionGasCeiling;
|
|
502
|
+
let batches = mode === "staged" ? packPreparationBatches(plan, prepared, computeCap) : [];
|
|
503
|
+
let transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
|
|
478
504
|
if (metadata.reasons.length > 0) {
|
|
479
505
|
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
506
|
}
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
507
|
+
const execute = (candidate, quotes = openingBuys) => evaluateLaunchTransactions({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, candidate, block, { diagnostic, openingBuys: quotes, capacity, forceSimulation });
|
|
508
|
+
let simulation = await execute(transactions);
|
|
509
|
+
assertFinalPlan(planned, options.signal);
|
|
510
|
+
if (!diagnostic && !forceSimulation && requestedMode === "atomic" && mode === "atomic" && progress.canonical.phase === LifecyclePhase.None &&
|
|
511
|
+
simulation.executionProof === "failed" && simulation.failureCategory === "capacity" && simulation.capacityConstraint === "compute") {
|
|
512
|
+
mode = "staged";
|
|
513
|
+
planned = { ...planned, mode };
|
|
514
|
+
batches = packPreparationBatches(plan, prepared, computeCap);
|
|
515
|
+
transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
|
|
516
|
+
simulation = await execute(transactions);
|
|
486
517
|
assertFinalPlan(planned, options.signal);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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` };
|
|
518
|
+
}
|
|
519
|
+
if (mode === "staged") {
|
|
520
|
+
// Only pressure at the native ceiling can invalidate packing. A smaller
|
|
521
|
+
// affordability/RPC/account envelope is not evidence for another wallet write.
|
|
522
|
+
while (!simulation.admitted && simulation.failedTransactionId?.startsWith("prepare:")) {
|
|
523
|
+
const failedIndex = transactions.findIndex((transaction) => transaction.id === simulation.failedTransactionId);
|
|
524
|
+
const failedTransaction = transactions[failedIndex];
|
|
525
|
+
const step = simulation.steps.find((candidate) => candidate.transactionId === simulation.failedTransactionId);
|
|
526
|
+
const count = failedTransaction?.marketCount ?? 0;
|
|
527
|
+
if (count <= 1 || step?.gasUsed === undefined || step.gasLimit === undefined || step.gasLimit < limits.executionGasCeiling || step.gasUsed * 100n < limits.executionGasCeiling * 95n)
|
|
528
|
+
break;
|
|
529
|
+
let first = 0;
|
|
530
|
+
const batchIndex = batches.findIndex((batch) => { const matches = first === failedTransaction?.marketStart; first += batch; return matches; });
|
|
531
|
+
const left = Math.ceil(count / 2);
|
|
532
|
+
batches.splice(batchIndex, 1, left, count - left);
|
|
533
|
+
transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg, capacity);
|
|
534
|
+
simulation = await execute(transactions, simulation.gasProfile?.initialBuys);
|
|
535
|
+
assertFinalPlan(planned, options.signal);
|
|
510
536
|
}
|
|
511
|
-
|
|
537
|
+
if (!simulation.admitted && simulation.failedTransactionId === "activate" && simulation.failureCategory === "capacity")
|
|
538
|
+
simulation = {
|
|
539
|
+
...simulation, capacityConstraint: "activation", reason: "Too many positions or pools for one activation. Reduce them to fit the native transaction ceiling."
|
|
540
|
+
};
|
|
512
541
|
}
|
|
513
|
-
|
|
514
|
-
|
|
542
|
+
else if (!diagnostic && !simulation.admitted) {
|
|
543
|
+
simulation = { ...simulation, reason: simulation.reason ?? "The exact atomic launch is not admitted" };
|
|
515
544
|
}
|
|
545
|
+
return { ...planned, simulation, transactions: attachEstimates(transactions, simulation), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
|
|
516
546
|
}
|
|
517
|
-
|
|
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: [] };
|
|
529
|
-
try {
|
|
530
|
-
if (rpcQuantity(await lifecycleRpc(submissionClient, "eth_chainId"), "submission chain ID") !== simulation.chainId)
|
|
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);
|
|
539
|
-
}
|
|
540
|
-
catch (failure) {
|
|
541
|
-
if (failure instanceof Error && failure.name === "AbortError")
|
|
542
|
-
throw failure;
|
|
543
|
-
const reason = `Exact next-transaction submission preflight failed: ${lifecycleFailureReason(failure)}`;
|
|
544
|
-
throw new LifecyclePlanningError("SUBMISSION_PREFLIGHT_FAILED", reason, { ...simulation, transportPreflight: "failed", reason });
|
|
545
|
-
}
|
|
546
|
-
if (next.admission === undefined)
|
|
547
|
-
throw new LifecyclePlanningError("MISSING_GAS_PROOF", "Next transaction omitted its exact simulation admission", simulation);
|
|
547
|
+
function finalizeNextTransaction(options, next, reviewed) {
|
|
548
548
|
assertPlanningActive(options.signal);
|
|
549
549
|
assertReviewedTransactionUnchanged(options, reviewed, next);
|
|
550
|
-
return { ...next, dependencies: []
|
|
550
|
+
return { ...next, dependencies: [] };
|
|
551
551
|
}
|
|
552
|
-
/** Recovery observes real canonical receipts
|
|
553
|
-
* execution;
|
|
552
|
+
/** Recovery observes real canonical receipts, then evaluates only unfinished
|
|
553
|
+
* execution; the wallet owns its nonce and assignment at send time. */
|
|
554
554
|
export async function buildNextTransaction(options) {
|
|
555
|
+
const original = options.planned, matches = observedFields(original);
|
|
556
|
+
const fullReviewAdmitted = original.simulation?.admitted === true;
|
|
557
|
+
const assertInput = () => {
|
|
558
|
+
if (options.planned !== original || !matches())
|
|
559
|
+
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored launch context changed while constructing its next transaction");
|
|
560
|
+
assertFinalPlan(original, options.signal);
|
|
561
|
+
};
|
|
555
562
|
const supplied = readReviewedNextTransaction(options.reviewedTransaction);
|
|
556
563
|
// Snapshot only the reviewed wire fields; caller proofs and commands are never
|
|
557
564
|
// reused, and final checks compare the mutable input with this one local copy.
|
|
565
|
+
const walletApproval = supplied?.kind === "approve" || supplied?.kind === "approve-reset";
|
|
558
566
|
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
|
|
567
|
+
from: supplied.from, to: supplied.to, data: supplied.data, value: supplied.value, ...(walletApproval ? {} : { gas: supplied.gas, gasPrice: supplied.gasPrice }) });
|
|
568
|
+
const envelope = reviewed === undefined || walletApproval ? undefined : reviewed;
|
|
560
569
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
570
|
+
registerLifecycleFinalCheck(client, assertInput);
|
|
561
571
|
const block = await readLifecyclePlanningBlock(client, options.planned.plan, options.planned.account, options.planned.ufg !== undefined);
|
|
562
572
|
const limitSource = options.limits === undefined ? options.planned.limits : options.limits;
|
|
563
573
|
if (getKnownLifecycleDeployment({ chainId: options.planned.chainId, orchestrator: options.planned.plan.orchestrator }) !== undefined && typeof limitSource !== "function") {
|
|
@@ -570,12 +580,12 @@ export async function buildNextTransaction(options) {
|
|
|
570
580
|
const progress = await readLifecycleExecutionProgress({ ...options, client }, block);
|
|
571
581
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) {
|
|
572
582
|
// Only the confirmed canonical terminal phase is terminal; a head-only
|
|
573
|
-
// Active observation is not, and the
|
|
574
|
-
//
|
|
583
|
+
// Active observation is not, and the launch-state/receipt confirmation
|
|
584
|
+
// policy below remains in force for every nonterminal case.
|
|
575
585
|
return undefined;
|
|
576
586
|
}
|
|
577
587
|
if (!progress.confirmationSafe)
|
|
578
|
-
throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for
|
|
588
|
+
throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for the selected canonical launch confirmation depth before constructing the next transaction");
|
|
579
589
|
if (progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed"))
|
|
580
590
|
throw new LifecyclePlanningError("RECEIPT_PENDING", "Wait for canonical receipt confirmations or resolve replacement before requesting another transaction");
|
|
581
591
|
assertPlanningActive(options.signal);
|
|
@@ -586,61 +596,66 @@ export async function buildNextTransaction(options) {
|
|
|
586
596
|
assertFinalPlan(planned, options.signal);
|
|
587
597
|
const transaction = planned.ufg !== undefined ? buildUfgCancelTransaction({ plan: planned.plan, preparedMarkets: progress.canonical.preparedMarkets })
|
|
588
598
|
: { 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
599
|
if (reviewed !== undefined && !matchesReviewedNextTransaction(transaction, reviewed))
|
|
591
600
|
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical cancellation");
|
|
592
|
-
const simulation = await
|
|
601
|
+
const simulation = await evaluateLaunchTransactions({ client, planned, limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork, signal: options.signal }, [transaction], block, { finalEnvelope: envelope, immediate: true });
|
|
593
602
|
if (!simulation.admitted)
|
|
594
603
|
throw new LifecyclePlanningError("CANCEL_NOT_ADMITTED", simulation.reason ?? "Cancellation could not be proved against current canonical state", simulation);
|
|
595
604
|
const next = attachEstimates([transaction], simulation)[0];
|
|
596
605
|
if (next === undefined)
|
|
597
606
|
throw new LifecyclePlanningError("MISSING_GAS_PROOF", "Cancellation omitted its exact simulation admission", simulation);
|
|
598
|
-
return
|
|
607
|
+
return finalizeNextTransaction(options, next, reviewed);
|
|
599
608
|
}
|
|
600
609
|
const { planned } = options;
|
|
601
610
|
assertFinalPlan(planned, options.signal);
|
|
611
|
+
if (progress.canonical.phase === LifecyclePhase.None && !fullReviewAdmitted)
|
|
612
|
+
throw new LifecyclePlanningError("PLAN_NOT_ADMITTED", "Complete an admitted full launch review before starting or approving funding; next-only construction does not replan the remaining sequence");
|
|
602
613
|
const funding = progress.canonical.phase === LifecyclePhase.None
|
|
603
614
|
? await readFunding(client, planned.plan, block, planned.account, planned.ufg !== undefined)
|
|
604
615
|
: { prerequisites: [], approvals: [] };
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
let
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
616
|
+
let candidate = funding.approvals[0];
|
|
617
|
+
if (candidate === undefined) {
|
|
618
|
+
const prepared = progress.canonical.preparedMarkets;
|
|
619
|
+
let command;
|
|
620
|
+
if (progress.canonical.phase === LifecyclePhase.None)
|
|
621
|
+
command = { kind: planned.mode === "atomic" ? "atomic" : "begin" };
|
|
622
|
+
else if (prepared < planned.plan.markets.length) {
|
|
623
|
+
let count;
|
|
624
|
+
if (reviewed?.kind === "prepare") {
|
|
625
|
+
const span = /^prepare:(\d+):(\d+)$/.exec(reviewed.id);
|
|
626
|
+
const first = Number(span?.[1]);
|
|
627
|
+
count = Number(span?.[2]);
|
|
628
|
+
if (first !== prepared)
|
|
629
|
+
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation does not start at the canonical unfinished market");
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
const stored = planned.transactions.find((transaction) => transaction.kind === "prepare" && transaction.marketStart <= prepared && transaction.marketStart + transaction.marketCount > prepared);
|
|
633
|
+
if (stored !== undefined)
|
|
634
|
+
count = stored.marketStart + stored.marketCount - prepared;
|
|
635
|
+
else {
|
|
636
|
+
const { limits } = await resolveLifecycleLimits({ client, account: planned.account, orchestrator: planned.plan.orchestrator, block, chainId: planned.chainId }, limitSource);
|
|
637
|
+
const cap = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
|
|
638
|
+
count = packPreparationBatches(planned.plan, prepared, cap)[prepared > 0 ? 1 : 0];
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (!Number.isSafeInteger(count) || count <= 0 || prepared + count > planned.plan.markets.length)
|
|
642
|
+
throw new LifecyclePlanningError("INVALID_PREPARATION_BATCHES", "Next preparation must be a positive contiguous canonical market span");
|
|
643
|
+
command = { kind: "prepare", first: prepared, count };
|
|
625
644
|
}
|
|
626
|
-
|
|
645
|
+
else
|
|
646
|
+
command = { kind: "activate" };
|
|
647
|
+
candidate = buildLifecycleCommand(planned.plan, command, planned.ufg === undefined ? undefined : { account: ufgPlanPrincipal(planned.plan), config: planned.ufg.config });
|
|
627
648
|
}
|
|
628
|
-
const candidate = orderedTransactions(planned.plan, planned.mode, progress, funding.approvals, batches, planned.ufg)[0];
|
|
629
|
-
if (candidate === undefined)
|
|
630
|
-
return undefined;
|
|
631
649
|
if (reviewed !== undefined && !matchesReviewedNextTransaction(candidate, reviewed))
|
|
632
650
|
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
|
|
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);
|
|
651
|
+
const simulation = await evaluateLaunchTransactions({ client, planned, limits: limitSource, fork: options.fork, signal: options.signal }, [candidate], block, { finalEnvelope: envelope, immediate: true });
|
|
637
652
|
assertFinalPlan(planned, options.signal);
|
|
638
653
|
if (!simulation.admitted)
|
|
639
654
|
throw new LifecyclePlanningError("PLAN_NOT_ADMITTED", simulation.reason ?? "Next exact launch transaction is not admitted", simulation);
|
|
640
655
|
const next = attachEstimates([candidate], simulation)[0];
|
|
641
|
-
return
|
|
656
|
+
return finalizeNextTransaction(options, next, reviewed);
|
|
642
657
|
});
|
|
643
|
-
|
|
658
|
+
assertInput();
|
|
644
659
|
if (result !== undefined)
|
|
645
660
|
assertReviewedTransactionUnchanged(options, reviewed, result);
|
|
646
661
|
return result;
|
|
@@ -651,12 +666,12 @@ export async function simulateLaunchPlan(options) {
|
|
|
651
666
|
const { client, planned } = options;
|
|
652
667
|
if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase())
|
|
653
668
|
throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its commitment");
|
|
654
|
-
const current = await
|
|
669
|
+
const current = await withLifecycleReadClient(client, (readClient) => planLaunchWithReads({
|
|
655
670
|
client, plan: planned.plan, account: planned.account, mode: planned.mode,
|
|
656
671
|
limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork,
|
|
657
672
|
receipts: options.receipts, confirmations: options.confirmations ?? planned.confirmations, signal: options.signal,
|
|
658
673
|
ufg: planned.ufg, allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
|
|
659
|
-
});
|
|
674
|
+
}, readClient, { forceSimulation: true }));
|
|
660
675
|
assertPlannedDeploymentBindings(planned, current);
|
|
661
676
|
return current.simulation;
|
|
662
677
|
}
|