@black-market/sdk 0.7.1 → 0.8.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 (57) hide show
  1. package/README.md +58 -22
  2. package/dist/launch.d.ts +31 -5
  3. package/dist/launch.d.ts.map +1 -1
  4. package/dist/launch.js +71 -11
  5. package/dist/launch.js.map +1 -1
  6. package/dist/lifecycle/abi.d.ts +18 -0
  7. package/dist/lifecycle/abi.d.ts.map +1 -1
  8. package/dist/lifecycle/abi.js +4 -0
  9. package/dist/lifecycle/abi.js.map +1 -1
  10. package/dist/lifecycle/index.d.ts +2 -0
  11. package/dist/lifecycle/index.d.ts.map +1 -1
  12. package/dist/lifecycle/index.js +2 -0
  13. package/dist/lifecycle/index.js.map +1 -1
  14. package/dist/lifecycle/planner.d.ts +1 -1
  15. package/dist/lifecycle/planner.d.ts.map +1 -1
  16. package/dist/lifecycle/planner.js +58 -10
  17. package/dist/lifecycle/planner.js.map +1 -1
  18. package/dist/lifecycle/progress.js +35 -35
  19. package/dist/lifecycle/progress.js.map +1 -1
  20. package/dist/lifecycle/rpc.d.ts +10 -4
  21. package/dist/lifecycle/rpc.d.ts.map +1 -1
  22. package/dist/lifecycle/rpc.js +137 -16
  23. package/dist/lifecycle/rpc.js.map +1 -1
  24. package/dist/lifecycle/types.d.ts +20 -1
  25. package/dist/lifecycle/types.d.ts.map +1 -1
  26. package/dist/lifecycle/types.js.map +1 -1
  27. package/docs/launch-lifecycle.md +396 -64
  28. package/examples/.env.example +22 -2
  29. package/examples/README.md +315 -87
  30. package/examples/{launch-erc20-abyss.mjs → launch-erc20-abyss.ts} +1 -1
  31. package/examples/{launch-erc20-burn-mixed.mjs → launch-erc20-burn-mixed.ts} +1 -1
  32. package/examples/launch-erc20-depth-v4.ts +7 -0
  33. package/examples/{launch-erc20-dividends-abyss.mjs → launch-erc20-dividends-abyss.ts} +1 -1
  34. package/examples/launch-erc20-dividends-multiquote-mixed.ts +8 -0
  35. package/examples/launch-erc20-multi-quote.ts +8 -0
  36. package/examples/{launch-erc20-staking-v4.mjs → launch-erc20-staking-v4.ts} +1 -1
  37. package/examples/{launch-erc20-v4.mjs → launch-erc20-v4.ts} +1 -1
  38. package/examples/{launch-erc404-abyss.mjs → launch-erc404-abyss.ts} +1 -1
  39. package/examples/{launch-erc404-dividends-mixed.mjs → launch-erc404-dividends-mixed.ts} +1 -1
  40. package/examples/{launch-erc404-v4.mjs → launch-erc404-v4.ts} +1 -1
  41. package/examples/launch-example-kyber.ts +204 -0
  42. package/examples/launch-example-support.ts +876 -0
  43. package/examples/launch-example.ts +1163 -0
  44. package/examples/{launch-lifecycle-smoke.mjs → launch-lifecycle-smoke.ts} +1 -1
  45. package/examples/{launch.mjs → launch.ts} +1 -1
  46. package/package.json +2 -2
  47. package/src/launch.ts +81 -14
  48. package/src/lifecycle/abi.ts +4 -0
  49. package/src/lifecycle/index.ts +2 -0
  50. package/src/lifecycle/planner.ts +61 -12
  51. package/src/lifecycle/progress.ts +35 -35
  52. package/src/lifecycle/rpc.ts +140 -17
  53. package/src/lifecycle/types.ts +19 -1
  54. package/examples/launch-example-support.mjs +0 -393
  55. package/examples/launch-example.mjs +0 -633
  56. /package/examples/{generate-lifecycle-commitment-fixture.mjs → generate-lifecycle-commitment-fixture.ts} +0 -0
  57. /package/examples/{quickstart.mjs → quickstart.ts} +0 -0
@@ -12,7 +12,7 @@ import {
12
12
  simulateLaunchPlan, isPoolBoundV4ConfigVersion,
13
13
  } from "@black-market/sdk/lifecycle";
14
14
  const [manifestFile, plansFile] = process.argv.slice(2);
15
- if (!manifestFile || !plansFile) throw new Error("Usage: node examples/launch-lifecycle-smoke.mjs manifest.json sdk-plans.json");
15
+ if (!manifestFile || !plansFile) throw new Error("Usage: node examples/launch-lifecycle-smoke.ts manifest.json sdk-plans.json");
16
16
  const manifest = JSON.parse(await readFile(manifestFile, "utf8"));
17
17
  const exported = JSON.parse(await readFile(plansFile, "utf8"));
18
18
  const rpcUrl = process.env.LAUNCH_LIFECYCLE_RPC_URL;
@@ -9,7 +9,7 @@ const args = process.argv.slice(2);
9
9
  const option = (name) => { const index = args.indexOf(name); return index === -1 ? undefined : args[index + 1]; };
10
10
  const planFile = option("--plan"), rpcUrl = option("--rpc"), mode = option("--mode");
11
11
  if (!planFile || !rpcUrl || !["atomic", "staged"].includes(mode)) {
12
- throw new Error("Usage: node examples/launch.mjs --plan plan.json --rpc RPC_URL --mode atomic|staged [--limits current-limits.json] [--receipts receipts.json] [--confirmations N] [--fork-rpc LOOPBACK_RPC_URL] [--mine]");
12
+ throw new Error("Usage: node examples/launch.ts --plan plan.json --rpc RPC_URL --mode atomic|staged [--limits current-limits.json] [--receipts receipts.json] [--confirmations N] [--fork-rpc LOOPBACK_RPC_URL] [--mine]");
13
13
  }
14
14
  const client = createPublicClient({ transport: http(rpcUrl) });
