@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.
Files changed (78) hide show
  1. package/README.md +96 -79
  2. package/dist/lifecycle/abi.d.ts +1 -0
  3. package/dist/lifecycle/abi.d.ts.map +1 -1
  4. package/dist/lifecycle/abi.js +1 -0
  5. package/dist/lifecycle/abi.js.map +1 -1
  6. package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.d.ts +71 -0
  7. package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.d.ts.map +1 -0
  8. package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.js +73 -0
  9. package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.js.map +1 -0
  10. package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.d.ts +71 -0
  11. package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.d.ts.map +1 -0
  12. package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.js +73 -0
  13. package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.js.map +1 -0
  14. package/dist/lifecycle/index.d.ts +1 -0
  15. package/dist/lifecycle/index.d.ts.map +1 -1
  16. package/dist/lifecycle/index.js +1 -0
  17. package/dist/lifecycle/index.js.map +1 -1
  18. package/dist/lifecycle/markets.d.ts +3 -1
  19. package/dist/lifecycle/markets.d.ts.map +1 -1
  20. package/dist/lifecycle/markets.js +20 -3
  21. package/dist/lifecycle/markets.js.map +1 -1
  22. package/dist/lifecycle/planner.d.ts.map +1 -1
  23. package/dist/lifecycle/planner.js +101 -46
  24. package/dist/lifecycle/planner.js.map +1 -1
  25. package/dist/lifecycle/presets.d.ts +4 -2
  26. package/dist/lifecycle/presets.d.ts.map +1 -1
  27. package/dist/lifecycle/presets.js +11 -4
  28. package/dist/lifecycle/presets.js.map +1 -1
  29. package/dist/lifecycle/progress.d.ts +3 -1
  30. package/dist/lifecycle/progress.d.ts.map +1 -1
  31. package/dist/lifecycle/progress.js +145 -59
  32. package/dist/lifecycle/progress.js.map +1 -1
  33. package/dist/lifecycle/rpc.d.ts +8 -2
  34. package/dist/lifecycle/rpc.d.ts.map +1 -1
  35. package/dist/lifecycle/rpc.js +165 -9
  36. package/dist/lifecycle/rpc.js.map +1 -1
  37. package/dist/lifecycle/simulation.d.ts +1 -1
  38. package/dist/lifecycle/simulation.d.ts.map +1 -1
  39. package/dist/lifecycle/simulation.js +15 -11
  40. package/dist/lifecycle/simulation.js.map +1 -1
  41. package/dist/lifecycle/types.d.ts +23 -1
  42. package/dist/lifecycle/types.d.ts.map +1 -1
  43. package/dist/lifecycle/types.js.map +1 -1
  44. package/dist/lifecycle/ufg-abi.d.ts +2386 -0
  45. package/dist/lifecycle/ufg-abi.d.ts.map +1 -0
  46. package/dist/lifecycle/ufg-abi.js +171 -0
  47. package/dist/lifecycle/ufg-abi.js.map +1 -0
  48. package/dist/lifecycle/ufg-config.d.ts +38 -0
  49. package/dist/lifecycle/ufg-config.d.ts.map +1 -0
  50. package/dist/lifecycle/ufg-config.js +75 -0
  51. package/dist/lifecycle/ufg-config.js.map +1 -0
  52. package/dist/lifecycle/ufg.d.ts +21 -0
  53. package/dist/lifecycle/ufg.d.ts.map +1 -0
  54. package/dist/lifecycle/ufg.js +98 -0
  55. package/dist/lifecycle/ufg.js.map +1 -0
  56. package/docs/launch-lifecycle.md +611 -1363
  57. package/docs/reference.md +332 -0
  58. package/examples/.env.example +3 -2
  59. package/examples/README.md +172 -539
  60. package/examples/launch-example-support.ts +11 -3
  61. package/examples/launch-example.ts +1 -1
  62. package/examples/launch-lifecycle-smoke.ts +1 -1
  63. package/examples/ufg-launch-config.ts +31 -0
  64. package/package.json +1 -1
  65. package/src/lifecycle/abi.ts +1 -0
  66. package/src/lifecycle/deployments/ufg-dynamic-pool-20261021.ts +74 -0
  67. package/src/lifecycle/deployments/ufg-fixed-pool-20261021.ts +74 -0
  68. package/src/lifecycle/index.ts +1 -0
  69. package/src/lifecycle/markets.ts +16 -3
  70. package/src/lifecycle/planner.ts +95 -45
  71. package/src/lifecycle/presets.ts +11 -4
  72. package/src/lifecycle/progress.ts +131 -51
  73. package/src/lifecycle/rpc.ts +158 -10
  74. package/src/lifecycle/simulation.ts +14 -10
  75. package/src/lifecycle/types.ts +15 -2
  76. package/src/lifecycle/ufg-abi.ts +173 -0
  77. package/src/lifecycle/ufg-config.ts +77 -0
  78. package/src/lifecycle/ufg.ts +86 -0
@@ -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", [plan.creator, toHex(block.number)], block), "creator native balance"), undefined] as const
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", [plan.creator], block),
90
- readLifecycleContract<bigint>(client, funding.inputAsset, lifecycleErc20Abi, "allowance", [plan.creator, escrow], block),
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: plan.creator, 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 }] });
107
- approvals.push({ id: `approve:${total.asset}`, kind: "approve", chainId: Number(plan.chainId), from: plan.creator, 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 }] });
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 commands = buildLaunchTransactions({ plan, mode, preparationBatches: mode === "staged" ? batches : undefined }).filter((transaction) =>
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.account.toLowerCase() !== next.from.toLowerCase() || options.planned.plan.creator.toLowerCase() !== next.from.toLowerCase()) {
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 (account.toLowerCase() !== plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
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
- return validatePlanShape(plan);
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
- return withLifecycleReadClient(options.client, async (client) => {
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
- return withLifecycleReadClient(options.client, async (client) => {
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 readLifecycleTokenAtBlock(client, plan, block);
359
- const identity = { plan, planHash, launchId, predictedToken, account, mode, confirmations: options.confirmations ?? 1 };
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 preparedSimulation = prepareLaunchSimulation({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, block, true, reviewed);
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, reviewed);
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
- return withLifecycleReadClient(options.client, async (client) => {
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
- const transaction: LifecycleTransaction = { 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 }] };
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;
@@ -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 hmmPreset(key: KnownLifecycleProfile, id: Hex, envelope: LaunchEnvelopeV2,
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
- hmmPreset(KnownLifecycleProfile.V4HmmFixedPool, HMM_FIXED_PROFILE_ID, hmmFixedEnvelope, hmmFixedAdapter, hmmFixedRegistration, hmmFixedCreationCode),
331
- hmmPreset(KnownLifecycleProfile.V4HmmDynamicPool, HMM_DYNAMIC_PROFILE_ID, hmmDynamicEnvelope, hmmDynamicAdapter, hmmDynamicRegistration, hmmDynamicCreationCode),
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 0 through 3, fixed/dynamic V4, then HMM fixed/dynamic. */
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
  }