@black-market/sdk 0.11.0 → 0.12.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 (66) hide show
  1. package/README.md +17 -0
  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 +42 -22
  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.map +1 -1
  30. package/dist/lifecycle/progress.js +26 -5
  31. package/dist/lifecycle/progress.js.map +1 -1
  32. package/dist/lifecycle/simulation.d.ts.map +1 -1
  33. package/dist/lifecycle/simulation.js +4 -1
  34. package/dist/lifecycle/simulation.js.map +1 -1
  35. package/dist/lifecycle/types.d.ts +11 -1
  36. package/dist/lifecycle/types.d.ts.map +1 -1
  37. package/dist/lifecycle/types.js.map +1 -1
  38. package/dist/lifecycle/ufg-abi.d.ts +2386 -0
  39. package/dist/lifecycle/ufg-abi.d.ts.map +1 -0
  40. package/dist/lifecycle/ufg-abi.js +171 -0
  41. package/dist/lifecycle/ufg-abi.js.map +1 -0
  42. package/dist/lifecycle/ufg-config.d.ts +38 -0
  43. package/dist/lifecycle/ufg-config.d.ts.map +1 -0
  44. package/dist/lifecycle/ufg-config.js +75 -0
  45. package/dist/lifecycle/ufg-config.js.map +1 -0
  46. package/dist/lifecycle/ufg.d.ts +21 -0
  47. package/dist/lifecycle/ufg.d.ts.map +1 -0
  48. package/dist/lifecycle/ufg.js +98 -0
  49. package/dist/lifecycle/ufg.js.map +1 -0
  50. package/docs/launch-lifecycle.md +138 -0
  51. package/examples/README.md +16 -2
  52. package/examples/ufg-launch-config.ts +31 -0
  53. package/package.json +1 -1
  54. package/src/lifecycle/abi.ts +1 -0
  55. package/src/lifecycle/deployments/ufg-dynamic-pool-20261021.ts +74 -0
  56. package/src/lifecycle/deployments/ufg-fixed-pool-20261021.ts +74 -0
  57. package/src/lifecycle/index.ts +1 -0
  58. package/src/lifecycle/markets.ts +16 -3
  59. package/src/lifecycle/planner.ts +37 -22
  60. package/src/lifecycle/presets.ts +11 -4
  61. package/src/lifecycle/progress.ts +22 -5
  62. package/src/lifecycle/simulation.ts +3 -1
  63. package/src/lifecycle/types.ts +6 -1
  64. package/src/lifecycle/ufg-abi.ts +173 -0
  65. package/src/lifecycle/ufg-config.ts +77 -0
  66. package/src/lifecycle/ufg.ts +86 -0