15
15
  const plan = parseLaunchPlan(await readFile(planFile, "utf8"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@black-market/sdk",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "TypeScript SDK for Abyss token launches, swaps, and lending.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "scripts": {
40
40
  "build": "tsc -p tsconfig.json",
41
41
  "dev": "tsc -p tsconfig.json --watch",
42
- "examples": "pnpm build && node examples/quickstart.mjs",
42
+ "examples": "pnpm build && node examples/quickstart.ts",
43
43
  "lint": "tsc -p tsconfig.json --noEmit",
44
44
  "prepublishOnly": "pnpm build",
45
45
  "test": "pnpm test:unit",
package/src/launch.ts CHANGED
@@ -43,9 +43,9 @@ export type LaunchPositionRangesInput = {
43
43
  /** Source-defined Black Market inventory weights, widths and gaps. */
44
44
  distributionPreset: "early-scarcity" | "staircase" | "smooth-ramp";
45
45
  positionCount?: number;
46
- /** Defaults to source-gaps; adjacent removes only Staircase's empty shelf gaps. */
47
- staircaseBoundaries?: "source-gaps" | "adjacent";
48
- /** Defaults to false; extends only the final band's far endpoint. */
46
+ /** Defaults to source-gaps for Staircase/Early Scarcity; adjacent removes their gaps. */
47
+ positionGaps?: "source-gaps" | "adjacent";
48
+ /** Defaults to true; extends only the final band's far endpoint. */
49
49
  extendFinalRangeToBoundary?: boolean;
50
50
  };
51
51
 
@@ -78,6 +78,21 @@ export type LaunchPositionRanges = {
78
78
  maxLiquidityPerTick: bigint;
79
79
  };
80
80
 
81
+ export type LaunchTopologyPosition = {
82
+ tickLower: number; tickUpper: number;
83
+ } & ({ maxTokenAmount: bigint } | { tokenAmountMaximum: bigint });
84
+
85
+ export type LaunchTopologyCoverageInput = {
86
+ positions: readonly LaunchTopologyPosition[];
87
+ /** Canonical tick derived from the market's opening sqrtPriceX96. */
88
+ openingTick: number;
89
+ tickSpacing: number;
90
+ /** Existing launch convention: true means the launched token is token0. */
91
+ launchedTokenIsQuote: boolean;
92
+ /** Defaults to false; opening containment and the permanent tail remain required. */
93
+ allowInteriorGaps?: boolean;
94
+ };
95
+
81
96
  export type LaunchBuySqrtPriceLimitInput = {
82
97
  launchSqrtPriceX96: bigint;
83
98
  launchedTokenIsQuote: boolean;
@@ -196,7 +211,7 @@ export function getSqrtRatioAtTick(tick: number): bigint {
196
211
  return (ratio >> 32n) + (ratio % Q32 === 0n ? 0n : 1n);
197
212
  }
198
213
 
199
- function getTickAtSqrtRatio(sqrtPriceX96: bigint): number {
214
+ export function getTickAtSqrtRatio(sqrtPriceX96: bigint): number {
200
215
  assertCanonicalSqrtPrice(sqrtPriceX96, "sqrtPriceX96");
201
216
 
202
217
  let low = MIN_TICK;
@@ -242,6 +257,56 @@ function launchBand(
242
257
  return { tickLower, tickUpper };
243
258
  }
244
259
 
260
+ /**
261
+ * Requires opening buy-side liquidity, continuous forward coverage and a funded
262
+ * permanent tail at the usable protocol boundary. Overlaps and adjacency are valid.
263
+ * Token1 containment includes the upper endpoint crossed by the first downward buy.
264
+ * Input positions are never reordered or mutated.
265
+ */
266
+ export function assertLaunchTopologyCoverage(input: LaunchTopologyCoverageInput): void {
267
+ const { positions, openingTick, tickSpacing, launchedTokenIsQuote } = input;
268
+ if (!Number.isInteger(tickSpacing) || tickSpacing < 1 || tickSpacing > 32767) throw new Error("tickSpacing must be 1..32767");
269
+ if (typeof launchedTokenIsQuote !== "boolean") throw new Error("launchedTokenIsQuote must be a boolean");
270
+ const allowInteriorGaps = input.allowInteriorGaps ?? false;
271
+ if (typeof allowInteriorGaps !== "boolean") throw new Error("allowInteriorGaps must be a boolean");
272
+ getSqrtRatioAtTick(openingTick);
273
+ const band = launchBand(openingTick, tickSpacing, launchedTokenIsQuote);
274
+ const boundaryTick = launchedTokenIsQuote ? band.tickUpper : band.tickLower;
275
+ let containsOpening = false;
276
+ let hasPermanentTail = false;
277
+ for (const position of positions) {
278
+ const { tickLower, tickUpper } = position;
279
+ if (!Number.isInteger(tickLower) || !Number.isInteger(tickUpper) ||
280
+ tickLower % tickSpacing !== 0 || tickUpper % tickSpacing !== 0) {
281
+ throw new Error("TOPOLOGY_OFF_GRID_TICK: Every position endpoint must be spacing-aligned");
282
+ }
283
+ getSqrtRatioAtTick(tickLower);
284
+ getSqrtRatioAtTick(tickUpper);
285
+ if (tickLower >= tickUpper) throw new Error("TOPOLOGY_OPENING_GAP: Every position must have a nonempty tick range");
286
+ if (launchedTokenIsQuote ? tickLower <= openingTick && openingTick < tickUpper : tickLower < openingTick && openingTick <= tickUpper) {
287
+ containsOpening = true;
288
+ }
289
+ const tokenMaximum = "maxTokenAmount" in position ? position.maxTokenAmount : position.tokenAmountMaximum;
290
+ if ((launchedTokenIsQuote ? tickUpper : tickLower) === boundaryTick && tokenMaximum > 0n) hasPermanentTail = true;
291
+ }
292
+ if (!containsOpening) throw new Error("TOPOLOGY_OPENING_GAP: No opening buy-side band contains the launch tick");
293
+ const sorted = [...positions].sort(launchedTokenIsQuote
294
+ ? (left, right) => left.tickLower - right.tickLower
295
+ : (left, right) => right.tickUpper - left.tickUpper);
296
+ let coveredTo = openingTick;
297
+ for (const position of sorted) {
298
+ const near = launchedTokenIsQuote ? position.tickLower : position.tickUpper;
299
+ const far = launchedTokenIsQuote ? position.tickUpper : position.tickLower;
300
+ if (launchedTokenIsQuote ? far <= coveredTo : far >= coveredTo) continue;
301
+ if (!allowInteriorGaps && (launchedTokenIsQuote ? near > coveredTo : near < coveredTo)) {
302
+ throw new Error(`TOPOLOGY_INTERIOR_GAP: Uncovered interval between ticks ${coveredTo} and ${near}`);
303
+ }
304
+ coveredTo = far;
305
+ if (coveredTo === boundaryTick) break;
306
+ }
307
+ if (!hasPermanentTail) throw new Error(`MISSING_PERMANENT_TAIL: A positive token allocation must reach tick ${boundaryTick}`);
308
+ }
309
+
245
310
  function liquidityForAmount0(amount0: bigint, sqrtLowerX96: bigint, sqrtUpperX96: bigint): bigint {
246
311
  return (((amount0 * sqrtLowerX96) / Q96) * sqrtUpperX96) / (sqrtUpperX96 - sqrtLowerX96);
247
312
  }
@@ -333,8 +398,8 @@ function apportionLaunchInventory(total: number, scores: readonly number[]): num
333
398
  /**
334
399
  * Source-defined one-sided Black Market presets, ordered toward higher launch-token
335
400
  * prices. The first position starts at launch; later bands are contiguous or leave
336
- * the preset's intentional gaps. Only an explicit final-tail adaptation reaches
337
- * the usable protocol boundary; no equal-tick extrapolation is applied.
401
+ * the preset's intentional gaps. By default the final band reaches the usable
402
+ * protocol boundary; no equal-tick extrapolation is applied to earlier bands.
338
403
  * Supply allocation across pools belongs to the caller: pass each pool's share.
339
404
  * Budget caps floor all but the final allocation, which receives the remainder.
340
405
  * Exact rational funding floors guarantee canonical rounded-up mint debits fit
@@ -356,14 +421,16 @@ export function deriveLaunchPositionRanges(input: LaunchPositionRangesInput): La
356
421
  }
357
422
  const positionCount = input.positionCount ?? 5;
358
423
  if (!Number.isInteger(positionCount) || positionCount < 2 || positionCount > 16) throw new Error("positionCount must be 2..16");
359
- const extendFinalRangeToBoundary = input.extendFinalRangeToBoundary ?? false;
424
+ const extendFinalRangeToBoundary = input.extendFinalRangeToBoundary ?? true;
360
425
  if (typeof extendFinalRangeToBoundary !== "boolean") throw new Error("extendFinalRangeToBoundary must be a boolean");
361
- const staircaseBoundaries = input.staircaseBoundaries === undefined ? "source-gaps" : input.staircaseBoundaries;
362
- if (staircaseBoundaries !== "source-gaps" && staircaseBoundaries !== "adjacent") {
363
- throw new Error("staircaseBoundaries must be source-gaps or adjacent");
426
+ const positionGaps = input.positionGaps === undefined
427
+ ? distributionPreset === "smooth-ramp" ? "adjacent" : "source-gaps"
428
+ : input.positionGaps;
429
+ if (positionGaps !== "source-gaps" && positionGaps !== "adjacent") {
430
+ throw new Error("positionGaps must be source-gaps or adjacent");
364
431
  }
365
- if (input.staircaseBoundaries !== undefined && distributionPreset !== "staircase") {
366
- throw new Error("staircaseBoundaries is only valid for the staircase preset");
432
+ if (distributionPreset === "smooth-ramp" && positionGaps === "source-gaps") {
433
+ throw new Error("smooth-ramp positions are always adjacent");
367
434
  }
368
435
  const launchTick = getTickAtSqrtRatio(input.launchSqrtPriceX96);
369
436
  if (launchTick % tickSpacing !== 0 || getSqrtRatioAtTick(launchTick) !== input.launchSqrtPriceX96) {
@@ -403,10 +470,10 @@ export function deriveLaunchPositionRanges(input: LaunchPositionRangesInput): La
403
470
  : (input.tokenBudget * BigInt(weight)) / 10_000n;
404
471
  if (allocatedTokenAmount <= 0n) throw new Error("Every position must have a positive allocated token cap");
405
472
  allocated += allocatedTokenAmount;
406
- if (index > 0) {
473
+ if (index > 0 && positionGaps === "source-gaps") {
407
474
  if (distributionPreset === "early-scarcity") {
408
475
  distance += 2 + Math.floor((4 * index) / (positionCount - 1));
409
- } else if (distributionPreset === "staircase" && staircaseBoundaries === "source-gaps" &&
476
+ } else if (distributionPreset === "staircase" &&
410
477
  Math.floor((index * 4) / positionCount) !== Math.floor(((index - 1) * 4) / positionCount)) {
411
478
  distance += 3;
412
479
  }
@@ -305,6 +305,8 @@ export const lifecycleV4LockerAbi = [
305
305
  { type: "function", name: "launcher", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
306
306
  { type: "function", name: "positionCount", stateMutability: "view", inputs: [{ type: "bytes32" }], outputs: [{ type: "uint256" }] },
307
307
  { type: "function", name: "isSealed", stateMutability: "view", inputs: [{ type: "bytes32" }], outputs: [{ type: "bool" }] },
308
+ // Present on the deployed V2 locker; read for permanent-custody verification.
309
+ { type: "function", name: "feeRecipient", stateMutability: "view", inputs: [{ type: "bytes32" }], outputs: [{ type: "address" }] },
308
310
  ] as const;
309
311
  const poolFeeCollectorFactoryCommonAbi = [
310
312
  { type: "function", name: "collectorDeployer", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
@@ -359,6 +361,8 @@ export const lifecycleErc404Abi = [
359
361
  { type: "function", name: "unit", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
360
362
  { type: "function", name: "baseURI", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
361
363
  { type: "function", name: "mirrorERC721", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
364
+ // Present on LaunchERC404V1; read for ERC404 supply/custody verification.
365
+ { type: "function", name: "maxNFTSupply", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
362
366
  ] as const;
363
367
  export const lifecycleErc404MirrorAbi = [
364
368
  { type: "function", name: "baseERC20", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
@@ -8,4 +8,6 @@ export * from "./fork.js";
8
8
  export * from "./authors.js";
9
9
  export * from "./presets.js";
10
10
  export * from "./transactions.js";
11
+ export { createLifecycleSessionCache } from "./rpc.js";
11
12
  export { planLaunch, simulateLaunchPlan, buildNextTransaction, prepareAndPlanLifecycleLaunch } from "./planner.js";
13
+ export { assertLaunchTopologyCoverage, getTickAtSqrtRatio, type LaunchTopologyCoverageInput, type LaunchTopologyPosition } from "../launch.js";
@@ -1,9 +1,11 @@
1
1
  import { encodeFunctionData, keccak256, toHex, zeroAddress, type Address, type Hex } from "viem";
2
+ import { ABYSS_FEE_TIERS } from "../abyss.js";
3
+ import { assertLaunchTopologyCoverage, getTickAtSqrtRatio } from "../launch.js";
2
4
  import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleErc20Abi } from "./abi.js";
3
5
  import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, LIFECYCLE_MAX_ERC404_SUPPLY, parseLaunchPlan, serializeLaunchPlan, type LaunchPlanV1 } from "./schema.js";
4
6
  import { readLaunchProgress, readLifecycleProfiles, readLifecycleTokenAtBlock, readPoolBoundHookDeployment } from "./progress.js";
5
- import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion, minePoolBoundHookSalt, type V4LifecycleMarketConfig, type V4PoolBoundLifecycleMarketConfig } from "./markets.js";
6
- import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleChainId, readLifecycleContract, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
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";
7
9
  import { prepareLaunchSimulation, simulateLaunchTransactions, type NextTransactionEnvelope } from "./simulation.js";
8
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";
9
11
  import { deriveKnownPoolBoundHookDeployment, protectOrderedBuyMinimums, validateBuySlippageBps } from "./calibration.js";
@@ -11,7 +13,12 @@ import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets
11
13
  import { buildLaunchTransactions } from "./transactions.js";
12
14
  import { assertLifecycleMarketAllocation, assertLifecycleSupplyAllocation } from "./allocations.js";
13
15
 
14
- function validatePlanShape(plan: LaunchPlanV1): Hex {
16
+ type ValidatedPlanShape = {
17
+ commitment: Hex;
18
+ marketConfigs: (AbyssLifecycleMarketConfig | V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig)[];
19
+ };
20
+
21
+ function validatePlanShape(plan: LaunchPlanV1): ValidatedPlanShape {
15
22
  // Encoding checks all exact integer widths, addresses, byte strings and tuple fields.
16
23
  const commitment = hashLaunchPlan(plan);
17
24
  if (plan.markets.length === 0 || plan.markets.length > 16 || plan.buys.length > 64 || plan.funding.length > 8 || plan.feeAssets.length === 0 || plan.feeAssets.length > 8 || plan.token.supply <= 0n || plan.token.inventoryRecipient === zeroAddress || plan.token.inventoryRecipient.toLowerCase() === plan.orchestrator.toLowerCase() || plan.executorFeeBps > 1000) throw new LifecyclePlanningError("INVALID_PLAN", "Plan exceeds lifecycle bounds or lacks positive token economics");
@@ -36,6 +43,7 @@ function validatePlanShape(plan: LaunchPlanV1): Hex {
36
43
  }
37
44
  const v4Quotes = new Set<string>();
38
45
  let positionCount = 0;
46
+ const marketConfigs: ValidatedPlanShape["marketConfigs"] = [];
39
47
  for (const market of plan.markets) {
40
48
  if (market.tokenBudget <= 0n || market.config === "0x" || (market.config.length - 2) / 2 > 16384) throw new LifecyclePlanningError("INVALID_MARKET", "Each market needs a positive token budget and bounded versioned config");
41
49
  let config: V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig | undefined;
@@ -49,8 +57,13 @@ function validatePlanShape(plan: LaunchPlanV1): Hex {
49
57
  v4Quotes.add(quote);
50
58
  if (config.lpFeePips >= 1000000 || (config.version === 6 ? config.hookFeePips > 1000000 : config.hookFeePips >= 1000000) || config.feeMode > 1 || config.tickSpacing < 1 || config.tickSpacing > 32767 || config.sqrtPriceX96 < 4295128739n || config.sqrtPriceX96 >= 1461446703485210103287273052203988822378723970342n || (config.protocolFeeDenominator !== 0 && (config.protocolFeeDenominator < 4 || config.protocolFeeDenominator > 10 || config.treasury === zeroAddress))) throw new LifecyclePlanningError("INVALID_MARKET", "V4 market violates lifecycle fee, price, treasury or position bounds");
51
59
  positionCount += assertLifecycleMarketAllocation(market, config.positions);
60
+ marketConfigs.push(config);
61
+ }
62
+ else {
63
+ const abyss = decodeAbyssLifecycleMarketConfig(market.config);
64
+ positionCount += assertLifecycleMarketAllocation(market, abyss.positions);
65
+ marketConfigs.push(abyss);
52
66
  }
53
- else positionCount += assertLifecycleMarketAllocation(market, decodeAbyssLifecycleMarketConfig(market.config).positions);
54
67
  if (positionCount > 32) throw new LifecyclePlanningError("INVALID_POSITION_COUNT", "A launch commits at most 32 total positions across every venue");
55
68
  }
56
69
  assertLifecycleSupplyAllocation(plan);
@@ -59,7 +72,7 @@ function validatePlanShape(plan: LaunchPlanV1): Hex {
59
72
  const funding = plan.funding.find((item) => item.asset.toLowerCase() === market.quoteAsset.toLowerCase());
60
73
  if (required > (funding?.amount ?? 0n)) throw new LifecyclePlanningError("INVALID_FUNDING", "Quote funding does not cover all ordered buys for its asset");
61
74
  }
62
- return commitment;
75
+ return { commitment, marketConfigs };
63
76
  }
64
77
 
65
78
  async function readFunding(client: LifecycleRpcClient, plan: LaunchPlanV1, block: LifecycleBlock): Promise<{ prerequisites: LifecycleFundingPrerequisite[]; approvals: LifecycleTransaction[] }> {
@@ -180,7 +193,7 @@ function assertReviewedTransactionUnchanged(options: BuildNextTransactionOptions
180
193
  async function readTokenFactoryBinding(client: LifecycleRpcClient, orchestrator: Address, block: LifecycleBlock): Promise<Pick<PlannedLaunch, "tokenFactory" | "tokenFactoryCodeHash">> {
181
194
  const tokenFactory = await readLifecycleContract<Address>(client, orchestrator, launchLifecycleAbi, "tokenFactory", [], block);
182
195
  const [codeValue, authority] = await Promise.all([
183
- lifecyclePinnedRpc(client, "eth_getCode", [tokenFactory, toHex(block.number)], block),
196
+ lifecycleStaticRpc(client, "eth_getCode", [tokenFactory, toHex(block.number)], block),
184
197
  readLifecycleContract<Address>(client, tokenFactory, lifecycleAdapterAbi, "core", [], block),
185
198
  ]);
186
199
  const code = rpcHex(codeValue, "token factory code");
@@ -202,7 +215,7 @@ function assertPlanningActive(signal?: AbortSignal): void {
202
215
  if (signal?.aborted) throw Object.assign(new Error("Lifecycle launch planning cancelled"), { name: "AbortError" });
203
216
  }
204
217
 
205
- function validatePlanningIntent(options: PlanLaunchOptions): Hex {
218
+ function validatePlanningIntent(options: PlanLaunchOptions): ValidatedPlanShape {
206
219
  const { plan, account, mode } = options;
207
220
  assertPlanningActive(options.signal);
208
221
  if (mode !== "atomic" && mode !== "staged") throw new LifecyclePlanningError("EXPLICIT_MODE_REQUIRED", "Choose atomic or staged explicitly; staged is never silently substituted");
@@ -211,15 +224,45 @@ function validatePlanningIntent(options: PlanLaunchOptions): Hex {
211
224
  return validatePlanShape(plan);
212
225
  }
213
226
 
227
+ function validatePlanTopology(options: PlanLaunchOptions, marketConfigs: ValidatedPlanShape["marketConfigs"], token: Address): void {
228
+ for (const [marketIndex, config] of marketConfigs.entries()) {
229
+ try {
230
+ let tickSpacing: number;
231
+ let openingSqrtPriceX96: bigint;
232
+ if ("version" in config) {
233
+ tickSpacing = config.tickSpacing;
234
+ openingSqrtPriceX96 = config.sqrtPriceX96;
235
+ } else {
236
+ const tier = ABYSS_FEE_TIERS.find(({ feePips }) => feePips === config.fee);
237
+ if (tier === undefined) throw new LifecyclePlanningError("INVALID_MARKET", "Abyss market fee must select a canonical tick-spacing tier");
238
+ tickSpacing = tier.tickSpacing;
239
+ openingSqrtPriceX96 = config.openingSqrtPriceX96;
240
+ }
241
+ assertLaunchTopologyCoverage({ positions: config.positions, openingTick: getTickAtSqrtRatio(openingSqrtPriceX96), tickSpacing,
242
+ launchedTokenIsQuote: BigInt(token) < BigInt(options.plan.markets[marketIndex]!.quoteAsset),
243
+ allowInteriorGaps: options.allowTopologyGaps === true });
244
+ } catch (error) {
245
+ if (!(error instanceof Error)) throw error;
246
+ const code = error.message.slice(0, error.message.indexOf(":"));
247
+ if (code === "MISSING_PERMANENT_TAIL" && options.allowMissingPermanentTail === true) continue;
248
+ if (code === "TOPOLOGY_OPENING_GAP" || code === "TOPOLOGY_INTERIOR_GAP" ||
249
+ code === "MISSING_PERMANENT_TAIL" || code === "TOPOLOGY_OFF_GRID_TICK") {
250
+ throw new LifecyclePlanningError(code, `Market ${marketIndex}: ${error.message}`);
251
+ }
252
+ throw error;
253
+ }
254
+ }
255
+ }
256
+
214
257
  function assertFinalPlan(planned: PlannedLaunch, signal?: AbortSignal): void {
215
258
  assertPlanningActive(signal);
216
259
  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");
217
260
  }
218
261
 
219
262
  /** One owner constructs and mines, optionally measures actual ordered buy outputs,
220
- * then proves only the final protected commitment. No certificates or skip flags. */
263
+ * then proves only the final protected commitment. No cached execution certificates. */
221
264
  export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifecycleLaunchOptions): Promise<PlannedLaunch> {
222
- const commitment = validatePlanningIntent(options);
265
+ const { commitment } = validatePlanningIntent(options);
223
266
  if (options.buySlippageBps !== undefined) validateBuySlippageBps(options.buySlippageBps);
224
267
  const needsMining = options.plan.markets.some((market) => isPoolBoundV4ConfigVersion(market.configVersion));
225
268
  const needsProtection = options.buySlippageBps !== undefined && options.plan.buys.length !== 0;
@@ -293,7 +336,7 @@ export async function planLaunch(options: PlanLaunchOptions): Promise<PlannedLau
293
336
 
294
337
  async function planLaunchWithReads(options: PlanLaunchOptions, client: LifecycleRpcClient, pinnedBlock?: LifecycleBlock, ownedProgress?: CanonicalLaunchProgress, diagnostic = false, reviewed?: ReviewedNextTransaction): Promise<PlannedLaunch> {
295
338
  const { plan, account, mode } = options;
296
- const planHash = validatePlanningIntent(options);
339
+ const { commitment: planHash, marketConfigs } = validatePlanningIntent(options);
297
340
  const launchId = hashLaunchIdentity(plan);
298
341
  const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
299
342
  pinnedBlock ?? readLifecycleBlock(client), readLifecycleChainId(client),
@@ -322,7 +365,8 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
322
365
  ]));
323
366
  assertPlanningActive(options.signal);
324
367
  const placeholder: LifecycleSimulation = { backend: "unavailable", confidence: "provisional", admitted: false, executionProof: "unavailable", protocolFit: "unknown", transportPreflight: "not-requested", blockNumber: block.number, blockHash: block.hash, account, chainId: plan.chainId, limits, steps: [] };
325
- let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, simulation: placeholder, preparationBatchSize: plan.markets.length, limits: options.limits };
368
+ let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, simulation: placeholder, preparationBatchSize: plan.markets.length, limits: options.limits,
369
+ allowTopologyGaps: options.allowTopologyGaps === true, allowMissingPermanentTail: options.allowMissingPermanentTail === true };
326
370
  const receiptsPending = progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed");
327
371
  if (!progress.confirmationSafe || receiptsPending) {
328
372
  const hookDeployments: LifecyclePoolBoundHookDeployment[] = [];
@@ -340,6 +384,9 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
340
384
  await assertLifecycleBlock(client, block, plan.chainId);
341
385
  return { ...planned, simulation: { ...placeholder, confidence: "stateful", executionProof: "proved", protocolFit: "proved", admitted: true, reason: "Canonical launch is terminal; no transaction remains" } };
342
386
  }
387
+ // Topology is a creation policy, not a retroactive obstacle to recovery of
388
+ // immutable onchain commitments. Waiting and terminal launches returned above.
389
+ if (progress.canonical.phase === LifecyclePhase.None && progress.head.phase === LifecyclePhase.None) validatePlanTopology(options, marketConfigs, identity.predictedToken);
343
390
  const [funding, metadata] = await lifecycleStage(client, "plan.inputs", () => Promise.all([
344
391
  progress.canonical.phase === LifecyclePhase.None ? readFunding(client, plan, block) : { prerequisites: [], approvals: [] },
345
392
  readPlanMetadata(client, plan, identity.predictedToken, block),
@@ -459,7 +506,8 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
459
506
  if (next === undefined) throw new LifecyclePlanningError("MISSING_GAS_PROOF", "Cancellation omitted its exact simulation admission", simulation);
460
507
  return preflightNextTransaction(options, next, simulation, reviewed);
461
508
  }
462
- 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 }, client, block, progress, false, reviewed));
509
+ 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,
510
+ allowTopologyGaps: options.planned.allowTopologyGaps, allowMissingPermanentTail: options.planned.allowMissingPermanentTail }, client, block, progress, false, reviewed));
463
511
  assertFinalPlan(refreshed, options.signal);
