@black-market/sdk 0.6.4 → 0.7.1

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 (82) hide show
  1. package/README.md +28 -4
  2. package/dist/addresses.d.ts +1 -1
  3. package/dist/addresses.js +4 -4
  4. package/dist/launch.d.ts +61 -0
  5. package/dist/launch.d.ts.map +1 -1
  6. package/dist/launch.js +171 -6
  7. package/dist/launch.js.map +1 -1
  8. package/dist/lifecycle/abi.d.ts +1999 -253
  9. package/dist/lifecycle/abi.d.ts.map +1 -1
  10. package/dist/lifecycle/abi.js +43 -14
  11. package/dist/lifecycle/abi.js.map +1 -1
  12. package/dist/lifecycle/allocations.d.ts +11 -0
  13. package/dist/lifecycle/allocations.d.ts.map +1 -0
  14. package/dist/lifecycle/allocations.js +36 -0
  15. package/dist/lifecycle/allocations.js.map +1 -0
  16. package/dist/lifecycle/calibration.d.ts +19 -0
  17. package/dist/lifecycle/calibration.d.ts.map +1 -0
  18. package/dist/lifecycle/calibration.js +65 -0
  19. package/dist/lifecycle/calibration.js.map +1 -0
  20. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.d.ts +11 -0
  21. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.d.ts.map +1 -0
  22. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.js +10 -0
  23. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.js.map +1 -0
  24. package/dist/lifecycle/index.d.ts +4 -2
  25. package/dist/lifecycle/index.d.ts.map +1 -1
  26. package/dist/lifecycle/index.js +4 -2
  27. package/dist/lifecycle/index.js.map +1 -1
  28. package/dist/lifecycle/markets.d.ts +170 -6
  29. package/dist/lifecycle/markets.d.ts.map +1 -1
  30. package/dist/lifecycle/markets.js +164 -37
  31. package/dist/lifecycle/markets.js.map +1 -1
  32. package/dist/lifecycle/planner.d.ts +7 -14
  33. package/dist/lifecycle/planner.d.ts.map +1 -1
  34. package/dist/lifecycle/planner.js +354 -400
  35. package/dist/lifecycle/planner.js.map +1 -1
  36. package/dist/lifecycle/presets.d.ts +62 -0
  37. package/dist/lifecycle/presets.d.ts.map +1 -0
  38. package/dist/lifecycle/presets.js +195 -0
  39. package/dist/lifecycle/presets.js.map +1 -0
  40. package/dist/lifecycle/progress.d.ts +5 -1
  41. package/dist/lifecycle/progress.d.ts.map +1 -1
  42. package/dist/lifecycle/progress.js +212 -53
  43. package/dist/lifecycle/progress.js.map +1 -1
  44. package/dist/lifecycle/rpc.d.ts +13 -2
  45. package/dist/lifecycle/rpc.d.ts.map +1 -1
  46. package/dist/lifecycle/rpc.js +92 -8
  47. package/dist/lifecycle/rpc.js.map +1 -1
  48. package/dist/lifecycle/simulation.d.ts +14 -4
  49. package/dist/lifecycle/simulation.d.ts.map +1 -1
  50. package/dist/lifecycle/simulation.js +381 -118
  51. package/dist/lifecycle/simulation.js.map +1 -1
  52. package/dist/lifecycle/transactions.d.ts +6 -0
  53. package/dist/lifecycle/transactions.d.ts.map +1 -0
  54. package/dist/lifecycle/transactions.js +40 -0
  55. package/dist/lifecycle/transactions.js.map +1 -0
  56. package/dist/lifecycle/types.d.ts +39 -2
  57. package/dist/lifecycle/types.d.ts.map +1 -1
  58. package/dist/lifecycle/types.js.map +1 -1
  59. package/docs/launch-lifecycle.md +228 -54
  60. package/examples/README.md +108 -18
  61. package/examples/generate-lifecycle-commitment-fixture.mjs +3 -1
  62. package/examples/launch-example-support.mjs +1 -1
  63. package/examples/launch-example.mjs +73 -26
  64. package/examples/launch-lifecycle-smoke.mjs +13 -11
  65. package/examples/launch.mjs +12 -11
  66. package/examples/quickstart.mjs +1 -1
  67. package/package.json +1 -1
  68. package/src/addresses.ts +4 -4
  69. package/src/launch.ts +213 -6
  70. package/src/lifecycle/abi.ts +43 -14
  71. package/src/lifecycle/allocations.ts +35 -0
  72. package/src/lifecycle/calibration.ts +59 -0
  73. package/src/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.ts +11 -0
  74. package/src/lifecycle/index.ts +4 -2
  75. package/src/lifecycle/markets.ts +169 -35
  76. package/src/lifecycle/planner.ts +326 -330
  77. package/src/lifecycle/presets.ts +243 -0
  78. package/src/lifecycle/progress.ts +193 -50
  79. package/src/lifecycle/rpc.ts +97 -9
  80. package/src/lifecycle/simulation.ts +369 -111
  81. package/src/lifecycle/transactions.ts +34 -0
  82. package/src/lifecycle/types.ts +35 -3
@@ -1,20 +1,79 @@
1
1
  import { concatHex, decodeFunctionData, encodeAbiParameters, encodeFunctionData, keccak256, stringToHex, toHex, zeroAddress, zeroHash, type Address, type Hash, type Hex } from "viem";