@@ -12,9 +12,12 @@ import { deriveKnownPoolBoundHookDeployment, protectOrderedBuyMinimums, validate
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,10 +135,12 @@ 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;
@@ -175,7 +180,8 @@ function matchesReviewedNextTransaction(next: LifecycleTransaction | ReviewedNex
175
180
  }
176
181
 
177
182
  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()) {
183
+ if (options.planned.ufg !== undefined) assertUfgLaunchAccount(options.planned.plan, options.planned.account);
184
+ if (options.planned.account.toLowerCase() !== next.from.toLowerCase() || options.planned.ufg === undefined && options.planned.plan.creator.toLowerCase() !== next.from.toLowerCase()) {
179
185
  throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account changed during fresh execution or submission proof");
180
186
  }
181
187
  if (options.planned.chainId !== BigInt(next.chainId) || options.planned.plan.chainId !== BigInt(next.chainId)) {
@@ -202,6 +208,7 @@ async function readTokenFactoryBinding(client: LifecycleRpcClient, orchestrator:
202
208
 
203
209
  function assertPlannedDeploymentBindings(stored: PlannedLaunch, current: PlannedLaunch): void {
204
210
  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");
211
+ 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
212
  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
213
  if (!current.simulation.admitted || current.progress.canonical.phase === LifecyclePhase.Active || current.progress.canonical.phase === LifecyclePhase.Cancelled) return;
207
214
  if (stored.hookDeployments.length !== current.hookDeployments.length || stored.hookDeployments.some((deployment, index) => {
@@ -218,9 +225,11 @@ function validatePlanningIntent(options: PlanLaunchOptions): ValidatedPlanShape
218
225
  const { plan, account, mode } = options;
219
226
  assertPlanningActive(options.signal);
220
227
  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");
228
+ if (options.ufg !== undefined) assertUfgLaunchAccount(plan, account);
229
+ else if (account.toLowerCase() !== plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
222
230
  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);
231
+ const shape = validatePlanShape(plan);
232
+ return options.ufg === undefined ? shape : { ...shape, ufgConfigHash: hashUfgLaunchConfig(options.ufg.config) };
224
233
  }
225
234
 
226
235
  function validatePlanTopology(options: PlanLaunchOptions, marketConfigs: ValidatedPlanShape["marketConfigs"], token: Address): void {
@@ -256,12 +265,13 @@ function validatePlanTopology(options: PlanLaunchOptions, marketConfigs: Validat
256
265
  function assertFinalPlan(planned: PlannedLaunch, signal?: AbortSignal): void {
257
266
  assertPlanningActive(signal);
258
267
  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");
268
+ if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash) throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed during asynchronous planning");
259
269
  }
260
270
 
261
271
  /** One owner constructs and mines, optionally measures actual ordered buy outputs,
262
272
  * then proves only the final protected commitment. No cached execution certificates. */
263
273
  export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifecycleLaunchOptions): Promise<PlannedLaunch> {
264
- const { commitment } = validatePlanningIntent(options);
274
+ const { commitment, ufgConfigHash } = validatePlanningIntent(options);
265
275
  if (options.buySlippageBps !== undefined) validateBuySlippageBps(options.buySlippageBps);
266
276
  const needsMining = options.plan.markets.some((market) => isPoolBoundV4ConfigVersion(market.configVersion));
267
277
  const needsProtection = options.buySlippageBps !== undefined && options.plan.buys.length !== 0;
@@ -270,6 +280,7 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
270
280
  const assertDraft = () => {
271
281
  assertPlanningActive(options.signal);
272
282
  if (hashLaunchPlan(options.plan).toLowerCase() !== commitment.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed during asynchronous preparation; previous construction is invalid");
283
+ if ((options.ufg === undefined ? undefined : hashUfgLaunchConfig(options.ufg.config)) !== ufgConfigHash) throw new LifecyclePlanningError("PLAN_MUTATED", "UFG config changed during asynchronous preparation");
273
284
  };
274
285
  return withLifecycleReadClient(options.client, async (client) => {
275
286
  const [block, chainId] = await Promise.all([readLifecycleBlock(client), readLifecycleChainId(client)]);
@@ -334,8 +345,8 @@ export async function planLaunch(options: PlanLaunchOptions): Promise<PlannedLau
334
345
  }
335
346
 
336
347
  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);
348
+ const { plan, account, mode, ufg } = options;
349
+ const { commitment: planHash, marketConfigs, ufgConfigHash } = validatePlanningIntent(options);
339
350
  const launchId = hashLaunchIdentity(plan);
340
351
  const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
341
352
  pinnedBlock ?? readLifecycleBlock(client), readLifecycleChainId(client),
@@ -356,7 +367,9 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
356
367
  resolveLifecycleLimits({ client, account, orchestrator: plan.orchestrator, block, chainId: plan.chainId }, options.limits),
357
368
  lifecycleStage(client, "plan.progress", async () => {
358
369
  const predictedToken = await readLifecycleTokenAtBlock(client, plan, block);
359
- const identity = { plan, planHash, launchId, predictedToken, account, mode, confirmations: options.confirmations ?? 1 };
370
+ const identity = { plan, planHash, launchId, predictedToken, account, mode, confirmations: options.confirmations ?? 1,
371
+ ...(ufg === undefined ? {} : { ufg: { config: ufg.config, configHash: ufgConfigHash! } }) };
372
+ if (ufg !== undefined) validateUfgLaunchPlan(plan, ufg.config, predictedToken);
360
373
  const progress = ownedProgress ?? await readLaunchProgress({ client, planned: identity, receipts: options.receipts, signal: options.signal }, block);
361
374
  if (ownedProgress !== undefined && progress.token.toLowerCase() !== predictedToken.toLowerCase()) throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed after its canonical progress was read");
362
375
  return { identity, progress };
@@ -387,7 +400,7 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
387
400
  // immutable onchain commitments. Waiting and terminal launches returned above.
388
401
  if (progress.canonical.phase === LifecyclePhase.None && progress.head.phase === LifecyclePhase.None) validatePlanTopology(options, marketConfigs, identity.predictedToken);
389
402
  const [funding, metadata] = await lifecycleStage(client, "plan.inputs", () => Promise.all([
390
- progress.canonical.phase === LifecyclePhase.None ? readFunding(client, plan, block) : { prerequisites: [], approvals: [] },
403
+ progress.canonical.phase === LifecyclePhase.None ? readFunding(client, plan, block, ufg === undefined ? plan.creator : account, ufg !== undefined) : { prerequisites: [], approvals: [] },
391
404
  readPlanMetadata(client, plan, identity.predictedToken, block),
392
405
  ]));
393
406
  assertFinalPlan(planned, options.signal);
@@ -405,7 +418,7 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
405
418
  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
419
  batches = [...(prepared > 0 ? [prepared] : []), count, ...(start + count < plan.markets.length ? [plan.markets.length - start - count] : [])];
407
420
  }
408
- let transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches);
421
+ let transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg);
409
422
  if (reviewed !== undefined) {
410
423
  if (!matchesReviewedNextTransaction(transactions[0], reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
411
424
  transactions = transactions.slice(0, 1);
@@ -432,7 +445,7 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
432
445
  const batchIndex = batches.findIndex((batch) => { const matches = first === failedTransaction?.marketStart; first += batch; return matches; });
433
446
  const left = Math.ceil(count / 2);
434
447
  batches.splice(batchIndex, 1, left, count - left);
435
- transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches);
448
+ transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg);
436
449
  simulation = await execute(transactions);
437
450
  assertFinalPlan(planned, options.signal);
438
451
  }
@@ -507,7 +520,9 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
507
520
  if (options.action === "cancel") {
508
521
  if (progress.canonical.phase === LifecyclePhase.None) throw new LifecyclePlanningError("NOT_STARTED", "An unstarted plan has no launch escrow to cancel");
509
522
  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 }] };
523
+ assertFinalPlan(planned, options.signal);
524
+ const transaction: LifecycleTransaction = planned.ufg !== undefined ? buildUfgCancelTransaction({ plan: planned.plan, preparedMarkets: progress.canonical.preparedMarkets })
525
+ : { 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 }] };
511
526
  if (reviewed !== undefined && !matchesReviewedNextTransaction(transaction, reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical cancellation");
512
527
  const simulation = await simulateLaunchTransactions({ client, planned, limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork, signal: options.signal }, [transaction], block, reviewed);
513
528
  if (!simulation.admitted) throw new LifecyclePlanningError("CANCEL_NOT_ADMITTED", simulation.reason ?? "Cancellation could not be proved against current canonical state", simulation);
@@ -516,7 +531,7 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
516
531
  return preflightNextTransaction(options, next, simulation, reviewed);
517
532
  }
518
533
  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));
534
+ ufg: options.planned.ufg, allowTopologyGaps: options.planned.allowTopologyGaps, allowMissingPermanentTail: options.planned.allowMissingPermanentTail }, client, block, progress, false, reviewed));
520
535
  assertFinalPlan(refreshed, options.signal);