464
512
  if (!refreshed.simulation.admitted) throw new LifecyclePlanningError("PLAN_NOT_ADMITTED", refreshed.simulation.reason ?? "Remaining exact launch sequence is not admitted", refreshed.simulation);
465
513
  assertPlannedDeploymentBindings(options.planned, refreshed);
@@ -478,6 +526,7 @@ export async function simulateLaunchPlan(options: SimulateLaunchPlanOptions): Pr
478
526
  client, plan: planned.plan, account: planned.account, mode: planned.mode,
479
527
  limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork,
480
528
  receipts: options.receipts, confirmations: options.confirmations ?? planned.confirmations, signal: options.signal,
529
+ allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
481
530
  });
482
531
  assertPlannedDeploymentBindings(planned, current);
483
532
  return current.simulation;
@@ -2,7 +2,7 @@ import { concatHex, decodeFunctionData, encodeAbiParameters, encodeFunctionData,
2
2
  import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleOracleFactoryAbi, lifecycleRegistryAbi, poolFeeCollectorFactoryV1Abi, poolFeeCollectorFactoryConfigV6Abi, lifecycleV4HookAbi, lifecycleV4LockerAbi, poolHookDeployerV1Abi, poolHookDeployerConfigV6Abi, sharedHookDeployerV1Abi, fixedFeePoolHookV1Abi, fixedFeePoolHookConfigV6Abi, poolMarketAdapterV1Abi, sharedMarketAdapterV1Abi } from "./abi.js";