2
- import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleOracleFactoryAbi, lifecycleRegistryAbi, poolFeeCollectorFactoryV1Abi, lifecycleV4HookAbi, lifecycleV4LockerAbi, poolHookDeployerV1Abi, sharedHookDeployerV1Abi, fixedFeePoolHookV1Abi, poolMarketAdapterV1Abi, sharedMarketAdapterV1Abi } from "./abi.js";
3
- import { decodePoolBoundV4LifecycleMarketConfig, encodePoolBoundHookParameters, hasLifecycleV4HookPermissions, hashPoolBoundV4MarketCommitment, poolBoundHookInitCodeHash, predictPoolBoundHookAddress, validateV4LifecycleMarket, V4_LIFECYCLE_CONFIG_SCHEMA, V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA, type PoolBoundHookParametersV1 } from "./markets.js";
4
- import { hashLaunchIdentity, hashLaunchPlan, hashLaunchBounds, hashLaunchDependencies, hashLifecycleProfile, 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, lifecyclePinnedRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleContract, readLifecycleProfileTopology, rpcHex, rpcObject, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
2
+ import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleOracleFactoryAbi, lifecycleRegistryAbi, poolFeeCollectorFactoryV1Abi, poolFeeCollectorFactoryConfigV6Abi, lifecycleV4HookAbi, lifecycleV4LockerAbi, poolHookDeployerV1Abi, poolHookDeployerConfigV6Abi, sharedHookDeployerV1Abi, fixedFeePoolHookV1Abi, fixedFeePoolHookConfigV6Abi, poolMarketAdapterV1Abi, sharedMarketAdapterV1Abi } from "./abi.js";
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
+ 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";
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
+ import { deriveKnownPoolBoundHookDeployment } from "./calibration.js";
8
+ import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
7
9
 
8
10
  export const ABYSS_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint8,uint24,bytes32,uint160,(int24,int24,uint128,uint256)[])"));
9
11
 
12
+ const lifecycleTokenPredictionAbi = [{
13
+ type: "function", name: "predictToken", stateMutability: "view",
14
+ inputs: [{ name: "launchId", type: "bytes32" }, { name: "config", type: "tuple", components: lifecycleTokenComponents }],
15
+ outputs: [{ type: "address" }],
16
+ }] as const;
17
+
18
+ /** The known immutable factory accepts only launch identity and token config,
19
+ * avoiding full-plan prediction reads after salts or buy minima change. */
20
+ export async function readLifecycleTokenAtBlock(client: LifecycleRpcClient, plan: LaunchPlanV1, block: LifecycleBlock): Promise<Address> {
21
+ const deployment = getKnownLifecycleDeployment({ chainId: plan.chainId, orchestrator: plan.orchestrator });
22
+ return deployment === undefined
23
+ ? readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "predictToken", [plan], block)
24
+ : readLifecycleContract<Address>(client, deployment.tokenFactory, lifecycleTokenPredictionAbi, "predictToken", [hashLaunchIdentity(plan), plan.token], block);
25
+ }
26
+
10
27
  export async function predictLifecycleToken(options: { client: LifecycleRpcClient; plan: LaunchPlanV1 }): Promise<Address> {
11
- const block = await readLifecycleBlock(options.client);
12
- const token = await readLifecycleContract<Address>(options.client, options.plan.orchestrator, launchLifecycleAbi, "predictToken", [options.plan], block);
13
- await assertLifecycleBlock(options.client, block);
14
- return token;
28
+ return withLifecycleReadClient(options.client, async (client) => {
29
+ const block = await readLifecycleBlock(client);
30
+ const token = await readLifecycleTokenAtBlock(client, options.plan, block);
31
+ await assertLifecycleBlock(client, block, options.plan.chainId);
32
+ return token;
33
+ });
15
34
  }
16
35
 
17
36
  type BoundProfileGraph = { manager: Address; oracleFactory: Address; locker: Address; collectorFactory: Address; deployer: Address; creationCode: Hex };