521
536
  if (!refreshed.simulation.admitted) throw new LifecyclePlanningError("PLAN_NOT_ADMITTED", refreshed.simulation.reason ?? "Remaining exact launch sequence is not admitted", refreshed.simulation);
522
537
  assertPlannedDeploymentBindings(options.planned, refreshed);
@@ -535,7 +550,7 @@ export async function simulateLaunchPlan(options: SimulateLaunchPlanOptions): Pr
535
550
  client, plan: planned.plan, account: planned.account, mode: planned.mode,
536
551
  limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork,
537
552
  receipts: options.receipts, confirmations: options.confirmations ?? planned.confirmations, signal: options.signal,
538
- allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
553
+ ufg: planned.ufg, allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
539
554
  });
540
555
  assertPlannedDeploymentBindings(planned, current);
541
556
  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
  }
@@ -6,6 +6,8 @@ import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureRe
6
6
  import { LifecyclePlanningError, type CanonicalLaunchProgress, type LifecycleBlock, type LifecycleMarketProgress, type LifecycleProfile, type LifecycleReceiptReference, type LifecycleReceiptStatus, type LifecycleRpcClient, type PoolBoundHookDeployment, type ReadLaunchProgressOptions } from "./types.js";
7
7
  import { deriveKnownPoolBoundHookDeployment } from "./calibration.js";
8
8
  import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
9
+ import { ufgLaunchProxyV1Abi } from "./ufg-abi.js";
10
+ import { assertUfgLaunchAccount, hashUfgLaunchConfig, UFG_LAUNCH_PROXY_ADDRESS, type UfgLaunchConfig } from "./ufg-config.js";
9
11
 