3
3
  import { buildPoolBoundHookParameters, decodePoolBoundV4LifecycleMarketConfig, encodePoolBoundHookParameters, hasLifecycleV4HookPermissions, isPoolBoundV4ConfigVersion, poolBoundHookInitCodeHash, predictPoolBoundHookAddress, validateV4LifecycleMarket, V4_LIFECYCLE_CONFIG_SCHEMA, V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V5, V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6, type PoolBoundHookParameters, type PoolBoundLifecycleConfigVersion } from "./markets.js";
4
4
  import { hashLaunchIdentity, hashLaunchPlan, hashLaunchBounds, hashLaunchDependencies, hashLifecycleProfile, lifecycleTokenComponents, launchProgressV1Components, LifecycleMode, LifecyclePhase, LifecycleVenue, LIFECYCLE_REQUIRED_CAPABILITIES, LIFECYCLE_ERC404_CAPABILITY, LIFECYCLE_MULTI_POSITION_CAPABILITY, type AdapterRegistrationV1, type LaunchPlanV1, type LaunchProgressV1, type MarketIdentityV1, type MarketLiveStateV1, type PreparedMarketV1, type ProfileRegistrationV1, type ProfileTopologyV1, type LaunchEnvelopeV2, type LifecycleDeveloperTerms } from "./schema.js";