37
+ type LifecycleProfileCertificate = { readonly topology: Readonly<ProfileTopologyV1>; readonly boundGraph?: Readonly<BoundProfileGraph> };
38
+ type LifecycleEligibilityObservation = { eligible: Address } | { failure: unknown };
39
+ type BoundHookDeploymentDetails = { readonly deployment: Readonly<PoolBoundHookDeployment>; readonly parameters: Readonly<PoolBoundHookParameters>; readonly configVersion: PoolBoundLifecycleConfigVersion };
40
+ const certifiedProfilesByInvocation = new WeakMap<LifecycleRpcClient, Map<string, Promise<LifecycleProfileCertificate>>>();
41
+ const boundDeploymentsByInvocation = new WeakMap<LifecycleRpcClient, Map<string, Promise<BoundHookDeploymentDetails>>>();
42
+
43
+ function assertDerivedReadClientOpen(client: LifecycleRpcClient): boolean {
44
+ try { return assertLifecycleReadClientOpen(client); }
45
+ catch (failure) {
46
+ certifiedProfilesByInvocation.delete(client);
47
+ boundDeploymentsByInvocation.delete(client);
48
+ throw failure;
49
+ }
50
+ }
51
+
52
+ function lifecycleProfileCertificateKey(orchestrator: Address, registry: Address, id: Hex, registration: ProfileRegistrationV1, adapter: AdapterRegistrationV1, topology: ProfileTopologyV1, envelope: LaunchEnvelopeV2 | undefined, block: LifecycleBlock, chainId: bigint): string {
53
+ const g = envelope?.graph;
54
+ const b = envelope?.bounds;
55
+ // Fixed ABI field order preserves every scalar, including flags delegated to
56
+ // registry eligibility. Keys never rehash runtime, chunk or creation bytes.
57
+ return JSON.stringify([
58
+ block.number.toString(), block.hash.toLowerCase(), chainId.toString(), orchestrator.toLowerCase(), registry.toLowerCase(), id.toLowerCase(),
59
+ [registration.adapterId.toLowerCase(), registration.configSchema.toLowerCase(), registration.dependencyDigest.toLowerCase(),
60
+ registration.venue.toLowerCase(), registration.factory.toLowerCase(), registration.hook.toLowerCase(), registration.capabilities.toString(), registration.enabled],
61
+ [adapter.implementation.toLowerCase(), adapter.codeHash.toLowerCase(), adapter.capabilities.toString(), adapter.configVersion, adapter.enabled],
62
+ [topology.hookTopology, topology.configVersion, topology.hookDeployer.toLowerCase(), topology.hookCreationCodeHash.toLowerCase()],
63
+ envelope === undefined ? null : [
64
+ envelope.artifactDigest.toLowerCase(), envelope.reviewManifestDigest.toLowerCase(), envelope.configBoundsDigest.toLowerCase(), envelope.termsDigest.toLowerCase(),
65
+ envelope.topology, envelope.configVersion, envelope.economicVersion, envelope.capabilities.toString(), envelope.flags.toString(),
66
+ envelope.callbackFlags, envelope.callbackMask, envelope.protocolTreasury.toLowerCase(), envelope.protocolFeeDenominator,
67
+ envelope.beneficiary.toLowerCase(), envelope.maximumDeveloperFeeBps,
68
+ [b!.minimumTickSpacing, b!.maximumTickSpacing, b!.maximumPositions, b!.maximumOracleCardinality, b!.feeModeFlags],
69
+ [g!.manager.toLowerCase(), g!.hookRoot.toLowerCase(), g!.oracleFactory.toLowerCase(), g!.locker.toLowerCase(), g!.collectorFactory.toLowerCase(),
70
+ g!.collectorDeployer.toLowerCase(), g!.hookDeployer.toLowerCase(), g!.coreCodeHash.toLowerCase(), g!.managerCodeHash.toLowerCase(),
71
+ g!.hookRuntimeCodeHash.toLowerCase(), g!.oracleFactoryCodeHash.toLowerCase(), g!.lockerCodeHash.toLowerCase(), g!.collectorFactoryCodeHash.toLowerCase(),
72
+ g!.collectorDeployerCodeHash.toLowerCase(), g!.hookDeployerCodeHash.toLowerCase(), g!.hookCreationCodeHash.toLowerCase(),
73
+ g!.codeChunk0.toLowerCase(), g!.codeChunk0Hash.toLowerCase(), g!.codeChunk1.toLowerCase(), g!.codeChunk1Hash.toLowerCase(), g!.sharedHookSalt.toLowerCase()],
74
+ ],
75
+ ]);
76
+ }
18
77
 
19
78
  async function readDependencyCode(client: LifecycleRpcClient, address: Address, block: LifecycleBlock): Promise<Hex> {
20
79
  const code = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [address, toHex(block.number)], block), "lifecycle dependency code");
@@ -22,12 +81,33 @@ async function readDependencyCode(client: LifecycleRpcClient, address: Address,
22
81
  return code;
23
82
  }
24
83
 
25
- async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator: Address, registry: Address, id: Hex, registration: ProfileRegistrationV1, adapter: AdapterRegistrationV1, topology: ProfileTopologyV1, envelope: LaunchEnvelopeV2 | undefined, block: LifecycleBlock, chainId: bigint): Promise<{ topology: ProfileTopologyV1; boundGraph?: BoundProfileGraph }> {
84
+ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator: Address, registry: Address, id: Hex, registration: ProfileRegistrationV1, adapter: AdapterRegistrationV1, topology: ProfileTopologyV1, envelope: LaunchEnvelopeV2 | undefined, block: LifecycleBlock, chainId: bigint, eligibility?: LifecycleEligibilityObservation): Promise<LifecycleProfileCertificate> {
85
+ if (!assertDerivedReadClientOpen(client)) return certifyLifecycleProfileFresh(client, orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
86
+ const key = lifecycleProfileCertificateKey(orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
87
+ let certificates = certifiedProfilesByInvocation.get(client);
88
+ if (certificates === undefined) {
89
+ certificates = new Map();
90
+ certifiedProfilesByInvocation.set(client, certificates);
91
+ }
92
+ let pending = certificates.get(key);
93
+ if (pending === undefined) {
94
+ pending = certifyLifecycleProfileFresh(client, orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
95
+ certificates.set(key, pending);
96
+ const owned = pending;
97
+ void pending.catch(() => { if (certificates.get(key) === owned) certificates.delete(key); });
98
+ }
99
+ try { return await pending; }
100
+ finally { assertDerivedReadClientOpen(client); }
101
+ }
102
+
103
+ async function certifyLifecycleProfileFresh(client: LifecycleRpcClient, orchestrator: Address, registry: Address, id: Hex, registration: ProfileRegistrationV1, adapter: AdapterRegistrationV1, topology: ProfileTopologyV1, envelope: LaunchEnvelopeV2 | undefined, block: LifecycleBlock, chainId: bigint, eligibility?: LifecycleEligibilityObservation): Promise<LifecycleProfileCertificate> {
26
104
  const shared = registration.configSchema.toLowerCase() === V4_LIFECYCLE_CONFIG_SCHEMA.toLowerCase();
27
- const bound = registration.configSchema.toLowerCase() === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA.toLowerCase();
105
+ const boundVersion = registration.configSchema.toLowerCase() === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V5.toLowerCase() ? 5
106
+ : registration.configSchema.toLowerCase() === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6.toLowerCase() ? 6 : undefined;
107
+ const bound = boundVersion !== undefined;
28
108
  const abyss = registration.configSchema.toLowerCase() === ABYSS_LIFECYCLE_CONFIG_SCHEMA.toLowerCase();
29
109
  if (!shared && !bound && !abyss) throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
30
- const version = shared ? 4 : bound ? 5 : 1;
110
+ const version = shared ? 4 : boundVersion ?? 1;
31
111
  if (adapter.configVersion !== version || topology.configVersion !== version || topology.hookTopology !== (shared ? 1 : bound ? 2 : 0)) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Registry topology/version differs from the supported config schema");
32
112
  if (abyss) {
33
113
  if (topology.hookDeployer !== zeroAddress || topology.hookCreationCodeHash !== zeroHash) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Abyss does not admit a V4 hook deployment graph");
@@ -37,9 +117,10 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
37
117
  const domain = keccak256(stringToHex("BLACK_MARKET_ABYSS_CANONICAL_PROFILE_V1"));
38
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());
39
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");
40
- const eligible = await readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, version, LIFECYCLE_REQUIRED_CAPABILITIES], block);
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);
41
122
  if (eligible.toLowerCase() !== adapter.implementation.toLowerCase()) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
42
- return { topology };
123
+ return Object.freeze({ topology: Object.freeze({ ...topology }) });
43
124
  }