10
12
  export const ABYSS_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint8,uint24,bytes32,uint160,(int24,int24,uint128,uint256)[])"));
11
13
 
@@ -460,12 +462,17 @@ async function transactionMatchesLaunch(transaction: Record<string, unknown>, op
460
462
  if (transaction.chainId !== undefined && rpcQuantity(transaction.chainId, "receipt transaction chain") !== planned.plan.chainId) throw new LifecyclePlanningError("RECEIPT_CHAIN", "Receipt transaction belongs to a different chain");
461
463
  const to = transaction.to === null ? zeroAddress : rpcHex(transaction.to, "receipt transaction target");
462
464
  const input = rpcHex(transaction.input ?? transaction.data, "receipt transaction calldata");
463
- if (to.toLowerCase() === planned.plan.orchestrator.toLowerCase()) {
465
+ const target = planned.ufg === undefined ? planned.plan.orchestrator : UFG_LAUNCH_PROXY_ADDRESS;
466
+ if (to.toLowerCase() === target.toLowerCase()) {
464
467
  try {
465
- const decoded = decodeFunctionData({ abi: launchLifecycleAbi, data: input });
468
+ const decoded = decodeFunctionData({ abi: planned.ufg === undefined ? launchLifecycleAbi : ufgLaunchProxyV1Abi, data: input });
466
469
  if (!["launchAtomic", "beginLaunch", "prepareMarkets", "activateLaunch", "cancelLaunch"].includes(decoded.functionName) || !Array.isArray(decoded.args)) return false;
467
470
  // Decoded plan layout is validated by the exact ABI, not trusted raw RPC JSON.
468
471
  const plan = decoded.args[0] as LaunchPlanV1;
472
+ if (planned.ufg !== undefined && (decoded.functionName === "launchAtomic" || decoded.functionName === "beginLaunch")) {
473
+ const config = decoded.args[decoded.functionName === "launchAtomic" ? 1 : 2] as UfgLaunchConfig;
474
+ if (hashUfgLaunchConfig(config) !== planned.ufg.configHash) return false;
475
+ }
469
476
  return hashLaunchPlan(plan).toLowerCase() === planned.planHash.toLowerCase();
470
477
  } catch { return false; }
471
478
  }
@@ -477,7 +484,7 @@ async function transactionMatchesLaunch(transaction: Record<string, unknown>, op
477
484
  if (decoded.functionName !== "approve" || rpcQuantity(transaction.value ?? "0x0", "approval native value") !== 0n) return false;
478
485
  spender = decoded.args[0];
479
486
  } catch { return false; }
480
- const escrow = await readLifecycleContract<Address>(options.client, planned.plan.orchestrator, launchLifecycleAbi, "fundingEscrow", [], block);
487
+ const escrow = planned.ufg !== undefined ? UFG_LAUNCH_PROXY_ADDRESS : await readLifecycleContract<Address>(options.client, planned.plan.orchestrator, launchLifecycleAbi, "fundingEscrow", [], block);
481
488
  return spender.toLowerCase() === escrow.toLowerCase();
482
489
  }
483
490
 
@@ -542,7 +549,10 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
542
549
  assertActive();
543
550
  const chainId = chainIdValue;
544
551
  if (chainId !== planned.plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed launch");
545
- if (planned.account.toLowerCase() !== planned.plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
552
+ if (planned.ufg !== undefined) {
553
+ assertUfgLaunchAccount(planned.plan, planned.account);
554
+ if (hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash) throw new LifecyclePlanningError("PLAN_MUTATED", "UFG config differs from its finalized commitment");
555
+ } else if (planned.account.toLowerCase() !== planned.plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
546
556
  if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its commitment");
547
557
  const [head, confirmedProgress, token, confirmedNonce, headNonce, pendingNonce] = await Promise.all([
548
558
  readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], block, planned.account),
@@ -556,10 +566,17 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
556
566
  const canonical = confirmedProgress ?? head;
557
567
  if (token.toLowerCase() !== planned.predictedToken.toLowerCase()) throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed for the committed token configuration");
558
568
  for (const progress of [canonical, head]) if (progress.phase !== LifecyclePhase.None) {
559
- if (progress.planHash.toLowerCase() !== planned.planHash.toLowerCase() || progress.launchId.toLowerCase() !== planned.launchId.toLowerCase() || progress.creator.toLowerCase() !== planned.account.toLowerCase() || progress.nonce !== planned.plan.nonce || progress.token.toLowerCase() !== token.toLowerCase() || progress.marketCount !== planned.plan.markets.length || progress.buyCount !== planned.plan.buys.length || progress.deadline !== planned.plan.deadline) throw new LifecyclePlanningError("PLAN_REPLAY", "This creator nonce is already bound to a different economic launch");
569
+ if (progress.planHash.toLowerCase() !== planned.planHash.toLowerCase() || progress.launchId.toLowerCase() !== planned.launchId.toLowerCase() || progress.creator.toLowerCase() !== planned.plan.creator.toLowerCase() || progress.nonce !== planned.plan.nonce || progress.token.toLowerCase() !== token.toLowerCase() || progress.marketCount !== planned.plan.markets.length || progress.buyCount !== planned.plan.buys.length || progress.deadline !== planned.plan.deadline) throw new LifecyclePlanningError("PLAN_REPLAY", "This creator nonce is already bound to a different economic launch");
560
570
  if (progress.mode !== (planned.mode === "atomic" ? LifecycleMode.Atomic : LifecycleMode.Staged)) throw new LifecyclePlanningError("MODE_MISMATCH", "Execution mode differs from the already recorded launch");
561
571
  if (progress.preparedMarkets > progress.marketCount || progress.phase === LifecyclePhase.Activating || (progress.phase === LifecyclePhase.Ready && progress.preparedMarkets !== progress.marketCount)) throw new LifecyclePlanningError("INVALID_PROGRESS", "Canonical progress violates the committed ordered state machine");
562
572
  }
573
+ if (planned.ufg !== undefined && head.phase !== LifecyclePhase.None) {
574
+ const [principal, configHash] = await Promise.all([
575
+ readLifecycleContract<Address>(client, UFG_LAUNCH_PROXY_ADDRESS, ufgLaunchProxyV1Abi, "principalOf", [planned.launchId], block),
576
+ readLifecycleContract<Hash>(client, UFG_LAUNCH_PROXY_ADDRESS, ufgLaunchProxyV1Abi, "launchConfigHash", [planned.launchId], block),
577
+ ]);
578
+ if (principal.toLowerCase() !== planned.account.toLowerCase() || configHash.toLowerCase() !== planned.ufg.configHash.toLowerCase()) throw new LifecyclePlanningError("PLAN_REPLAY", "UFG proxy principal or launch config differs from the stored launch");
579
+ }
563
580
  const confirmedAccountNonce = rpcQuantity(confirmedNonce, "confirmed account nonce");
564
581
  const headAccountNonce = headNonce === undefined ? confirmedAccountNonce : rpcQuantity(headNonce, "head account nonce");
565
582
  const pendingAccountNonce = rpcQuantity(pendingNonce, "pending account nonce");
@@ -6,6 +6,7 @@ import { LifecyclePlanningError, type ControlledLifecycleFork, type LifecycleBlo
6
6
  import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion } from "./markets.js";
7
7
  import { hmmV2ForwardingReserve } from "./presets.js";
8
8
  import { transactionGasEstimate } from "./transactions.js";
9
+ import { hashUfgLaunchConfig } from "./ufg-config.js";
9
10
 
10
11
  function committedPositionCount(plan: LaunchPlanV1): number {
11
12
  let count = 0;
@@ -163,7 +164,7 @@ async function simulateRpcPass(client: LifecycleRpcClient, planned: PlannedLaunc
163
164
  const buy = planned.plan.buys[buyIndex];
164
165
  if (buy === undefined || (receipt.quoteSpent[buyIndex] ?? buy.quoteAmountIn + 1n) > buy.quoteAmountIn || (receipt.tokenOut[buyIndex] ?? 0n) < buy.minTokenOut) throw new LifecyclePlanningError("POSTCONDITION_FAILED", "Simulation did not satisfy every ordered buy's budget and output minimum");
165
166
  }
166
- } else if (transaction.kind === "cancel" && !events.some((event) => event.eventName === "LaunchCancelled" && event.args.launchId.toLowerCase() === planned.launchId.toLowerCase() && event.args.creator.toLowerCase() === planned.account.toLowerCase())) throw new LifecyclePlanningError("POSTCONDITION_FAILED", "Simulation did not cancel the exact committed launch");
167
+ } else if (transaction.kind === "cancel" && !events.some((event) => event.eventName === "LaunchCancelled" && event.args.launchId.toLowerCase() === planned.launchId.toLowerCase() && event.args.creator.toLowerCase() === planned.plan.creator.toLowerCase())) throw new LifecyclePlanningError("POSTCONDITION_FAILED", "Simulation did not cancel the exact committed launch");
167
168
  else if ((transaction.kind === "approve" || transaction.kind === "approve-reset") && returnData !== "0x" && decodeFunctionResult({ abi: lifecycleErc20Abi, functionName: "approve", data: returnData }) !== true) throw new LifecyclePlanningError("POSTCONDITION_FAILED", "Funding approval returned failure");
168
169
  } catch (failure) { error = failure instanceof LifecyclePlanningError ? failure.message : "Simulation returned an invalid committed postcondition"; }
169
170
  }
@@ -346,6 +347,7 @@ export function prepareLaunchSimulation(options: SimulateLaunchPlanOptions, bloc
346
347
  if (hashLaunchPlan(plan).toLowerCase() !== planHash.toLowerCase() || hashLaunchIdentity(plan).toLowerCase() !== launchId.toLowerCase()) {
347
348
  throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed after its simulation context was bound");
348
349
  }
350
+ if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash) throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed after its simulation context was bound");
349
351
  };