5
- import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleChainId, readLifecycleContract, readLifecycleProfileTopology, rpcHex, rpcObject, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
5
+ import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleChainId, readLifecycleContract, readLifecycleStaticContract, readLifecycleProfileTopology, rpcHex, rpcObject, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
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";
@@ -76,7 +76,7 @@ function lifecycleProfileCertificateKey(orchestrator: Address, registry: Address
76
76
  }
77
77
 
78
78
  async function readDependencyCode(client: LifecycleRpcClient, address: Address, block: LifecycleBlock): Promise<Hex> {
79
- const code = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [address, toHex(block.number)], block), "lifecycle dependency code");
79
+ const code = rpcHex(await lifecycleStaticRpc(client, "eth_getCode", [address, toHex(block.number)], block), "lifecycle dependency code");
80
80
  if (address === zeroAddress || code === "0x") throw new LifecyclePlanningError("PROFILE_DEPENDENCY", `Lifecycle dependency ${address} has no deployed code`);
81
81
  return code;
82
82
  }
@@ -118,7 +118,7 @@ async function certifyLifecycleProfileFresh(client: LifecycleRpcClient, orchestr
118
118
  const known = [0, 1, 2, 3].some((variant) => keccak256(encodeAbiParameters([{ type: "bytes32" }, { type: "uint256" }, { type: "address" }, { type: "uint8" }], [domain, chainId, factory, variant])).toLowerCase() === id.toLowerCase());
119
119
  if (!known || registration.venue.toLowerCase() !== factory.toLowerCase() || registration.hook !== zeroAddress) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Abyss profile is not the exact canonical factory/variant/schema binding");
120
120
  if (eligibility !== undefined && "failure" in eligibility) throw eligibility.failure;
121
- const eligible = eligibility?.eligible ?? await readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, version, LIFECYCLE_REQUIRED_CAPABILITIES], block);
121
+ const eligible = eligibility?.eligible ?? await readLifecycleStaticContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, version, LIFECYCLE_REQUIRED_CAPABILITIES], block);
122
122
  if (eligible.toLowerCase() !== adapter.implementation.toLowerCase()) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