44
125
  if (envelope === undefined || envelope.topology !== topology.hookTopology || envelope.configVersion !== version || envelope.economicVersion !== 3 ||
45
126
  envelope.flags !== 0n || envelope.callbackFlags !== 0x1afc || envelope.callbackMask !== 0x3fff ||
@@ -59,7 +140,7 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
59
140
  const g = envelope.graph;
60
141
  if (registration.factory !== zeroAddress || registration.venue.toLowerCase() !== g.manager.toLowerCase() || registration.hook.toLowerCase() !== g.hookRoot.toLowerCase() ||
61
142
  topology.hookDeployer.toLowerCase() !== g.hookDeployer.toLowerCase() || topology.hookCreationCodeHash.toLowerCase() !== g.hookCreationCodeHash.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed venue/topology differs from the frozen envelope graph");
62
- const deployerAbi = shared ? sharedHookDeployerV1Abi : poolHookDeployerV1Abi;
143
+ const deployerAbi = shared ? sharedHookDeployerV1Abi : boundVersion === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
63
144
  const codeBindings = [[orchestrator, g.coreCodeHash], [g.manager, g.managerCodeHash], [g.oracleFactory, g.oracleFactoryCodeHash],
64
145
  [g.locker, g.lockerCodeHash], [g.collectorFactory, g.collectorFactoryCodeHash], [g.collectorDeployer, g.collectorDeployerCodeHash],
65
146
  [g.hookDeployer, g.hookDeployerCodeHash]] as const;
@@ -109,7 +190,7 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
109
190
  (chunk1 !== zeroAddress && (!chunk1Code.startsWith("0x00") || chunk1Code.length <= 4 || (chunk1Code.length - 2) / 2 > 24576 || (chunk0Code.length - 2) / 2 !== 24576)) ||
110
191
  keccak256(chunk0Code).toLowerCase() !== g.codeChunk0Hash.toLowerCase() || (chunk1 === zeroAddress ? zeroHash : keccak256(chunk1Code)).toLowerCase() !== g.codeChunk1Hash.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed deployer chunks differ from exact immutable STOP-prefixed bytecode evidence");
111
192
  const creationCode = concatHex([`0x${chunk0Code.slice(4)}`, chunk1 === zeroAddress ? "0x" : `0x${chunk1Code.slice(4)}`]);
112
- if ((creationCode.length - 2) / 2 + (shared ? 96 : 576) > 49152 || creationCodeHash === zeroHash ||
193
+ if ((creationCode.length - 2) / 2 + (shared ? 96 : boundVersion === 5 ? 576 : 640) > 49152 || creationCodeHash === zeroHash ||
113
194
  creationCodeHash.toLowerCase() !== g.hookCreationCodeHash.toLowerCase() || keccak256(creationCode).toLowerCase() !== creationCodeHash.toLowerCase() ||
114
195
  hashLaunchDependencies({ chainId, core: orchestrator, registry, registrar: adapter.implementation, graph: g }).toLowerCase() !== registration.dependencyDigest.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed creation artifact, constructor bound or graph digest differs from admission");
115
196
  if (sharedRoot !== undefined) {
@@ -124,7 +205,7 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
124
205
  if (requiredFlags !== 0x1afcn || allFlags !== 0x3fffn) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared hook callbacks differ from admission");
125
206
  } else if (g.hookRoot !== zeroAddress || g.hookRuntimeCodeHash !== zeroHash || g.sharedHookSalt !== zeroHash) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Bound topology cannot reuse a shared root");
126
207
  if ([zeroAddress, orchestrator, registry, adapter.implementation, g.manager, g.hookRoot, g.locker, g.collectorFactory, g.collectorDeployer, g.hookDeployer].some((address) => address.toLowerCase() === envelope.beneficiary.toLowerCase())) throw new LifecyclePlanningError("PROFILE_TERMS", "Stable author identity is an excluded reviewed graph destination");
127
- return { topology, boundGraph: bound ? { manager: g.manager, oracleFactory: g.oracleFactory, locker: g.locker, collectorFactory: g.collectorFactory, deployer: g.hookDeployer, creationCode } : undefined };
208
+ return Object.freeze({ topology: Object.freeze({ ...topology }), boundGraph: bound ? Object.freeze({ manager: g.manager, oracleFactory: g.oracleFactory, locker: g.locker, collectorFactory: g.collectorFactory, deployer: g.hookDeployer, creationCode }) : undefined });
128
209
  }
129
210
 
130
211
  /** Validate the market-selected registered oracle against the certified factory at the pinned block. */
@@ -157,7 +238,7 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
157
238
  const readProfile = async (id: Hex): Promise<LifecycleProfile> => {
158
239
  const registration = await readLifecycleContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [id], block);
159
240
  const schema = registration.configSchema.toLowerCase();
160
- const venueKind = schema === V4_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() || schema === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() ? "uniswap-v4" : schema === ABYSS_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() ? "abyss" : "unknown";
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";
161
242
  const [adapter, certification] = await Promise.all([
162
243
  readLifecycleContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block),
163
244
  venueKind === "unknown" ? undefined : Promise.all([
@@ -167,6 +248,12 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
167
248
  readLifecycleContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
168
249
  readProtocolMaximumDeveloperFeeBps(),
169
250
  ]) : undefined,
251
+ // The exact Abyss arguments follow from registration, independently
252
+ // of adapter/topology responses. Handle refusal immediately, but
253
+ // keep its original place after canonical/schema/topology checks.
254
+ venueKind === "abyss" ? chainIdPromise.then(() =>
255
+ readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, 1, LIFECYCLE_REQUIRED_CAPABILITIES], block)
256
+ ).then((eligible) => ({ eligible }), (failure: unknown) => ({ failure })) : undefined,
170
257
  ]).then((values) => ({ values }), (failure: unknown) => ({ failure })),
