@black-market/sdk 0.11.0 → 0.13.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 +96 -79
- package/dist/lifecycle/abi.d.ts +1 -0
- package/dist/lifecycle/abi.d.ts.map +1 -1
- package/dist/lifecycle/abi.js +1 -0
- package/dist/lifecycle/abi.js.map +1 -1
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.d.ts +71 -0
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.d.ts.map +1 -0
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.js +73 -0
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.js.map +1 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.d.ts +71 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.d.ts.map +1 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.js +73 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.js.map +1 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +1 -0
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/markets.d.ts +3 -1
- package/dist/lifecycle/markets.d.ts.map +1 -1
- package/dist/lifecycle/markets.js +20 -3
- package/dist/lifecycle/markets.js.map +1 -1
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +101 -46
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +4 -2
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +11 -4
- package/dist/lifecycle/presets.js.map +1 -1
- package/dist/lifecycle/progress.d.ts +3 -1
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +145 -59
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.d.ts +8 -2
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +165 -9
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/simulation.d.ts +1 -1
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +15 -11
- package/dist/lifecycle/simulation.js.map +1 -1
- package/dist/lifecycle/types.d.ts +23 -1
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/lifecycle/ufg-abi.d.ts +2386 -0
- package/dist/lifecycle/ufg-abi.d.ts.map +1 -0
- package/dist/lifecycle/ufg-abi.js +171 -0
- package/dist/lifecycle/ufg-abi.js.map +1 -0
- package/dist/lifecycle/ufg-config.d.ts +38 -0
- package/dist/lifecycle/ufg-config.d.ts.map +1 -0
- package/dist/lifecycle/ufg-config.js +75 -0
- package/dist/lifecycle/ufg-config.js.map +1 -0
- package/dist/lifecycle/ufg.d.ts +21 -0
- package/dist/lifecycle/ufg.d.ts.map +1 -0
- package/dist/lifecycle/ufg.js +98 -0
- package/dist/lifecycle/ufg.js.map +1 -0
- package/docs/launch-lifecycle.md +611 -1363
- package/docs/reference.md +332 -0
- package/examples/.env.example +3 -2
- package/examples/README.md +172 -539
- package/examples/launch-example-support.ts +11 -3
- package/examples/launch-example.ts +1 -1
- package/examples/launch-lifecycle-smoke.ts +1 -1
- package/examples/ufg-launch-config.ts +31 -0
- package/package.json +1 -1
- package/src/lifecycle/abi.ts +1 -0
- package/src/lifecycle/deployments/ufg-dynamic-pool-20261021.ts +74 -0
- package/src/lifecycle/deployments/ufg-fixed-pool-20261021.ts +74 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/markets.ts +16 -3
- package/src/lifecycle/planner.ts +95 -45
- package/src/lifecycle/presets.ts +11 -4
- package/src/lifecycle/progress.ts +131 -51
- package/src/lifecycle/rpc.ts +158 -10
- package/src/lifecycle/simulation.ts +14 -10
- package/src/lifecycle/types.ts +15 -2
- package/src/lifecycle/ufg-abi.ts +173 -0
- package/src/lifecycle/ufg-config.ts +77 -0
- package/src/lifecycle/ufg.ts +86 -0
package/src/lifecycle/planner.ts
CHANGED
|
@@ -3,18 +3,21 @@ import { ABYSS_FEE_TIERS } from "../abyss.js";
|
|
|
3
3
|
import { assertLaunchTopologyCoverage, getTickAtSqrtRatio } from "../launch.js";
|
|
4
4
|
import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleErc20Abi } from "./abi.js";
|
|
5
5
|
import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, LIFECYCLE_MAX_ERC404_SUPPLY, parseLaunchPlan, serializeLaunchPlan, type LaunchPlanV1 } from "./schema.js";
|
|
6
|
-
import { readLaunchProgress, readLifecycleProfiles, readLifecycleTokenAtBlock, readPoolBoundHookDeployment } from "./progress.js";
|
|
6
|
+
import { readLaunchProgress, readLifecycleHeadProgress, readLifecycleProfiles, readLifecycleTokenAtBlock, readPoolBoundHookDeployment } from "./progress.js";
|
|
7
7
|
import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion, minePoolBoundHookSalt, type AbyssLifecycleMarketConfig, type V4LifecycleMarketConfig, type V4PoolBoundLifecycleMarketConfig } from "./markets.js";
|
|
8
|
-
import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleChainId, readLifecycleContract, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
8
|
+
import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleChainId, readLifecycleContract, readLifecycleGasPrice, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
9
9
|
import { prepareLaunchSimulation, simulateLaunchTransactions, type NextTransactionEnvelope } from "./simulation.js";
|
|
10
10
|
import { LifecyclePlanningError, type BuildNextTransactionOptions, type CanonicalLaunchProgress, type LifecycleBlock, type LifecycleConstructionProfile, type LifecycleFundingPrerequisite, type LifecyclePoolBoundHookDeployment, type LifecycleProfile, type LifecycleRpcClient, type LifecycleSimulation, type LifecycleTransaction, type PlannedLaunch, type PlanLaunchOptions, type PrepareAndPlanLifecycleLaunchOptions, type SimulateLaunchPlanOptions } from "./types.js";
|
|
11
11
|
import { deriveKnownPoolBoundHookDeployment, protectOrderedBuyMinimums, validateBuySlippageBps } from "./calibration.js";
|
|
12
12
|
import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
|
|
13
13
|
import { buildLaunchTransactions, 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 { buildUfgLaunchTransactions, buildUfgCancelTransaction, validateUfgLaunchPlan } from "./ufg.js";
|
|
15
17
|
|
|
16
18
|
type ValidatedPlanShape = {
|
|
17
19
|
commitment: Hex;
|
|
20
|
+
ufgConfigHash?: Hex;
|
|
18
21
|
marketConfigs: (AbyssLifecycleMarketConfig | V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig)[];
|
|
19
22
|
};
|
|
20
23
|
|
|
@@ -75,19 +78,19 @@ function validatePlanShape(plan: LaunchPlanV1): ValidatedPlanShape {
|
|
|
75
78
|
return { commitment, marketConfigs };
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
async function readFunding(client: LifecycleRpcClient, plan: LaunchPlanV1, block: LifecycleBlock): Promise<{ prerequisites: LifecycleFundingPrerequisite[]; approvals: LifecycleTransaction[] }> {
|
|
81
|
+
async function readFunding(client: LifecycleRpcClient, plan: LaunchPlanV1, block: LifecycleBlock, account = plan.creator, ufg = false): Promise<{ prerequisites: LifecycleFundingPrerequisite[]; approvals: LifecycleTransaction[] }> {
|
|
79
82
|
if (plan.funding.length === 0) return { prerequisites: [], approvals: [] };
|
|
80
83
|
const preset = getKnownLifecycleDeployment({ chainId: plan.chainId, orchestrator: plan.orchestrator });
|
|
81
|
-
const escrow = preset?.fundingEscrow ?? await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "fundingEscrow", [], block);
|
|
84
|
+
const escrow = ufg ? UFG_LAUNCH_PROXY_ADDRESS : preset?.fundingEscrow ?? await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "fundingEscrow", [], block);
|
|
82
85
|
// Only allowance observations construct approval commands. Core execution
|
|
83
86
|
// enforces input/output asset, funding target, balance and conversion predicates.
|
|
84
87
|
const prerequisites = await Promise.all(plan.funding.map(async (funding): Promise<LifecycleFundingPrerequisite> => {
|
|
85
88
|
const native = funding.kind === LifecycleFundingKind.NativeWrap || funding.kind === LifecycleFundingKind.Swap && funding.inputAsset === zeroAddress;
|
|
86
89
|
const [inputBalance, allowance] = native
|
|
87
|
-
? [rpcQuantity(await lifecyclePinnedRpc(client, "eth_getBalance", [
|
|
90
|
+
? [rpcQuantity(await lifecyclePinnedRpc(client, "eth_getBalance", [account, toHex(block.number)], block), "creator native balance"), undefined] as const
|
|
88
91
|
: await Promise.all([
|
|
89
|
-
readLifecycleContract<bigint>(client, funding.inputAsset, lifecycleErc20Abi, "balanceOf", [
|
|
90
|
-
readLifecycleContract<bigint>(client, funding.inputAsset, lifecycleErc20Abi, "allowance", [
|
|
92
|
+
readLifecycleContract<bigint>(client, funding.inputAsset, lifecycleErc20Abi, "balanceOf", [account], block),
|
|
93
|
+
readLifecycleContract<bigint>(client, funding.inputAsset, lifecycleErc20Abi, "allowance", [account, escrow], block),
|
|
91
94
|
]);
|
|
92
95
|
return { funding, inputBalance, requiredInput: funding.inputAmount, allowance, spender: escrow, nativeValue: native ? funding.inputAmount : 0n, conversion: funding.kind === LifecycleFundingKind.NativeWrap ? "native-wrap" : funding.kind === LifecycleFundingKind.Swap ? "allowlisted-swap" : "none" };
|
|
93
96
|
}));
|
|
@@ -103,8 +106,8 @@ async function readFunding(client: LifecycleRpcClient, plan: LaunchPlanV1, block
|
|
|
103
106
|
const approvals: LifecycleTransaction[] = [];
|
|
104
107
|
for (const total of totals.values()) {
|
|
105
108
|
if (total.allowance >= total.amount) continue;
|
|
106
|
-
if (total.allowance > 0n) approvals.push({ id: `approve-reset:${total.asset}`, kind: "approve-reset", chainId: Number(plan.chainId), from:
|
|
107
|
-
approvals.push({ id: `approve:${total.asset}`, kind: "approve", chainId: Number(plan.chainId), from:
|
|
109
|
+
if (total.allowance > 0n) approvals.push({ id: `approve-reset:${total.asset}`, kind: "approve-reset", chainId: Number(plan.chainId), from: account, to: total.asset, data: encodeFunctionData({ abi: lifecycleErc20Abi, functionName: "approve", args: [escrow, 0n] }), value: 0n, dependencies: [], postconditions: [{ kind: "allowance", asset: total.asset, spender: escrow, minimum: 0n, exact: 0n }] });
|
|
110
|
+
approvals.push({ id: `approve:${total.asset}`, kind: "approve", chainId: Number(plan.chainId), from: account, to: total.asset, data: encodeFunctionData({ abi: lifecycleErc20Abi, functionName: "approve", args: [escrow, total.amount] }), value: 0n, dependencies: [], postconditions: [{ kind: "allowance", asset: total.asset, spender: escrow, minimum: total.amount }] });
|
|
108
111
|
}
|
|
109
112
|
return { prerequisites, approvals };
|
|
110
113
|
}
|
|
@@ -132,14 +135,16 @@ async function readPlanMetadata(client: LifecycleRpcClient, plan: LaunchPlanV1,
|
|
|
132
135
|
return { profiles, hookDeployments, reasons: live.filter((profile) => !profile.admitted).map((profile) => profile.reason ?? "Unlisted lifecycle profile is unavailable") };
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
function orderedTransactions(plan: LaunchPlanV1, mode: "atomic" | "staged", progress: CanonicalLaunchProgress, approvals: readonly LifecycleTransaction[], batches: readonly number[]): LifecycleTransaction[] {
|
|
138
|
+
function orderedTransactions(plan: LaunchPlanV1, mode: "atomic" | "staged", progress: CanonicalLaunchProgress, approvals: readonly LifecycleTransaction[], batches: readonly number[], ufg?: PlanLaunchOptions["ufg"]): LifecycleTransaction[] {
|
|
136
139
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) return [];
|
|
137
140
|
const prepared = progress.canonical.preparedMarkets;
|
|
138
|
-
const
|
|
141
|
+
const compiled = ufg === undefined ? buildLaunchTransactions({ plan, mode, preparationBatches: mode === "staged" ? batches : undefined })
|
|
142
|
+
: buildUfgLaunchTransactions({ plan, config: ufg.config, mode, preparationBatches: mode === "staged" ? batches : undefined });
|
|
143
|
+
const commands = compiled.filter((transaction) =>
|
|
139
144
|
(transaction.kind !== "begin" && transaction.kind !== "atomic" || progress.canonical.phase === LifecyclePhase.None) &&
|
|
140
145
|
(transaction.kind !== "prepare" || (transaction.marketStart ?? 0) >= prepared));
|
|
141
146
|
const transactions = progress.canonical.phase === LifecyclePhase.None ? [...approvals, ...commands] : commands;
|
|
142
|
-
return transactions.map((transaction, index) => ({ ...transaction, calldataBytes: (transaction.data.length - 2) / 2, dependencies: index === 0 ? [] : [transactions[index - 1]!.id] }));
|
|
147
|
+
return transactions.map((transaction, index) => ({ ...transaction, nonce: progress.pendingAccountNonce + BigInt(index), calldataBytes: (transaction.data.length - 2) / 2, dependencies: index === 0 ? [] : [transactions[index - 1]!.id] }));
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
function attachEstimates(transactions: readonly LifecycleTransaction[], simulation: LifecycleSimulation): LifecycleTransaction[] {
|
|
@@ -149,7 +154,7 @@ function attachEstimates(transactions: readonly LifecycleTransaction[], simulati
|
|
|
149
154
|
admission: estimate === undefined ? undefined : { admitted: simulation.admitted, confidence: simulation.confidence, executionProof: simulation.executionProof, protocolFit: simulation.protocolFit, transportPreflight: simulation.transportPreflight, blockNumber: simulation.blockNumber, blockHash: simulation.blockHash, account: simulation.account, chainId: simulation.chainId, reason: simulation.reason, limits: simulation.limits } };
|
|
150
155
|
});
|
|
151
156
|
}
|
|
152
|
-
type ReviewedNextTransaction = Pick<LifecycleTransaction, "id" | "kind" | "chainId" | "from" | "to" | "data" | "value"> & NextTransactionEnvelope;
|
|
157
|
+
type ReviewedNextTransaction = Pick<LifecycleTransaction, "id" | "kind" | "chainId" | "from" | "to" | "data" | "value" | "nonce"> & NextTransactionEnvelope;
|
|
153
158
|
const reviewedTransactionKinds = ["approve-reset", "approve", "atomic", "begin", "prepare", "activate", "cancel"] as const;
|
|
154
159
|
|
|
155
160
|
function readReviewedNextTransaction(transaction: LifecycleTransaction | undefined): ReviewedNextTransaction | undefined {
|
|
@@ -162,7 +167,8 @@ function readReviewedNextTransaction(transaction: LifecycleTransaction | undefin
|
|
|
162
167
|
typeof transaction.data !== "string" || !/^0x(?:[\da-fA-F]{2})*$/.test(transaction.data) ||
|
|
163
168
|
typeof transaction.value !== "bigint" || transaction.value < 0n || transaction.value >= 1n << 256n ||
|
|
164
169
|
typeof transaction.gas !== "bigint" || transaction.gas <= 0n || transaction.gas >= 1n << 64n ||
|
|
165
|
-
typeof transaction.gasPrice !== "bigint" || transaction.gasPrice < 0n || transaction.gasPrice >= 1n << 256n
|
|
170
|
+
typeof transaction.gasPrice !== "bigint" || transaction.gasPrice < 0n || transaction.gasPrice >= 1n << 256n ||
|
|
171
|
+
transaction.nonce !== undefined && (typeof transaction.nonce !== "bigint" || transaction.nonce < 0n || transaction.nonce >= 1n << 64n)) {
|
|
166
172
|
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");
|
|
167
173
|
}
|
|
168
174
|
return transaction as ReviewedNextTransaction;
|
|
@@ -171,11 +177,13 @@ function readReviewedNextTransaction(transaction: LifecycleTransaction | undefin
|
|
|
171
177
|
function matchesReviewedNextTransaction(next: LifecycleTransaction | ReviewedNextTransaction | undefined, reviewed: ReviewedNextTransaction): boolean {
|
|
172
178
|
return next !== undefined && next.id === reviewed.id && next.kind === reviewed.kind && next.chainId === reviewed.chainId &&
|
|
173
179
|
next.from.toLowerCase() === reviewed.from.toLowerCase() && next.to.toLowerCase() === reviewed.to.toLowerCase() &&
|
|
174
|
-
next.data.toLowerCase() === reviewed.data.toLowerCase() && next.value === reviewed.value
|
|
180
|
+
next.data.toLowerCase() === reviewed.data.toLowerCase() && next.value === reviewed.value &&
|
|
181
|
+
(reviewed.nonce === undefined || next.nonce === reviewed.nonce);
|
|
175
182
|
}
|
|
176
183
|
|
|
177
184
|
function assertReviewedTransactionUnchanged(options: BuildNextTransactionOptions, reviewed: ReviewedNextTransaction | undefined, next: LifecycleTransaction): void {
|
|
178
|
-
if (options.planned.
|
|
185
|
+
if (options.planned.ufg !== undefined) assertUfgLaunchAccount(options.planned.plan, options.planned.account);
|
|
186
|
+
if (options.planned.account.toLowerCase() !== next.from.toLowerCase() || options.planned.ufg === undefined && options.planned.plan.creator.toLowerCase() !== next.from.toLowerCase()) {
|
|
179
187
|
throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account changed during fresh execution or submission proof");
|
|
180
188
|
}
|
|
181
189
|
if (options.planned.chainId !== BigInt(next.chainId) || options.planned.plan.chainId !== BigInt(next.chainId)) {
|
|
@@ -202,6 +210,7 @@ async function readTokenFactoryBinding(client: LifecycleRpcClient, orchestrator:
|
|
|
202
210
|
|
|
203
211
|
function assertPlannedDeploymentBindings(stored: PlannedLaunch, current: PlannedLaunch): void {
|
|
204
212
|
if (hashLaunchPlan(stored.plan).toLowerCase() !== stored.planHash.toLowerCase() || current.planHash.toLowerCase() !== stored.planHash.toLowerCase() || current.launchId.toLowerCase() !== stored.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its finalized commitment");
|
|
213
|
+
if (stored.ufg === undefined ? current.ufg !== undefined : current.ufg === undefined || hashUfgLaunchConfig(stored.ufg.config) !== stored.ufg.configHash || current.ufg.configHash !== stored.ufg.configHash) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored UFG route or treasury config no longer matches its finalized commitment");
|
|
205
214
|
if (current.predictedToken.toLowerCase() !== stored.predictedToken.toLowerCase() || current.tokenFactory.toLowerCase() !== stored.tokenFactory.toLowerCase() || current.tokenFactoryCodeHash.toLowerCase() !== stored.tokenFactoryCodeHash.toLowerCase()) throw new LifecyclePlanningError("TOKEN_FACTORY_BINDING", "Final token prediction or its immutable factory code binding changed before execution");
|
|
206
215
|
if (!current.simulation.admitted || current.progress.canonical.phase === LifecyclePhase.Active || current.progress.canonical.phase === LifecyclePhase.Cancelled) return;
|
|
207
216
|
if (stored.hookDeployments.length !== current.hookDeployments.length || stored.hookDeployments.some((deployment, index) => {
|
|
@@ -218,9 +227,11 @@ function validatePlanningIntent(options: PlanLaunchOptions): ValidatedPlanShape
|
|
|
218
227
|
const { plan, account, mode } = options;
|
|
219
228
|
assertPlanningActive(options.signal);
|
|
220
229
|
if (mode !== "atomic" && mode !== "staged") throw new LifecyclePlanningError("EXPLICIT_MODE_REQUIRED", "Choose atomic or staged explicitly; staged is never silently substituted");
|
|
221
|
-
if (
|
|
230
|
+
if (options.ufg !== undefined) assertUfgLaunchAccount(plan, account);
|
|
231
|
+
else if (account.toLowerCase() !== plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
|
|
222
232
|
if (plan.chainId > BigInt(Number.MAX_SAFE_INTEGER) || plan.chainId <= 0n) throw new LifecyclePlanningError("CHAIN_MISMATCH", "Chain ID must be a positive exact JavaScript integer");
|
|
223
|
-
|
|
233
|
+
const shape = validatePlanShape(plan);
|
|
234
|
+
return options.ufg === undefined ? shape : { ...shape, ufgConfigHash: hashUfgLaunchConfig(options.ufg.config) };
|
|
224
235
|
}
|
|
225
236
|
|
|
226
237
|
function validatePlanTopology(options: PlanLaunchOptions, marketConfigs: ValidatedPlanShape["marketConfigs"], token: Address): void {
|
|
@@ -256,22 +267,32 @@ function validatePlanTopology(options: PlanLaunchOptions, marketConfigs: Validat
|
|
|
256
267
|
function assertFinalPlan(planned: PlannedLaunch, signal?: AbortSignal): void {
|
|
257
268
|
assertPlanningActive(signal);
|
|
258
269
|
if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed during asynchronous planning; previous reads and simulation are invalid");
|
|
270
|
+
if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash) throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed during asynchronous planning");
|
|
259
271
|
}
|
|
260
272
|
|
|
261
273
|
/** One owner constructs and mines, optionally measures actual ordered buy outputs,
|
|
262
274
|
* then proves only the final protected commitment. No cached execution certificates. */
|
|
263
275
|
export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifecycleLaunchOptions): Promise<PlannedLaunch> {
|
|
264
|
-
const { commitment } = validatePlanningIntent(options);
|
|
276
|
+
const { commitment, ufgConfigHash } = validatePlanningIntent(options);
|
|
265
277
|
if (options.buySlippageBps !== undefined) validateBuySlippageBps(options.buySlippageBps);
|
|
278
|
+
const fixed = options.fixedFinalEnvelope;
|
|
279
|
+
if (fixed !== undefined && (fixed === null || typeof fixed !== "object" || options.mode !== "atomic" || typeof fixed.gas !== "bigint" || fixed.gas <= 0n || fixed.gas >= 1n << 64n ||
|
|
280
|
+
fixed.gasPrice !== undefined && (typeof fixed.gasPrice !== "bigint" || fixed.gasPrice < 0n || fixed.gasPrice >= 1n << 256n))) {
|
|
281
|
+
throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Fixed final envelope requires atomic mode, exact positive uint64 gas and optional uint256 price");
|
|
282
|
+
}
|
|
283
|
+
const heldEnvelope = fixed === undefined ? undefined : Object.freeze({ ...fixed });
|
|
266
284
|
const needsMining = options.plan.markets.some((market) => isPoolBoundV4ConfigVersion(market.configVersion));
|
|
267
285
|
const needsProtection = options.buySlippageBps !== undefined && options.plan.buys.length !== 0;
|
|
268
|
-
if (!needsMining && !needsProtection) return planLaunch(options);
|
|
286
|
+
if (!needsMining && !needsProtection && fixed === undefined) return planLaunch(options);
|
|
269
287
|
const draft = parseLaunchPlan(serializeLaunchPlan(options.plan));
|
|
270
288
|
const assertDraft = () => {
|
|
271
289
|
assertPlanningActive(options.signal);
|
|
272
290
|
if (hashLaunchPlan(options.plan).toLowerCase() !== commitment.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed during asynchronous preparation; previous construction is invalid");
|
|
291
|
+
if ((options.ufg === undefined ? undefined : hashUfgLaunchConfig(options.ufg.config)) !== ufgConfigHash) throw new LifecyclePlanningError("PLAN_MUTATED", "UFG config changed during asynchronous preparation");
|
|
292
|
+
if (fixed === undefined ? options.fixedFinalEnvelope !== undefined : options.fixedFinalEnvelope !== fixed ||
|
|
293
|
+
fixed.gas !== heldEnvelope!.gas || fixed.gasPrice !== heldEnvelope!.gasPrice) throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Fixed final envelope changed during preparation");
|
|
273
294
|
};
|
|
274
|
-
|
|
295
|
+
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
275
296
|
const [block, chainId] = await Promise.all([readLifecycleBlock(client), readLifecycleChainId(client)]);
|
|
276
297
|
if (chainId !== draft.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the economic plan being finalized");
|
|
277
298
|
const metadata = needsMining ? await Promise.all(draft.markets.map((market, marketIndex) => isPoolBoundV4ConfigVersion(market.configVersion)
|
|
@@ -296,13 +317,19 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
|
|
|
296
317
|
// start at a fresh post-mining snapshot and share it through final protection.
|
|
297
318
|
const executionBlock = needsMining ? await readLifecycleBlock(client) : block;
|
|
298
319
|
assertDraft();
|
|
320
|
+
const price = heldEnvelope === undefined ? undefined : heldEnvelope.gasPrice ?? rpcQuantity(await readLifecycleGasPrice(client), "gas price");
|
|
321
|
+
const baseFeeEnvelope = (executionBlock.baseFeePerGas ?? 0n) * 2n;
|
|
322
|
+
const finalEnvelope = heldEnvelope === undefined ? undefined : Object.freeze({
|
|
323
|
+
gas: heldEnvelope.gas,
|
|
324
|
+
gasPrice: heldEnvelope.gasPrice ?? (price! > baseFeeEnvelope ? price! : baseFeeEnvelope),
|
|
325
|
+
});
|
|
299
326
|
let plan: LaunchPlanV1 = { ...draft, markets };
|
|
300
327
|
let ownedProgress: CanonicalLaunchProgress | undefined;
|
|
301
328
|
if (needsProtection) {
|
|
302
329
|
// A diagnostic minimum is never returned as an admitted or executable plan.
|
|
303
330
|
// Price guards, funding minima, recipients and all ordered budgets stay exact.
|
|
304
331
|
const diagnosticPlan = { ...plan, buys: plan.buys.map((buy) => ({ ...buy, minTokenOut: 1n })) };
|
|
305
|
-
const diagnostic = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan: diagnosticPlan }, client, executionBlock, undefined, true));
|
|
332
|
+
const diagnostic = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan: diagnosticPlan }, client, executionBlock, undefined, true, undefined, finalEnvelope));
|
|
306
333
|
assertDraft();
|
|
307
334
|
if (diagnostic.progress.canonical.phase !== LifecyclePhase.None || diagnostic.progress.head.phase !== LifecyclePhase.None) throw new LifecyclePlanningError("LAUNCH_ALREADY_STARTED", "Buy protection cannot replace an already committed launch plan");
|
|
308
335
|
plan = protectOrderedBuyMinimums(diagnostic, options.buySlippageBps!);
|
|
@@ -310,7 +337,7 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
|
|
|
310
337
|
// state. No onchain commitment existed at this exact snapshot to replace.
|
|
311
338
|
ownedProgress = { ...diagnostic.progress, planHash: hashLaunchPlan(plan) };
|
|
312
339
|
}
|
|
313
|
-
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, executionBlock, ownedProgress));
|
|
340
|
+
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, executionBlock, ownedProgress, false, undefined, finalEnvelope));
|
|
314
341
|
assertDraft();
|
|
315
342
|
assertFinalPlan(planned, options.signal);
|
|
316
343
|
if (planned.progress.canonical.phase !== LifecyclePhase.Active && planned.progress.canonical.phase !== LifecyclePhase.Cancelled) {
|
|
@@ -322,20 +349,25 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
|
|
|
322
349
|
if (needsProtection && !planned.simulation.admitted) throw new LifecyclePlanningError("PROTECTED_PLAN_NOT_ADMITTED", planned.simulation.reason ?? "The final protected launch did not establish exact execution proof", planned.simulation);
|
|
323
350
|
return planned;
|
|
324
351
|
});
|
|
352
|
+
assertDraft();
|
|
353
|
+
assertFinalPlan(result, options.signal);
|
|
354
|
+
return result;
|
|
325
355
|
}
|
|
326
356
|
|
|
327
357
|
/** Independently fresh current-state admission of an already finalized commitment. No broadcasts. */
|
|
328
358
|
export async function planLaunch(options: PlanLaunchOptions): Promise<PlannedLaunch> {
|
|
329
|
-
|
|
359
|
+
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
330
360
|
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads(options, client));
|
|
331
361
|
assertFinalPlan(planned, options.signal);
|
|
332
362
|
return planned;
|
|
333
363
|
});
|
|
364
|
+
assertFinalPlan(result, options.signal);
|
|
365
|
+
return result;
|
|
334
366
|
}
|
|
335
367
|
|
|
336
|
-
async function planLaunchWithReads(options: PlanLaunchOptions, client: LifecycleRpcClient, pinnedBlock?: LifecycleBlock, ownedProgress?: CanonicalLaunchProgress, diagnostic = false, reviewed?: ReviewedNextTransaction): Promise<PlannedLaunch> {
|
|
337
|
-
const { plan, account, mode } = options;
|
|
338
|
-
const { commitment: planHash, marketConfigs } = validatePlanningIntent(options);
|
|
368
|
+
async function planLaunchWithReads(options: PlanLaunchOptions, client: LifecycleRpcClient, pinnedBlock?: LifecycleBlock, ownedProgress?: CanonicalLaunchProgress, diagnostic = false, reviewed?: ReviewedNextTransaction, fixedFinalEnvelope?: NextTransactionEnvelope): Promise<PlannedLaunch> {
|
|
369
|
+
const { plan, account, mode, ufg } = options;
|
|
370
|
+
const { commitment: planHash, marketConfigs, ufgConfigHash } = validatePlanningIntent(options);
|
|
339
371
|
const launchId = hashLaunchIdentity(plan);
|
|
340
372
|
const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
|
|
341
373
|
pinnedBlock ?? readLifecycleBlock(client), readLifecycleChainId(client),
|
|
@@ -355,8 +387,13 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
355
387
|
preset ?? readTokenFactoryBinding(client, plan.orchestrator, block),
|
|
356
388
|
resolveLifecycleLimits({ client, account, orchestrator: plan.orchestrator, block, chainId: plan.chainId }, options.limits),
|
|
357
389
|
lifecycleStage(client, "plan.progress", async () => {
|
|
358
|
-
const predictedToken = await
|
|
359
|
-
|
|
390
|
+
const [predictedToken] = await Promise.all([
|
|
391
|
+
readLifecycleTokenAtBlock(client, plan, block),
|
|
392
|
+
ownedProgress === undefined ? readLifecycleHeadProgress(client, plan, account, block) : undefined,
|
|
393
|
+
]);
|
|
394
|
+
const identity = { plan, planHash, launchId, predictedToken, account, mode, confirmations: options.confirmations ?? 1,
|
|
395
|
+
...(ufg === undefined ? {} : { ufg: { config: ufg.config, configHash: ufgConfigHash! } }) };
|
|
396
|
+
if (ufg !== undefined) validateUfgLaunchPlan(plan, ufg.config, predictedToken);
|
|
360
397
|
const progress = ownedProgress ?? await readLaunchProgress({ client, planned: identity, receipts: options.receipts, signal: options.signal }, block);
|
|
361
398
|
if (ownedProgress !== undefined && progress.token.toLowerCase() !== predictedToken.toLowerCase()) throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed after its canonical progress was read");
|
|
362
399
|
return { identity, progress };
|
|
@@ -387,7 +424,7 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
387
424
|
// immutable onchain commitments. Waiting and terminal launches returned above.
|
|
388
425
|
if (progress.canonical.phase === LifecyclePhase.None && progress.head.phase === LifecyclePhase.None) validatePlanTopology(options, marketConfigs, identity.predictedToken);
|
|
389
426
|
const [funding, metadata] = await lifecycleStage(client, "plan.inputs", () => Promise.all([
|
|
390
|
-
progress.canonical.phase === LifecyclePhase.None ? readFunding(client, plan, block) : { prerequisites: [], approvals: [] },
|
|
427
|
+
progress.canonical.phase === LifecyclePhase.None ? readFunding(client, plan, block, ufg === undefined ? plan.creator : account, ufg !== undefined) : { prerequisites: [], approvals: [] },
|
|
391
428
|
readPlanMetadata(client, plan, identity.predictedToken, block),
|
|
392
429
|
]));
|
|
393
430
|
assertFinalPlan(planned, options.signal);
|
|
@@ -405,7 +442,10 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
405
442
|
if (start !== prepared || !Number.isSafeInteger(count) || count <= 0 || start + count > plan.markets.length) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation differs from canonical unfinished progress");
|
|
406
443
|
batches = [...(prepared > 0 ? [prepared] : []), count, ...(start + count < plan.markets.length ? [plan.markets.length - start - count] : [])];
|
|
407
444
|
}
|
|
408
|
-
let transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches);
|
|
445
|
+
let transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg);
|
|
446
|
+
if (fixedFinalEnvelope !== undefined && (transactions.length !== 1 || transactions[0]!.kind !== "atomic")) {
|
|
447
|
+
throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Fixed final envelope requires one immediate atomic launch with no preceding approvals");
|
|
448
|
+
}
|
|
409
449
|
if (reviewed !== undefined) {
|
|
410
450
|
if (!matchesReviewedNextTransaction(transactions[0], reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
|
|
411
451
|
transactions = transactions.slice(0, 1);
|
|
@@ -414,9 +454,10 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
414
454
|
await assertLifecycleBlock(client, block, plan.chainId);
|
|
415
455
|
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) };
|
|
416
456
|
}
|
|
417
|
-
const
|
|
457
|
+
const envelope = diagnostic ? undefined : reviewed ?? fixedFinalEnvelope;
|
|
458
|
+
const preparedSimulation = prepareLaunchSimulation({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, block, true, envelope, fixedFinalEnvelope?.gasPrice);
|
|
418
459
|
try {
|
|
419
|
-
const execute = diagnostic ? preparedSimulation.measure : (candidate: readonly LifecycleTransaction[]) => preparedSimulation(candidate,
|
|
460
|
+
const execute = diagnostic ? preparedSimulation.measure : (candidate: readonly LifecycleTransaction[]) => preparedSimulation(candidate, envelope);
|
|
420
461
|
let simulation = await execute(transactions);
|
|
421
462
|
assertFinalPlan(planned, options.signal);
|
|
422
463
|
if (mode === "staged") {
|
|
@@ -432,7 +473,7 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
432
473
|
const batchIndex = batches.findIndex((batch) => { const matches = first === failedTransaction?.marketStart; first += batch; return matches; });
|
|
433
474
|
const left = Math.ceil(count / 2);
|
|
434
475
|
batches.splice(batchIndex, 1, left, count - left);
|
|
435
|
-
transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches);
|
|
476
|
+
transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg);
|
|
436
477
|
simulation = await execute(transactions);
|
|
437
478
|
assertFinalPlan(planned, options.signal);
|
|
438
479
|
}
|
|
@@ -462,6 +503,7 @@ async function preflightNextTransaction(options: BuildNextTransactionOptions, ne
|
|
|
462
503
|
if (rpcQuantity(await lifecycleRpc(submissionClient, "eth_chainId"), "submission chain ID") !== simulation.chainId) throw new Error("Active submission RPC chain differs from the reviewed transaction");
|
|
463
504
|
const estimated = rpcQuantity(await lifecycleRpc(submissionClient, "eth_estimateGas", [{
|
|
464
505
|
from: next.from, to: next.to, data: next.data, value: toHex(next.value), gas: toHex(next.gas), gasPrice: toHex(next.gasPrice),
|
|
506
|
+
...(next.nonce === undefined ? {} : { nonce: toHex(next.nonce) }),
|
|
465
507
|
}, "latest"]), "submission gas estimate");
|
|
466
508
|
if (estimated <= 0n || estimated > next.gas) throw new Error("Submission RPC gas estimate exceeds the exact reviewed envelope or is zero");
|
|
467
509
|
if (rpcQuantity(await lifecycleRpc(submissionClient, "eth_chainId"), "submission chain ID") !== simulation.chainId) throw new Error("Active submission RPC chain changed during preflight");
|
|
@@ -471,12 +513,6 @@ async function preflightNextTransaction(options: BuildNextTransactionOptions, ne
|
|
|
471
513
|
const reason = `Exact next-transaction submission preflight failed: ${lifecycleFailureReason(failure)}`;
|
|
472
514
|
throw new LifecyclePlanningError("SUBMISSION_PREFLIGHT_FAILED", reason, { ...simulation, transportPreflight: "failed", reason });
|
|
473
515
|
}
|
|
474
|
-
const [canonical, chainId] = await Promise.all([
|
|
475
|
-
readLifecycleBlock(options.client, toHex(simulation.blockNumber)),
|
|
476
|
-
lifecycleRpc(options.client, "eth_chainId"),
|
|
477
|
-
]);
|
|
478
|
-
if (canonical.hash.toLowerCase() !== simulation.blockHash.toLowerCase()) throw new LifecyclePlanningError("STATE_REORGED", "Pinned launch proof changed during submission preflight; rebuild from canonical progress");
|
|
479
|
-
if (rpcQuantity(chainId, "chain ID") !== simulation.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "Source RPC chain changed during submission preflight");
|
|
480
516
|
if (next.admission === undefined) throw new LifecyclePlanningError("MISSING_GAS_PROOF", "Next transaction omitted its exact simulation admission", simulation);
|
|
481
517
|
assertPlanningActive(options.signal);
|
|
482
518
|
assertReviewedTransactionUnchanged(options, reviewed, next);
|
|
@@ -490,9 +526,17 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
|
|
|
490
526
|
// Snapshot only the reviewed wire fields; caller proofs and commands are never
|
|
491
527
|
// reused, and final checks compare the mutable input with this one local copy.
|
|
492
528
|
const reviewed = supplied === undefined ? undefined : Object.freeze({ id: supplied.id, kind: supplied.kind, chainId: supplied.chainId,
|
|
493
|
-
from: supplied.from, to: supplied.to, data: supplied.data, value: supplied.value, gas: supplied.gas, gasPrice: supplied.gasPrice });
|
|
494
|
-
|
|
529
|
+
from: supplied.from, to: supplied.to, data: supplied.data, value: supplied.value, gas: supplied.gas, gasPrice: supplied.gasPrice, nonce: supplied.nonce });
|
|
530
|
+
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
495
531
|
const block = await readLifecycleBlock(client);
|
|
532
|
+
const limitSource = options.limits === undefined ? options.planned.limits : options.limits;
|
|
533
|
+
if (getKnownLifecycleDeployment({ chainId: options.planned.chainId, orchestrator: options.planned.plan.orchestrator }) !== undefined && typeof limitSource !== "function") {
|
|
534
|
+
await Promise.all([
|
|
535
|
+
readLifecycleTokenAtBlock(client, options.planned.plan, block),
|
|
536
|
+
readLifecycleHeadProgress(client, options.planned.plan, options.planned.account, block),
|
|
537
|
+
resolveLifecycleLimits({ client, account: options.planned.account, orchestrator: options.planned.plan.orchestrator, block, chainId: options.planned.chainId }, limitSource),
|
|
538
|
+
]);
|
|
539
|
+
}
|
|
496
540
|
const progress = await readLaunchProgress({ ...options, client }, block);
|
|
497
541
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) {
|
|
498
542
|
// Only the confirmed canonical terminal phase is terminal; a head-only
|
|
@@ -507,7 +551,10 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
|
|
|
507
551
|
if (options.action === "cancel") {
|
|
508
552
|
if (progress.canonical.phase === LifecyclePhase.None) throw new LifecyclePlanningError("NOT_STARTED", "An unstarted plan has no launch escrow to cancel");
|
|
509
553
|
const { planned } = options;
|
|
510
|
-
|
|
554
|
+
assertFinalPlan(planned, options.signal);
|
|
555
|
+
const transaction: LifecycleTransaction = planned.ufg !== undefined ? buildUfgCancelTransaction({ plan: planned.plan, preparedMarkets: progress.canonical.preparedMarkets })
|
|
556
|
+
: { 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 }] };
|
|
557
|
+
transaction.nonce = progress.pendingAccountNonce;
|
|
511
558
|
if (reviewed !== undefined && !matchesReviewedNextTransaction(transaction, reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical cancellation");
|
|
512
559
|
const simulation = await simulateLaunchTransactions({ client, planned, limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork, signal: options.signal }, [transaction], block, reviewed);
|
|
513
560
|
if (!simulation.admitted) throw new LifecyclePlanningError("CANCEL_NOT_ADMITTED", simulation.reason ?? "Cancellation could not be proved against current canonical state", simulation);
|
|
@@ -516,7 +563,7 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
|
|
|
516
563
|
return preflightNextTransaction(options, next, simulation, reviewed);
|
|
517
564
|
}
|
|
518
565
|
const refreshed = await lifecycleStage(client, "plan", () => planLaunchWithReads({ client, plan: options.planned.plan, account: options.planned.account, mode: options.planned.mode, limits: options.limits === undefined ? options.planned.limits : options.limits, fork: options.fork, receipts: options.receipts, confirmations: options.confirmations ?? options.planned.confirmations, signal: options.signal,
|
|
519
|
-
allowTopologyGaps: options.planned.allowTopologyGaps, allowMissingPermanentTail: options.planned.allowMissingPermanentTail }, client, block, progress, false, reviewed));
|
|
566
|
+
ufg: options.planned.ufg, allowTopologyGaps: options.planned.allowTopologyGaps, allowMissingPermanentTail: options.planned.allowMissingPermanentTail }, client, block, progress, false, reviewed));
|
|
520
567
|
assertFinalPlan(refreshed, options.signal);
|
|
521
568
|
if (!refreshed.simulation.admitted) throw new LifecyclePlanningError("PLAN_NOT_ADMITTED", refreshed.simulation.reason ?? "Remaining exact launch sequence is not admitted", refreshed.simulation);
|
|
522
569
|
assertPlannedDeploymentBindings(options.planned, refreshed);
|
|
@@ -524,6 +571,9 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
|
|
|
524
571
|
if (next === undefined) return undefined;
|
|
525
572
|
return preflightNextTransaction(options, next, refreshed.simulation, reviewed);
|
|
526
573
|
});
|
|
574
|
+
assertFinalPlan(options.planned, options.signal);
|
|
575
|
+
if (result !== undefined) assertReviewedTransactionUnchanged(options, reviewed, result);
|
|
576
|
+
return result;
|
|
527
577
|
}
|
|
528
578
|
|
|
529
579
|
/** Re-simulates only canonical unfinished work with the same submitted receipt
|
|
@@ -535,7 +585,7 @@ export async function simulateLaunchPlan(options: SimulateLaunchPlanOptions): Pr
|
|
|
535
585
|
client, plan: planned.plan, account: planned.account, mode: planned.mode,
|
|
536
586
|
limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork,
|
|
537
587
|
receipts: options.receipts, confirmations: options.confirmations ?? planned.confirmations, signal: options.signal,
|
|
538
|
-
allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
|
|
588
|
+
ufg: planned.ufg, allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
|
|
539
589
|
});
|
|
540
590
|
assertPlannedDeploymentBindings(planned, current);
|
|
541
591
|
return current.simulation;
|
package/src/lifecycle/presets.ts
CHANGED
|
@@ -8,6 +8,9 @@ import { dynamicFeePoolHookV1CreationCode } from "./deployments/dynamic-fee-pool
|
|
|
8
8
|
import { hmmFixedEnvelope, hmmFixedAdapter, hmmFixedRegistration, hmmFixedCreationCode } from "./deployments/hmm-fixed-pool-hook-v2-20261017.js";
|
|
9
9
|
import { hmmDynamicEnvelope, hmmDynamicAdapter, hmmDynamicRegistration, hmmDynamicCreationCode } from "./deployments/hmm-dynamic-pool-hook-v2-20261017.js";
|
|
10
10
|
import { HMM_FIXED_PROFILE_ID, HMM_DYNAMIC_PROFILE_ID, isHmmProfile } from "./hmm.js";
|
|
11
|
+
import { ufgFixedEnvelope, ufgFixedAdapter, ufgFixedRegistration } from "./deployments/ufg-fixed-pool-20261021.js";
|
|
12
|
+
import { ufgDynamicEnvelope, ufgDynamicAdapter, ufgDynamicRegistration } from "./deployments/ufg-dynamic-pool-20261021.js";
|
|
13
|
+
import { UFG_FIXED_PROFILE_ID, UFG_DYNAMIC_PROFILE_ID } from "./ufg-config.js";
|
|
11
14
|
|
|
12
15
|
export enum KnownLifecycleProfile {
|
|
13
16
|
Abyss0 = "abyss-0",
|
|
@@ -18,6 +21,8 @@ export enum KnownLifecycleProfile {
|
|
|
18
21
|
V4DynamicFeePool = "v4-dynamic-fee-pool",
|
|
19
22
|
V4HmmFixedPool = "v4-hmm-fixed-pool",
|
|
20
23
|
V4HmmDynamicPool = "v4-hmm-dynamic-pool",
|
|
24
|
+
V4UfgFixedPool = "v4-ufg-fixed-pool",
|
|
25
|
+
V4UfgDynamicPool = "v4-ufg-dynamic-pool",
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
export enum KnownLifecycleHook {
|
|
@@ -294,7 +299,7 @@ export function hmmV2ForwardingReserve(plan: LaunchPlanV1): bigint {
|
|
|
294
299
|
return count === 0 ? 0n : HMM_V2_FORWARDING_BASE_GAS + HMM_V2_FORWARDING_EXTRA_MARKET_GAS * BigInt(count - 1);
|
|
295
300
|
}
|
|
296
301
|
|
|
297
|
-
function
|
|
302
|
+
function poolBoundPreset(key: KnownLifecycleProfile, id: Hex, envelope: LaunchEnvelopeV2,
|
|
298
303
|
adapter: AdapterRegistrationV1, registration: ProfileRegistrationV1, creationCode: Hex): LifecycleProfilePreset {
|
|
299
304
|
return Object.freeze({
|
|
300
305
|
key, supported: true,
|
|
@@ -327,8 +332,10 @@ const knownDeployment: LifecycleDeploymentPreset = Object.freeze({
|
|
|
327
332
|
abyssPreset(KnownLifecycleProfile.Abyss3, "0xe1bd95e086469e6afd9791ccc957e4d22b735012cb3db4a747f703673c62c306"),
|
|
328
333
|
fixedFeePoolPreset,
|
|
329
334
|
dynamicFeePoolPreset,
|
|
330
|
-
|
|
331
|
-
|
|
335
|
+
poolBoundPreset(KnownLifecycleProfile.V4HmmFixedPool, HMM_FIXED_PROFILE_ID, hmmFixedEnvelope, hmmFixedAdapter, hmmFixedRegistration, hmmFixedCreationCode),
|
|
336
|
+
poolBoundPreset(KnownLifecycleProfile.V4HmmDynamicPool, HMM_DYNAMIC_PROFILE_ID, hmmDynamicEnvelope, hmmDynamicAdapter, hmmDynamicRegistration, hmmDynamicCreationCode),
|
|
337
|
+
poolBoundPreset(KnownLifecycleProfile.V4UfgFixedPool, UFG_FIXED_PROFILE_ID, ufgFixedEnvelope, ufgFixedAdapter, ufgFixedRegistration, fixedFeePoolHookV1CreationCode),
|
|
338
|
+
poolBoundPreset(KnownLifecycleProfile.V4UfgDynamicPool, UFG_DYNAMIC_PROFILE_ID, ufgDynamicEnvelope, ufgDynamicAdapter, ufgDynamicRegistration, dynamicFeePoolHookV1CreationCode),
|
|
332
339
|
]),
|
|
333
340
|
provenance: Object.freeze({ sourceCommit: "e174ce695c7010f50ca9211b07b9f178c990beac", deploymentBlock: 82880546n }),
|
|
334
341
|
});
|
|
@@ -358,7 +365,7 @@ export function getKnownLifecycleProfile(options: KnownLifecycleDeploymentIdenti
|
|
|
358
365
|
return undefined;
|
|
359
366
|
}
|
|
360
367
|
|
|
361
|
-
/** Stable, frozen order: Abyss variants
|
|
368
|
+
/** Stable, frozen order: Abyss variants, fixed/dynamic V4, HMM, then UFG fixed/dynamic. */
|
|
362
369
|
export function listKnownLifecycleProfiles(options: KnownLifecycleDeploymentIdentity): readonly LifecycleProfilePreset[] {
|
|
363
370
|
return getKnownLifecycleDeployment(options)?.profiles ?? noProfiles;
|
|
364
371
|
}
|