123
123
  return Object.freeze({ topology: Object.freeze({ ...topology }) });
124
124
  }
@@ -147,28 +147,28 @@ async function certifyLifecycleProfileFresh(client: LifecycleRpcClient, orchestr
147
147
  // Frozen graph addresses permit independent evidence reads. Getter results
148
148
  // still have to match that graph; none of these observations is caller authority.
149
149
  const [eligible, metadata, actualVersion, dependencies, dependencyCodes, collectorDeployer, launcher, lockerManager, creationCodeHash, chunk0, chunk1, chunkCodes, sharedRoot] = await Promise.all([
150
- readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, version, LIFECYCLE_REQUIRED_CAPABILITIES], block),
151
- Promise.all((["PROFILE_ID", "CONFIG_SCHEMA", "implementationRegistry"] as const).map((getter) => readLifecycleContract<Hex>(client, adapter.implementation, abi, getter, [], block))),
152
- readLifecycleContract<number>(client, adapter.implementation, abi, "CONFIG_VERSION", [], block),
153
- Promise.all((["poolManager", "hookRoot", "oracleFactory", "locker", "collectorFactory", "hookDeployer"] as const).map((getter) => readLifecycleContract<Address>(client, adapter.implementation, abi, getter, [], block))),
150
+ readLifecycleStaticContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, version, LIFECYCLE_REQUIRED_CAPABILITIES], block),
151
+ Promise.all((["PROFILE_ID", "CONFIG_SCHEMA", "implementationRegistry"] as const).map((getter) => readLifecycleStaticContract<Hex>(client, adapter.implementation, abi, getter, [], block))),
152
+ readLifecycleStaticContract<number>(client, adapter.implementation, abi, "CONFIG_VERSION", [], block),
153
+ Promise.all((["poolManager", "hookRoot", "oracleFactory", "locker", "collectorFactory", "hookDeployer"] as const).map((getter) => readLifecycleStaticContract<Address>(client, adapter.implementation, abi, getter, [], block))),
154
154
  Promise.all(codeBindings.map(([address]) => readDependencyCode(client, address, block))),