171
258
  ]);
172
259
  const chainId = await chainIdPromise;
@@ -177,7 +264,7 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
177
264
  try {
178
265
  if (certification === undefined) throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
179
266
  if ("failure" in certification) throw certification.failure;
180
- const [recordedTopology, reviewed] = certification.values;
267
+ const [recordedTopology, reviewed, eligibility] = certification.values;
181
268
  topology = recordedTopology;
182
269
  if (reviewed !== undefined) {
183
270
  envelope = reviewed[0];
@@ -188,10 +275,10 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
188
275
  termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
189
276
  maximumDeveloperFeeBps > reviewed[2]) throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
190
277
  }
191
- await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
278
+ await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
192
279
  if (developerTerms !== undefined && !developerTerms.enabled) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
193
280
  admitted = true;
194
- } catch (failure) { reason = failure instanceof Error ? failure.message : String(failure); }
281
+ } catch (failure) { reason = lifecycleFailureReason(failure); }
195
282
  return { id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind };
196
283
  };
197
284
  const [authority, profiles] = await Promise.all([
@@ -209,12 +296,52 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
209
296
  }));
210
297
  }
211
298
 
212
- async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, block: LifecycleBlock): Promise<{ deployment: PoolBoundHookDeployment; parameters: PoolBoundHookParametersV1 }> {
299
+ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, block: LifecycleBlock, commitment: Hex): Promise<BoundHookDeploymentDetails> {
213
300
  const { client, plan, marketIndex } = options;
214
301
  const market = plan.markets[marketIndex];
215
- if (market === undefined || market.configVersion !== 5) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market index must select reviewed pool-bound config version 5");
216
- const chainId = rpcQuantity(await lifecycleRpc(client, "eth_chainId"), "chain ID");
302
+ if (market === undefined || !isPoolBoundV4ConfigVersion(market.configVersion)) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market index must select explicit pool-bound config version 5 or 6");
303
+ let chainId: bigint;
304
+ try { chainId = rpcQuantity(await lifecycleRpc(client, "eth_chainId"), "chain ID"); }
305
+ finally { assertDerivedReadClientOpen(client); }
306
+ if (hashLaunchPlan(plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while observing the hook deployment chain");
217
307
  if (chainId !== plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed plan");
308
+ if (getKnownLifecycleProfile({ chainId, orchestrator: plan.orchestrator, profileId: market.profileId }) !== undefined) {
309
+ const token = await readLifecycleTokenAtBlock(client, plan, block);
310
+ const details = deriveKnownPoolBoundHookDeployment({ plan, marketIndex, token });
311
+ if (details === undefined) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Known profile has no bound-hook construction metadata");
312
+ if (hashLaunchPlan(plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while deriving hook deployment metadata");
313
+ assertDerivedReadClientOpen(client);
314
+ return Object.freeze({ deployment: Object.freeze(details.deployment), parameters: Object.freeze(details.parameters), configVersion: details.configVersion });
315
+ }
316
+ if (!assertDerivedReadClientOpen(client)) {
317
+ const details = await readPoolBoundHookDeploymentDetailsFresh(options, block, chainId);
318
+ if (hashLaunchPlan(plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
319
+ return details;
320
+ }
321
+ const key = JSON.stringify([block.number.toString(), block.hash.toLowerCase(), chainId.toString(), commitment.toLowerCase(), marketIndex]);
322
+ let deployments = boundDeploymentsByInvocation.get(client);
323
+ if (deployments === undefined) {
324
+ deployments = new Map();
325
+ boundDeploymentsByInvocation.set(client, deployments);
326
+ }
327
+ let pending = deployments.get(key);
328
+ if (pending === undefined) {
329
+ pending = readPoolBoundHookDeploymentDetailsFresh(options, block, chainId).then((details) => {
330
+ if (hashLaunchPlan(plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
331
+ assertDerivedReadClientOpen(client);
332
+ return details;
333
+ });
334
+ deployments.set(key, pending);
335
+ const owned = pending;
336
+ void pending.catch(() => { if (deployments.get(key) === owned) deployments.delete(key); });
337
+ }
338
+ try { return await pending; }
339
+ finally { assertDerivedReadClientOpen(client); }
340
+ }
341
+
342
+ async function readPoolBoundHookDeploymentDetailsFresh(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, block: LifecycleBlock, chainId: bigint): Promise<BoundHookDeploymentDetails> {
343
+ const { client, plan, marketIndex } = options;
344
+ const market = plan.markets[marketIndex]!;
218
345
  const registry = await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "registry", [], block);
219
346
  const [authority, registration] = await Promise.all([
220
347
  readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
@@ -232,34 +359,33 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
232
359
  ]);
233
360
  const { boundGraph } = await certifyLifecycleProfile(client, plan.orchestrator, registry, market.profileId, registration, adapter, recorded, envelope, block, chainId);
234
361
  if (boundGraph === undefined) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Market does not have a certified pool-bound deployer graph");
235
- const config = decodePoolBoundV4LifecycleMarketConfig(market.config);
362
+ if (!isPoolBoundV4ConfigVersion(market.configVersion)) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market must select an explicit pool-bound version");
363
+ const config = decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion);
236
364
  validateV4LifecycleMarket({ market, config, token, profile: {
237
365
  id: market.profileId, registration, adapter, topology: recorded, envelope,
238
366
  developerTerms: { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled },
239
367
  protocolMaximumDeveloperFeeBps, venueKind: "uniswap-v4", admitted: enabled,
240
368
  } });
241
369
  await validateV4LifecycleOracle({ client, envelope, oracleConfigId: config.oracleConfigId, block });
242
- const expected: PoolBoundHookParametersV1 = {
243
- poolManager: boundGraph.manager, registrar: adapter.implementation, oracleFactory: boundGraph.oracleFactory, core: plan.orchestrator,
244
- liquidityLocker: boundGraph.locker, token, quoteCurrency: market.quoteAsset, lpFeePips: config.lpFeePips, tickSpacing: config.tickSpacing,
245
- sqrtPriceX96: config.sqrtPriceX96, hookFeePips: config.hookFeePips, feeMode: config.feeMode, protocolFeeDenominator: config.protocolFeeDenominator,
246
- treasury: config.treasury, externalLiquidityDisabled: config.externalLiquidityDisabled, oracleConfigId: config.oracleConfigId,
247
- marketCommitment: hashPoolBoundV4MarketCommitment({ chainId, core: plan.orchestrator, registrar: adapter.implementation, token, market }),
248
- expectedPositionCount: config.positions.length,
249
- };
370
+ const expected = buildPoolBoundHookParameters({
371
+ chainId, core: plan.orchestrator, poolManager: boundGraph.manager, registrar: adapter.implementation,
372
+ oracleFactory: boundGraph.oracleFactory, liquidityLocker: boundGraph.locker, token, market, config,
373
+ });
374
+ const deployerAbi = config.version === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
375
+ const collectorAbi = config.version === 5 ? poolFeeCollectorFactoryV1Abi : poolFeeCollectorFactoryConfigV6Abi;
250
376
  const [[parameters, parameterSalt], [deployer, initCodeHash, salt, predictedHook], remoteInitCodeHash, remotePrediction] = await Promise.all([
251
- readLifecycleContract<readonly [PoolBoundHookParametersV1, Hex]>(client, boundGraph.collectorFactory, poolFeeCollectorFactoryV1Abi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
377
+ readLifecycleContract<readonly [PoolBoundHookParameters, Hex]>(client, boundGraph.collectorFactory, collectorAbi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
252
378
  readLifecycleContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block),
253
- readLifecycleContract<Hex>(client, boundGraph.deployer, poolHookDeployerV1Abi, "initCodeHash", [expected], block),
254
- readLifecycleContract<Address>(client, boundGraph.deployer, poolHookDeployerV1Abi, "predict", [expected, config.hookSalt], block),
379
+ readLifecycleContract<Hex>(client, boundGraph.deployer, deployerAbi, "initCodeHash", [expected], block),
380
+ readLifecycleContract<Address>(client, boundGraph.deployer, deployerAbi, "predict", [expected, config.hookSalt], block),
255
381
  ]);
256
- const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected);
382
+ const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected, config.version);
257
383
  const localPrediction = predictPoolBoundHookAddress({ deployer: boundGraph.deployer, initCodeHash: localInitCodeHash, salt: config.hookSalt });
258
- if (encodePoolBoundHookParameters(parameters).toLowerCase() !== encodePoolBoundHookParameters(expected).toLowerCase() || parameterSalt.toLowerCase() !== config.hookSalt.toLowerCase() || salt.toLowerCase() !== config.hookSalt.toLowerCase() || deployer.toLowerCase() !== boundGraph.deployer.toLowerCase() || initCodeHash.toLowerCase() !== localInitCodeHash.toLowerCase() || remoteInitCodeHash.toLowerCase() !== localInitCodeHash.toLowerCase() || predictedHook.toLowerCase() !== localPrediction.toLowerCase() || remotePrediction.toLowerCase() !== localPrediction.toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Bound hook metadata differs from the exact certified constructor economics, factory, initcode, salt or CREATE2 prediction");
384
+ if (encodePoolBoundHookParameters(parameters, config.version).toLowerCase() !== encodePoolBoundHookParameters(expected, config.version).toLowerCase() || parameterSalt.toLowerCase() !== config.hookSalt.toLowerCase() || salt.toLowerCase() !== config.hookSalt.toLowerCase() || deployer.toLowerCase() !== boundGraph.deployer.toLowerCase() || initCodeHash.toLowerCase() !== localInitCodeHash.toLowerCase() || remoteInitCodeHash.toLowerCase() !== localInitCodeHash.toLowerCase() || predictedHook.toLowerCase() !== localPrediction.toLowerCase() || remotePrediction.toLowerCase() !== localPrediction.toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Bound hook metadata differs from the exact certified version, constructor economics, factory, initcode, salt or CREATE2 prediction");
259
385
  const hookCode = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [predictedHook, toHex(block.number)], block), "bound hook code");
260
386
  if (hookCode !== "0x") {
261
- const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions] = await Promise.all([
262
- readLifecycleContract<Hex>(client, deployer, poolHookDeployerV1Abi, "deployedCodeHash", [predictedHook], block),
387
+ const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions, feePolicy] = await Promise.all([
388
+ readLifecycleContract<Hex>(client, deployer, deployerAbi, "deployedCodeHash", [predictedHook], block),
263
389
  Promise.all((["poolManager", "registrar", "oracleFactory", "core", "liquidityLocker", "token"] as const).map((getter) => readLifecycleContract<Address>(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block))),
264
390
  readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block),
265
391
  readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block),
@@ -268,6 +394,10 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
268
394
  readLifecycleContract<Hex>(client, predictedHook, fixedFeePoolHookV1Abi, "marketCommitment", [], block),
269
395
  readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "openingSqrtPriceX96", [], block),
270
396
  readLifecycleContract<number>(client, predictedHook, fixedFeePoolHookV1Abi, "expectedPositionCount", [], block),
397
+ config.version === 6 ? Promise.all([
398
+ readLifecycleContract<number>(client, predictedHook, fixedFeePoolHookConfigV6Abi, "minimumHookFeePips", [], block),
399
+ readLifecycleContract<number>(client, predictedHook, fixedFeePoolHookConfigV6Abi, "feeSensitivityPipsSecondsPerTick", [], block),
400
+ ]) : undefined,
271
401
  ]);
272
402
  if ((hookCode.length - 2) / 2 > 24576 || deployedCodeHash === zeroHash || deployedCodeHash.toLowerCase() !== keccak256(hookCode).toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook lacks this exact typed deployer's bounded runtime-code provenance");
273
403
  for (const [index, expectedAddress] of [expected.poolManager, expected.registrar, expected.oracleFactory, expected.core, expected.liquidityLocker, expected.token].entries()) {
@@ -276,20 +406,22 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
276
406
  if (requiredFlags !== 0x1afcn || allFlags !== 0x3fffn) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing bound hook callback declarations differ from the reviewed envelope");
277
407
  const [currency0, currency1] = BigInt(token) < BigInt(market.quoteAsset) ? [token, market.quoteAsset] : [market.quoteAsset, token];
278
408
  const expectedPoolId = keccak256(encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }], [currency0, currency1, config.lpFeePips, config.tickSpacing, predictedHook]));
279
- if (actualPoolId.toLowerCase() !== expectedPoolId.toLowerCase() || deploymentConfigHash.toLowerCase() !== keccak256(encodePoolBoundHookParameters(expected)).toLowerCase() || marketCommitment.toLowerCase() !== expected.marketCommitment.toLowerCase() || opening !== expected.sqrtPriceX96 || positions !== expected.expectedPositionCount) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook key, economic commitment or immutable opening geometry changed");
409
+ if (actualPoolId.toLowerCase() !== expectedPoolId.toLowerCase() || deploymentConfigHash.toLowerCase() !== keccak256(encodePoolBoundHookParameters(expected, config.version)).toLowerCase() || marketCommitment.toLowerCase() !== expected.marketCommitment.toLowerCase() || opening !== expected.sqrtPriceX96 || positions !== expected.expectedPositionCount ||
410
+ (config.version === 6 && (feePolicy?.[0] !== config.minimumHookFeePips || feePolicy?.[1] !== config.feeSensitivityPipsSecondsPerTick))) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook key, economic commitment, versioned fee policy or immutable opening geometry changed");
280
411
  }
281
- return { deployment: { deployer, initCodeHash, salt, predictedHook }, parameters: expected };
412
+ return Object.freeze({ deployment: Object.freeze({ deployer, initCodeHash, salt, predictedHook }), parameters: Object.freeze(expected), configVersion: config.version });
282
413
  }
283
414
 
284
- /** Works with an unmined candidate salt and after preparation; never accepts a caller-selected root. */
415
+ /** Known identities use frozen local construction data; unlisted identities use
416
+ * explicit live certification. Exact launch execution, not either DTO, authorizes. */
285
417
  export async function readPoolBoundHookDeployment(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, pinnedBlock?: LifecycleBlock): Promise<PoolBoundHookDeployment> {
286
418
  const commitment = hashLaunchPlan(options.plan);
287
419
  const block = pinnedBlock ?? await readLifecycleBlock(options.client);
288
420
  return withLifecycleReadClient(options.client, async (client) => {
289
- const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
421
+ const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block, commitment);
290
422
  if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
291
423
  if (pinnedBlock === undefined) await assertLifecycleBlock(client, block, options.plan.chainId);
292
- return deployment;
424
+ return { ...deployment };
293
425
  });
294
426
  }
295
427
 
@@ -298,11 +430,12 @@ export async function buildPoolBoundHookDeploymentTransaction(options: { client:
298
430
  const commitment = hashLaunchPlan(options.plan);
299
431
  const block = await readLifecycleBlock(options.client);
300
432
  return withLifecycleReadClient(options.client, async (client) => {
301
- const { deployment, parameters } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
433
+ const { deployment, parameters, configVersion } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block, commitment);
302
434
  if (!hasLifecycleV4HookPermissions(deployment.predictedHook)) throw new LifecyclePlanningError("INVALID_HOOK_BITS", "Finalize a salt with the exact lifecycle hook permissions before predeployment");
303
435
  if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while constructing hook deployment calldata");
304
436
  await assertLifecycleBlock(client, block, options.plan.chainId);
305
- return { to: deployment.deployer, data: encodeFunctionData({ abi: poolHookDeployerV1Abi, functionName: "deploy", args: [parameters, deployment.salt] }), value: 0n, deployment };
437
+ const abi = configVersion === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
438
+ return { to: deployment.deployer, data: encodeFunctionData({ abi, functionName: "deploy", args: [parameters, deployment.salt] }), value: 0n, deployment: { ...deployment } };
306
439
  });
307
440
  }
308
441
 
@@ -388,31 +521,38 @@ async function readReceipt(reference: LifecycleReceiptReference, options: ReadLa
388
521
 
389
522
  /** Reads confirmation-bound canonical state and receipt provenance; local step counters are never used. */
390
523
  export async function readLaunchProgress(options: ReadLaunchProgressOptions, pinnedBlock?: LifecycleBlock): Promise<CanonicalLaunchProgress> {
524
+ if (options.signal?.aborted) throw Object.assign(new Error("Lifecycle progress read cancelled"), { name: "AbortError" });
391
525
  const block = pinnedBlock ?? await readLifecycleBlock(options.client);
392
526
  return withLifecycleReadClient(options.client, (client) => readLaunchProgressAtBlock(options, client, block, pinnedBlock === undefined));
393
527
  }
394
528
 
395
529
  async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, client: LifecycleRpcClient, block: LifecycleBlock, checkBlock: boolean): Promise<CanonicalLaunchProgress> {
396
530
  const { planned } = options;
531
+ const assertActive = () => {
532
+ if (options.signal?.aborted) throw Object.assign(new Error("Lifecycle progress read cancelled"), { name: "AbortError" });
533
+ };
534
+ assertActive();
397
535
  const confirmationDepth = options.confirmations ?? planned.confirmations;
398
536
  if (!Number.isSafeInteger(confirmationDepth) || confirmationDepth < 1) throw new LifecyclePlanningError("INVALID_CONFIRMATIONS", "Confirmation depth must be a positive integer");
399
537
  const confirmedNumber = block.number >= BigInt(confirmationDepth - 1) ? block.number - BigInt(confirmationDepth - 1) : 0n;
400
538
  const [confirmedBlock, chainIdValue] = await Promise.all([
401
539
  confirmedNumber === block.number ? block : readLifecycleBlock(client, toHex(confirmedNumber)),
402
- lifecycleRpc(client, "eth_chainId"),
540
+ readLifecycleChainId(client),
403
541
  ]);
404
- const chainId = rpcQuantity(chainIdValue, "chain ID");
542
+ assertActive();
543
+ const chainId = chainIdValue;
405
544
  if (chainId !== planned.plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed launch");
406
545
  if (planned.account.toLowerCase() !== planned.plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
407
546
  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");
408
547
  const [head, confirmedProgress, token, confirmedNonce, headNonce, pendingNonce] = await Promise.all([
409
548
  readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], block, planned.account),
410
549
  confirmedNumber === block.number ? undefined : readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], confirmedBlock, planned.account),
411
- readLifecycleContract<Address>(client, planned.plan.orchestrator, launchLifecycleAbi, "predictToken", [planned.plan], block),
550
+ readLifecycleTokenAtBlock(client, planned.plan, block),
412
551
  lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(confirmedBlock.number)]),