350
352
  const pending = (async () => {
351
353
  if (fork !== undefined && lifecycleSourceClient(fork.client) === sourceClient) throw new LifecyclePlanningError("UNSAFE_FORK", "Source RPC must never receive controlled simulation transactions/snapshots/resets");
@@ -1,4 +1,5 @@
1
1
  import type { Address, Hash, Hex } from "viem";
2
+ import type { UfgLaunchConfig } from "./ufg-config.js";
2
3
  import type {
3
4
  AdapterRegistrationV1, AssetFundingV1, LaunchExecutionMode, LaunchPlanV1, LaunchProgressV1,
4
5
  MarketLiveStateV1, PreparedMarketV1, ProfileRegistrationV1, ProfileTopologyV1, LaunchEnvelopeV2, LifecycleDeveloperTerms,
@@ -161,6 +162,8 @@ export type PlannedLaunch = {
161
162
  plan: LaunchPlanV1; planHash: Hash; launchId: Hash; predictedToken: Address;
162
163
  tokenFactory: Address; tokenFactoryCodeHash: Hex;
163
164
  account: Address; chainId: bigint; mode: LaunchExecutionMode; confirmations: number;
165
+ /** Opt-in proxy routing; the committed creator remains the proxy, account is its principal. */
166
+ ufg?: { config: UfgLaunchConfig; configHash: Hash };
164
167
  transactions: readonly LifecycleTransaction[]; prerequisites: readonly LifecycleFundingPrerequisite[];
165
168
  /** Sum of exact calldata bytes across the returned remaining commands, including approvals. */
166
169
  totalCalldataBytes: number;
@@ -176,6 +179,8 @@ export type PlanLaunchOptions = {
176
179
  client: LifecycleRpcClient; account: Address; plan: LaunchPlanV1; mode: LaunchExecutionMode;
177
180
  limits?: LifecycleLimitSource; fork?: ControlledLifecycleFork; receipts?: readonly LifecycleReceiptReference[];
178
181
  confirmations?: number; signal?: AbortSignal;
182
+ /** Opt in to the pinned UFG proxy; sender must match the nonce's principal namespace. */
183
+ ufg?: { config: UfgLaunchConfig };
179
184
  /** Unstarted only: permit interior gaps; opening containment, aligned ticks and the tail remain required. */
180
185
  allowTopologyGaps?: boolean;
181
186
  /** Unstarted only: waive the funded boundary tail; all other topology checks remain required. */
@@ -201,7 +206,7 @@ export type SimulateLaunchPlanOptions = {
201
206
  signal?: AbortSignal;
202
207
  };
203
208
  export type ReadLaunchProgressOptions = {
204
- client: LifecycleRpcClient; planned: Pick<PlannedLaunch, "plan" | "planHash" | "launchId" | "predictedToken" | "account" | "mode" | "confirmations">;
209
+ client: LifecycleRpcClient; planned: Pick<PlannedLaunch, "plan" | "planHash" | "launchId" | "predictedToken" | "account" | "mode" | "confirmations" | "ufg">;
205
210
  receipts?: readonly LifecycleReceiptReference[]; confirmations?: number;
206
211
  signal?: AbortSignal;
207
212
  };