155
- readLifecycleContract<Address>(client, g.collectorFactory, poolFeeCollectorFactoryV1Abi, "collectorDeployer", [], block),
156
- readLifecycleContract<Address>(client, g.locker, lifecycleV4LockerAbi, "launcher", [], block),
157
- readLifecycleContract<Address>(client, g.locker, lifecycleV4LockerAbi, "poolManager", [], block),
158
- readLifecycleContract<Hex>(client, g.hookDeployer, deployerAbi, "creationCodeHash", [], block),
159
- readLifecycleContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk0", [], block),
160
- readLifecycleContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk1", [], block),
155
+ readLifecycleStaticContract<Address>(client, g.collectorFactory, poolFeeCollectorFactoryV1Abi, "collectorDeployer", [], block),
156
+ readLifecycleStaticContract<Address>(client, g.locker, lifecycleV4LockerAbi, "launcher", [], block),
157
+ readLifecycleStaticContract<Address>(client, g.locker, lifecycleV4LockerAbi, "poolManager", [], block),
158
+ readLifecycleStaticContract<Hex>(client, g.hookDeployer, deployerAbi, "creationCodeHash", [], block),
159
+ readLifecycleStaticContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk0", [], block),
160
+ readLifecycleStaticContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk1", [], block),
161
161
  Promise.all([
162
162
  readDependencyCode(client, g.codeChunk0, block),
163
163
  g.codeChunk1 === zeroAddress ? Promise.resolve<Hex>("0x") : readDependencyCode(client, g.codeChunk1, block),
164
164
  ]),
165
165
  shared ? Promise.all([
166
166
  readDependencyCode(client, g.hookRoot, block),
167
- readLifecycleContract<Hex>(client, g.hookDeployer, deployerAbi, "deployedCodeHash", [g.hookRoot], block),
168
- readLifecycleContract<Address>(client, g.hookDeployer, sharedHookDeployerV1Abi, "predict", [g.manager, adapter.implementation, g.oracleFactory, g.sharedHookSalt], block),
169
- Promise.all((["registrar", "poolManager", "oracleFactory"] as const).map((getter) => readLifecycleContract<Address>(client, g.hookRoot, lifecycleV4HookAbi, getter, [], block))),
170
- readLifecycleContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "REQUIRED_HOOK_FLAGS", [], block),
171
- readLifecycleContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "ALL_HOOK_MASK", [], block),
167
+ readLifecycleStaticContract<Hex>(client, g.hookDeployer, deployerAbi, "deployedCodeHash", [g.hookRoot], block),
168
+ readLifecycleStaticContract<Address>(client, g.hookDeployer, sharedHookDeployerV1Abi, "predict", [g.manager, adapter.implementation, g.oracleFactory, g.sharedHookSalt], block),
169
+ Promise.all((["registrar", "poolManager", "oracleFactory"] as const).map((getter) => readLifecycleStaticContract<Address>(client, g.hookRoot, lifecycleV4HookAbi, getter, [], block))),
170
+ readLifecycleStaticContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "REQUIRED_HOOK_FLAGS", [], block),
171
+ readLifecycleStaticContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "ALL_HOOK_MASK", [], block),
172
172
  ]) : undefined,
173
173
  ]);
174
174
  if (eligible.toLowerCase() !== adapter.implementation.toLowerCase()) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
@@ -214,7 +214,7 @@ export async function validateV4LifecycleOracle(options: {
214
214
  }): Promise<void> {
215
215
  const { client, envelope, oracleConfigId, block } = options;
216
216
  if (oracleConfigId.toLowerCase() === zeroHash) throw new LifecyclePlanningError("INVALID_ORACLE_CONFIG", "Market oracle configuration must be nonzero");
217
- const [move, cardinality] = await readLifecycleContract<readonly [number, number]>(client, envelope.graph.oracleFactory, lifecycleOracleFactoryAbi, "oracleConfigs", [oracleConfigId], block);
217
+ const [move, cardinality] = await readLifecycleStaticContract<readonly [number, number]>(client, envelope.graph.oracleFactory, lifecycleOracleFactoryAbi, "oracleConfigs", [oracleConfigId], block);
218
218
  if (move <= 0 || move > 887272 || cardinality < 2 || cardinality > envelope.bounds.maximumOracleCardinality) {
219
219
  throw new LifecyclePlanningError("INVALID_ORACLE_CONFIG", "Selected market oracle is unregistered or exceeds the admitted cardinality bounds");
220
220
  }
@@ -234,25 +234,25 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
234
234
  if (offset < 0n || limit < 0n || limit > 100n) throw new LifecyclePlanningError("INVALID_PROFILE_PAGE", "Registry profile pages require unsigned offset and limit at most 100");
235
235
  let protocolMaximumDeveloperFeeBpsPromise: Promise<number> | undefined;
236
236
  const readProtocolMaximumDeveloperFeeBps = () => protocolMaximumDeveloperFeeBpsPromise ??=
237
- readLifecycleContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
237
+ readLifecycleStaticContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
238
238
  const readProfile = async (id: Hex): Promise<LifecycleProfile> => {
239
- const registration = await readLifecycleContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [id], block);
239
+ const registration = await readLifecycleStaticContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [id], block);
240
240
  const schema = registration.configSchema.toLowerCase();