413
552
  confirmedNumber === block.number ? undefined : lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(block.number)]),
414
553
  lifecycleRpc(client, "eth_getTransactionCount", [planned.account, "pending"]),
415
554
  ]);
555
+ assertActive();
416
556
  const canonical = confirmedProgress ?? head;
417
557
  if (token.toLowerCase() !== planned.predictedToken.toLowerCase()) throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed for the committed token configuration");
418
558
  for (const progress of [canonical, head]) if (progress.phase !== LifecyclePhase.None) {
@@ -426,6 +566,7 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
426
566
  const progressParameters = [{ type: "tuple", components: launchProgressV1Components }] as const;
427
567
  const confirmationSafe = confirmedAccountNonce === headAccountNonce && pendingAccountNonce === headAccountNonce && keccak256(encodeAbiParameters(progressParameters, [canonical])) === keccak256(encodeAbiParameters(progressParameters, [head]));
428
568
  const receipts = await Promise.all((options.receipts ?? []).map((reference) => readReceipt({ ...reference, confirmations: Math.max(reference.confirmations ?? 1, confirmationDepth) }, { ...options, client }, block)));
569
+ assertActive();
429
570
  let markets: LifecycleMarketProgress[] = [];
430
571
  if (canonical.preparedMarkets > 0) {
431
572
  const directory = await readLifecycleContract<Address>(client, planned.plan.orchestrator, launchLifecycleAbi, "directory", [], confirmedBlock);
@@ -436,12 +577,14 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
436
577
  const live = await readLifecycleContract<MarketLiveStateV1>(client, adapter, lifecycleAdapterAbi, "readMarket", [planned.launchId, index], confirmedBlock);
437
578
  return { index, prepared, live };
438
579
  } catch (failure) {
439
- return { index, prepared, error: failure instanceof Error ? failure.message : String(failure) };
580
+ if (failure instanceof Error && failure.name === "AbortError") throw failure;
581
+ return { index, prepared, error: lifecycleFailureReason(failure) };
440
582
  }
441
583
  }));
442
584
  }
443
585
  if (checkBlock) await assertLifecycleBlock(client, block, chainId);
444
586
  if (confirmedNumber !== block.number) await assertLifecycleBlock(client, confirmedBlock, chainId);
445
587
  if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan changed during asynchronous progress reads");
588
+ assertActive();
446
589
  return { canonical, head, confirmationDepth, confirmedBlockNumber: confirmedBlock.number, confirmedBlockHash: confirmedBlock.hash, confirmedAccountNonce, headAccountNonce, pendingAccountNonce, confirmationSafe, token, planHash: planned.planHash, launchId: planned.launchId, chainId, blockNumber: block.number, blockHash: block.hash, markets, receipts };
447
590
  }