241
241
  const venueKind = schema === V4_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() || schema === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V5.toLowerCase() || schema === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6.toLowerCase() ? "uniswap-v4" : schema === ABYSS_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() ? "abyss" : "unknown";
242
242
  const [adapter, certification] = await Promise.all([
243
- readLifecycleContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block),
243
+ readLifecycleStaticContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block),
244
244
  venueKind === "unknown" ? undefined : Promise.all([
245
245
  readLifecycleProfileTopology(client, registry, id, block),
246
246
  venueKind === "uniswap-v4" ? Promise.all([
247
- readLifecycleContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block),
248
- readLifecycleContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
247
+ readLifecycleStaticContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block),
248
+ readLifecycleStaticContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
249
249
  readProtocolMaximumDeveloperFeeBps(),
250
250
  ]) : undefined,
251
251
  // The exact Abyss arguments follow from registration, independently
252
252
  // of adapter/topology responses. Handle refusal immediately, but
253
253
  // keep its original place after canonical/schema/topology checks.
254
254
  venueKind === "abyss" ? chainIdPromise.then(() =>
255
- readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, 1, LIFECYCLE_REQUIRED_CAPABILITIES], block)
255
+ readLifecycleStaticContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, 1, LIFECYCLE_REQUIRED_CAPABILITIES], block)
256
256
  ).then((eligible) => ({ eligible }), (failure: unknown) => ({ failure })) : undefined,
257
257
  ]).then((values) => ({ values }), (failure: unknown) => ({ failure })),
258
258
  ]);
@@ -345,16 +345,16 @@ async function readPoolBoundHookDeploymentDetailsFresh(options: { client: Lifecy
345
345
  const registry = await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "registry", [], block);
346
346
  const [authority, registration] = await Promise.all([
347
347
  readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
348
- readLifecycleContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [market.profileId], block),
348
+ readLifecycleStaticContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [market.profileId], block),
349
349
  ]);
350
350
  if (authority.toLowerCase() !== plan.orchestrator.toLowerCase()) throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to this lifecycle core");
351
- const adapter = await readLifecycleContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block);
351
+ const adapter = await readLifecycleStaticContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block);
352
352
  if (registration.adapterId.toLowerCase() !== market.adapterId.toLowerCase()) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market adapter differs from the profile's immutable registration");
353
353
  const [recorded, envelope, [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled], protocolMaximumDeveloperFeeBps, token] = await Promise.all([
354
354
  readLifecycleProfileTopology(client, registry, market.profileId, block),
355
- readLifecycleContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [market.profileId], block),
356
- readLifecycleContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [market.profileId], block),
357
- readLifecycleContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block),
355
+ readLifecycleStaticContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [market.profileId], block),
356
+ readLifecycleStaticContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [market.profileId], block),
357
+ readLifecycleStaticContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block),
358
358
  readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "predictToken", [plan], block),
359
359
  ]);
360
360
  const { boundGraph } = await certifyLifecycleProfile(client, plan.orchestrator, registry, market.profileId, registration, adapter, recorded, envelope, block, chainId);
@@ -374,10 +374,10 @@ async function readPoolBoundHookDeploymentDetailsFresh(options: { client: Lifecy
374
374
  const deployerAbi = config.version === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
375
375
  const collectorAbi = config.version === 5 ? poolFeeCollectorFactoryV1Abi : poolFeeCollectorFactoryConfigV6Abi;
376
376
  const [[parameters, parameterSalt], [deployer, initCodeHash, salt, predictedHook], remoteInitCodeHash, remotePrediction] = await Promise.all([
377
- readLifecycleContract<readonly [PoolBoundHookParameters, Hex]>(client, boundGraph.collectorFactory, collectorAbi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
378
- readLifecycleContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block),
379
- readLifecycleContract<Hex>(client, boundGraph.deployer, deployerAbi, "initCodeHash", [expected], block),
380
- readLifecycleContract<Address>(client, boundGraph.deployer, deployerAbi, "predict", [expected, config.hookSalt], block),
377
+ readLifecycleStaticContract<readonly [PoolBoundHookParameters, Hex]>(client, boundGraph.collectorFactory, collectorAbi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
378
+ readLifecycleStaticContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block),
379
+ readLifecycleStaticContract<Hex>(client, boundGraph.deployer, deployerAbi, "initCodeHash", [expected], block),
380
+ readLifecycleStaticContract<Address>(client, boundGraph.deployer, deployerAbi, "predict", [expected, config.hookSalt], block),
381
381
  ]);
382
382
  const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected, config.version);
383
383
  const localPrediction = predictPoolBoundHookAddress({ deployer: boundGraph.deployer, initCodeHash: localInitCodeHash, salt: config.hookSalt });
@@ -385,7 +385,7 @@ async function readPoolBoundHookDeploymentDetailsFresh(options: { client: Lifecy
385
385
  const hookCode = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [predictedHook, toHex(block.number)], block), "bound hook code");
386
386
  if (hookCode !== "0x") {
387
387
  const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions, feePolicy] = await Promise.all([
388
- readLifecycleContract<Hex>(client, deployer, deployerAbi, "deployedCodeHash", [predictedHook], block),
388
+ readLifecycleStaticContract<Hex>(client, deployer, deployerAbi, "deployedCodeHash", [predictedHook], block),
389
389
  Promise.all((["poolManager", "registrar", "oracleFactory", "core", "liquidityLocker", "token"] as const).map((getter) => readLifecycleContract<Address>(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block))),
390
390
  readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block),
391
391
  readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block),