@black-market/sdk 0.6.3 → 0.7.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 (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 +355 -392
  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 +286 -118
  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 -1
  49. package/dist/lifecycle/simulation.d.ts.map +1 -1
  50. package/dist/lifecycle/simulation.js +400 -100
  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 +236 -39
  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 -321
  77. package/src/lifecycle/presets.ts +243 -0
  78. package/src/lifecycle/progress.ts +254 -102
  79. package/src/lifecycle/rpc.ts +97 -9
  80. package/src/lifecycle/simulation.ts +391 -94
  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. */
@@ -141,74 +222,126 @@ export async function validateV4LifecycleOracle(options: {
141
222
 
142
223
  export async function readLifecycleProfiles(options: { client: LifecycleRpcClient; orchestrator: Address; profileIds?: readonly Hex[]; offset?: bigint; limit?: bigint }, pinnedBlock?: LifecycleBlock): Promise<LifecycleProfile[]> {
143
224
  return withLifecycleReadClient(options.client, (client) => lifecycleStage(client, "profile.certification", async () => {
144
- const [block, chainIdValue] = await Promise.all([
145
- pinnedBlock ?? readLifecycleBlock(client),
146
- lifecycleRpc(client, "eth_chainId"),
147
- ]);
148
- const chainId = rpcQuantity(chainIdValue, "chain ID");
149
- const registry = await readLifecycleContract<Address>(client, options.orchestrator, launchLifecycleAbi, "registry", [], block);
150
- const offset = options.offset ?? 0n; const limit = options.limit ?? 100n;
151
- if (offset < 0n || limit < 0n || limit > 100n) throw new LifecyclePlanningError("INVALID_PROFILE_PAGE", "Registry profile pages require unsigned offset and limit at most 100");
152
- let protocolMaximumDeveloperFeeBpsPromise: Promise<number> | undefined;
153
- const readProtocolMaximumDeveloperFeeBps = () => protocolMaximumDeveloperFeeBpsPromise ??=
154
- readLifecycleContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
155
- const readProfile = async (id: Hex): Promise<LifecycleProfile> => {
156
- const registration = await readLifecycleContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [id], block);
157
- const schema = registration.configSchema.toLowerCase();
158
- 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";
159
- const [adapter, certification] = await Promise.all([
160
- readLifecycleContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block),
161
- venueKind === "unknown" ? undefined : Promise.all([
162
- readLifecycleProfileTopology(client, registry, id, block),
163
- venueKind === "uniswap-v4" ? Promise.all([
164
- readLifecycleContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block),
165
- readLifecycleContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
166
- readProtocolMaximumDeveloperFeeBps(),
167
- ]) : undefined,
168
- ]).then((values) => ({ values }), (failure: unknown) => ({ failure })),
169
- ]);
170
- let topology: ProfileTopologyV1 = { hookTopology: 0, configVersion: adapter.configVersion, hookDeployer: zeroAddress, hookCreationCodeHash: zeroHash };
171
- let envelope: LaunchEnvelopeV2 | undefined; let developerTerms: LifecycleDeveloperTerms | undefined;
172
- let protocolMaximumDeveloperFeeBps: number | undefined;
173
- let admitted = false; let reason: string | undefined;
174
- try {
175
- if (certification === undefined) throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
176
- if ("failure" in certification) throw certification.failure;
177
- const [recordedTopology, reviewed] = certification.values;
178
- topology = recordedTopology;
179
- if (reviewed !== undefined) {
180
- envelope = reviewed[0];
181
- protocolMaximumDeveloperFeeBps = reviewed[2];
182
- const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = reviewed[1];
183
- developerTerms = { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled };
184
- if (implementation.toLowerCase() !== adapter.implementation.toLowerCase() || beneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
185
- termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
186
- maximumDeveloperFeeBps > reviewed[2]) throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
187
- }
188
- await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
189
- if (developerTerms !== undefined && !developerTerms.enabled) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
190
- admitted = true;
191
- } catch (failure) { reason = failure instanceof Error ? failure.message : String(failure); }
192
- return { id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind };
193
- };
194
- const [authority, profiles] = await Promise.all([
195
- readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
196
- Promise.resolve(options.profileIds ?? readLifecycleContract<readonly Hex[]>(client, registry, lifecycleRegistryAbi, "profileIds", [offset, limit], block)).then((ids) => Promise.all(ids.map(readProfile))),
225
+ const chainIdPromise = lifecycleRpc(client, "eth_chainId").then((value) => rpcQuantity(value, "chain ID"));
226
+ // Block-bound discovery does not depend on chain identity. Join both branches
227
+ // immediately so a failed chain read cannot become detached or a profile refusal.
228
+ const [chainId, { block, profiles }] = await Promise.all([
229
+ chainIdPromise,
230
+ (async () => {
231
+ const block = pinnedBlock ?? await readLifecycleBlock(client);
232
+ const registry = await readLifecycleContract<Address>(client, options.orchestrator, launchLifecycleAbi, "registry", [], block);
233
+ const offset = options.offset ?? 0n; const limit = options.limit ?? 100n;
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
+ let protocolMaximumDeveloperFeeBpsPromise: Promise<number> | undefined;
236
+ const readProtocolMaximumDeveloperFeeBps = () => protocolMaximumDeveloperFeeBpsPromise ??=
237
+ readLifecycleContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
238
+ const readProfile = async (id: Hex): Promise<LifecycleProfile> => {
239
+ const registration = await readLifecycleContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [id], block);
240
+ const schema = registration.configSchema.toLowerCase();
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
+ const [adapter, certification] = await Promise.all([
243
+ readLifecycleContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block),
244
+ venueKind === "unknown" ? undefined : Promise.all([
245
+ readLifecycleProfileTopology(client, registry, id, block),
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),
249
+ readProtocolMaximumDeveloperFeeBps(),
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,
257
+ ]).then((values) => ({ values }), (failure: unknown) => ({ failure })),
258
+ ]);
259
+ const chainId = await chainIdPromise;
260
+ let topology: ProfileTopologyV1 = { hookTopology: 0, configVersion: adapter.configVersion, hookDeployer: zeroAddress, hookCreationCodeHash: zeroHash };
261
+ let envelope: LaunchEnvelopeV2 | undefined; let developerTerms: LifecycleDeveloperTerms | undefined;
262
+ let protocolMaximumDeveloperFeeBps: number | undefined;
263
+ let admitted = false; let reason: string | undefined;
264
+ try {
265
+ if (certification === undefined) throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
266
+ if ("failure" in certification) throw certification.failure;
267
+ const [recordedTopology, reviewed, eligibility] = certification.values;
268
+ topology = recordedTopology;
269
+ if (reviewed !== undefined) {
270
+ envelope = reviewed[0];
271
+ protocolMaximumDeveloperFeeBps = reviewed[2];
272
+ const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = reviewed[1];
273
+ developerTerms = { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled };
274
+ if (implementation.toLowerCase() !== adapter.implementation.toLowerCase() || beneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
275
+ termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
276
+ maximumDeveloperFeeBps > reviewed[2]) throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
277
+ }
278
+ await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
279
+ if (developerTerms !== undefined && !developerTerms.enabled) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
280
+ admitted = true;
281
+ } catch (failure) { reason = lifecycleFailureReason(failure); }
282
+ return { id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind };
283
+ };
284
+ const [authority, profiles] = await Promise.all([
285
+ readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
286
+ Promise.resolve(options.profileIds ?? readLifecycleContract<readonly Hex[]>(client, registry, lifecycleRegistryAbi, "profileIds", [offset, limit], block)).then((ids) => Promise.all(ids.map(readProfile))),
287
+ ]);
288
+ if (authority.toLowerCase() !== options.orchestrator.toLowerCase()) throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to the selected lifecycle orchestrator");
289
+ // A required V4 cap read still rejects the invocation on failure; Abyss has no developer-fee terms.
290
+ if (protocolMaximumDeveloperFeeBpsPromise !== undefined) await protocolMaximumDeveloperFeeBpsPromise;
291
+ return { block, profiles };
292
+ })(),
197
293
  ]);
198
- if (authority.toLowerCase() !== options.orchestrator.toLowerCase()) throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to the selected lifecycle orchestrator");
199
- // A required V4 cap read still rejects the invocation on failure; Abyss has no developer-fee terms.
200
- if (protocolMaximumDeveloperFeeBpsPromise !== undefined) await protocolMaximumDeveloperFeeBpsPromise;
201
294
  if (pinnedBlock === undefined) await assertLifecycleBlock(client, block, chainId);
202
295
  return profiles;
203
296
  }));
204
297
  }
205
298
 
206
- 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> {
207
300
  const { client, plan, marketIndex } = options;
208
301
  const market = plan.markets[marketIndex];
209
- if (market === undefined || market.configVersion !== 5) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market index must select reviewed pool-bound config version 5");
210
- 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");
211
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]!;
212
345
  const registry = await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "registry", [], block);
213
346
  const [authority, registration] = await Promise.all([
214
347
  readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
@@ -226,34 +359,33 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
226
359
  ]);
227
360
  const { boundGraph } = await certifyLifecycleProfile(client, plan.orchestrator, registry, market.profileId, registration, adapter, recorded, envelope, block, chainId);
228
361
  if (boundGraph === undefined) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Market does not have a certified pool-bound deployer graph");
229
- 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);
230
364
  validateV4LifecycleMarket({ market, config, token, profile: {
231
365
  id: market.profileId, registration, adapter, topology: recorded, envelope,
232
366
  developerTerms: { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled },
233
367
  protocolMaximumDeveloperFeeBps, venueKind: "uniswap-v4", admitted: enabled,
234
368
  } });
235
369
  await validateV4LifecycleOracle({ client, envelope, oracleConfigId: config.oracleConfigId, block });
236
- const expected: PoolBoundHookParametersV1 = {
237
- poolManager: boundGraph.manager, registrar: adapter.implementation, oracleFactory: boundGraph.oracleFactory, core: plan.orchestrator,
238
- liquidityLocker: boundGraph.locker, token, quoteCurrency: market.quoteAsset, lpFeePips: config.lpFeePips, tickSpacing: config.tickSpacing,
239
- sqrtPriceX96: config.sqrtPriceX96, hookFeePips: config.hookFeePips, feeMode: config.feeMode, protocolFeeDenominator: config.protocolFeeDenominator,
240
- treasury: config.treasury, externalLiquidityDisabled: config.externalLiquidityDisabled, oracleConfigId: config.oracleConfigId,
241
- marketCommitment: hashPoolBoundV4MarketCommitment({ chainId, core: plan.orchestrator, registrar: adapter.implementation, token, market }),
242
- expectedPositionCount: config.positions.length,
243
- };
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;
244
376
  const [[parameters, parameterSalt], [deployer, initCodeHash, salt, predictedHook], remoteInitCodeHash, remotePrediction] = await Promise.all([
245
- 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),
246
378
  readLifecycleContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block),
247
- readLifecycleContract<Hex>(client, boundGraph.deployer, poolHookDeployerV1Abi, "initCodeHash", [expected], block),
248
- 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),
249
381
  ]);
250
- const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected);
382
+ const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected, config.version);
251
383
  const localPrediction = predictPoolBoundHookAddress({ deployer: boundGraph.deployer, initCodeHash: localInitCodeHash, salt: config.hookSalt });
252
- 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");
253
385
  const hookCode = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [predictedHook, toHex(block.number)], block), "bound hook code");
254
386
  if (hookCode !== "0x") {
255
- const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions] = await Promise.all([
256
- 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),
257
389
  Promise.all((["poolManager", "registrar", "oracleFactory", "core", "liquidityLocker", "token"] as const).map((getter) => readLifecycleContract<Address>(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block))),
258
390
  readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block),
259
391
  readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block),
@@ -262,6 +394,10 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
262
394
  readLifecycleContract<Hex>(client, predictedHook, fixedFeePoolHookV1Abi, "marketCommitment", [], block),
263
395
  readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "openingSqrtPriceX96", [], block),
264
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,
265
401
  ]);
266
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");
267
403
  for (const [index, expectedAddress] of [expected.poolManager, expected.registrar, expected.oracleFactory, expected.core, expected.liquidityLocker, expected.token].entries()) {
@@ -270,20 +406,22 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
270
406
  if (requiredFlags !== 0x1afcn || allFlags !== 0x3fffn) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing bound hook callback declarations differ from the reviewed envelope");
271
407
  const [currency0, currency1] = BigInt(token) < BigInt(market.quoteAsset) ? [token, market.quoteAsset] : [market.quoteAsset, token];
272
408
  const expectedPoolId = keccak256(encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }], [currency0, currency1, config.lpFeePips, config.tickSpacing, predictedHook]));
273
- 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");
274
411
  }
275
- 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 });
276
413
  }
277
414
 
278
- /** 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. */
279
417
  export async function readPoolBoundHookDeployment(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, pinnedBlock?: LifecycleBlock): Promise<PoolBoundHookDeployment> {
280
418
  const commitment = hashLaunchPlan(options.plan);
281
419
  const block = pinnedBlock ?? await readLifecycleBlock(options.client);
282
420
  return withLifecycleReadClient(options.client, async (client) => {
283
- const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
421
+ const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block, commitment);
284
422
  if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
285
423
  if (pinnedBlock === undefined) await assertLifecycleBlock(client, block, options.plan.chainId);
286
- return deployment;
424
+ return { ...deployment };
287
425
  });
288
426
  }
289
427
 
@@ -292,11 +430,12 @@ export async function buildPoolBoundHookDeploymentTransaction(options: { client:
292
430
  const commitment = hashLaunchPlan(options.plan);
293
431
  const block = await readLifecycleBlock(options.client);
294
432
  return withLifecycleReadClient(options.client, async (client) => {
295
- const { deployment, parameters } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
433
+ const { deployment, parameters, configVersion } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block, commitment);
296
434
  if (!hasLifecycleV4HookPermissions(deployment.predictedHook)) throw new LifecyclePlanningError("INVALID_HOOK_BITS", "Finalize a salt with the exact lifecycle hook permissions before predeployment");
297
435
  if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while constructing hook deployment calldata");
298
436
  await assertLifecycleBlock(client, block, options.plan.chainId);
299
- 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 } };
300
439
  });
301
440
  }
302
441
 
@@ -382,28 +521,38 @@ async function readReceipt(reference: LifecycleReceiptReference, options: ReadLa
382
521
 
383
522
  /** Reads confirmation-bound canonical state and receipt provenance; local step counters are never used. */
384
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" });
385
525
  const block = pinnedBlock ?? await readLifecycleBlock(options.client);
386
526
  return withLifecycleReadClient(options.client, (client) => readLaunchProgressAtBlock(options, client, block, pinnedBlock === undefined));
387
527
  }
388
528
 
389
529
  async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, client: LifecycleRpcClient, block: LifecycleBlock, checkBlock: boolean): Promise<CanonicalLaunchProgress> {
390
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();
391
535
  const confirmationDepth = options.confirmations ?? planned.confirmations;
392
536
  if (!Number.isSafeInteger(confirmationDepth) || confirmationDepth < 1) throw new LifecyclePlanningError("INVALID_CONFIRMATIONS", "Confirmation depth must be a positive integer");
393
537
  const confirmedNumber = block.number >= BigInt(confirmationDepth - 1) ? block.number - BigInt(confirmationDepth - 1) : 0n;
394
- const confirmedBlock = confirmedNumber === block.number ? block : await readLifecycleBlock(client, toHex(confirmedNumber));
395
- const chainId = rpcQuantity(await lifecycleRpc(client, "eth_chainId"), "chain ID");
538
+ const [confirmedBlock, chainIdValue] = await Promise.all([
539
+ confirmedNumber === block.number ? block : readLifecycleBlock(client, toHex(confirmedNumber)),
540
+ readLifecycleChainId(client),
541
+ ]);
542
+ assertActive();
543
+ const chainId = chainIdValue;
396
544
  if (chainId !== planned.plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed launch");
397
545
  if (planned.account.toLowerCase() !== planned.plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
398
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");
399
547
  const [head, confirmedProgress, token, confirmedNonce, headNonce, pendingNonce] = await Promise.all([
400
548
  readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], block, planned.account),
401
549
  confirmedNumber === block.number ? undefined : readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], confirmedBlock, planned.account),
402
- readLifecycleContract<Address>(client, planned.plan.orchestrator, launchLifecycleAbi, "predictToken", [planned.plan], block),
550
+ readLifecycleTokenAtBlock(client, planned.plan, block),
403
551
  lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(confirmedBlock.number)]),
404
552
  confirmedNumber === block.number ? undefined : lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(block.number)]),
405
553
  lifecycleRpc(client, "eth_getTransactionCount", [planned.account, "pending"]),
406
554
  ]);
555
+ assertActive();
407
556
  const canonical = confirmedProgress ?? head;
408
557
  if (token.toLowerCase() !== planned.predictedToken.toLowerCase()) throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed for the committed token configuration");
409
558
  for (const progress of [canonical, head]) if (progress.phase !== LifecyclePhase.None) {
@@ -417,6 +566,7 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
417
566
  const progressParameters = [{ type: "tuple", components: launchProgressV1Components }] as const;
418
567
  const confirmationSafe = confirmedAccountNonce === headAccountNonce && pendingAccountNonce === headAccountNonce && keccak256(encodeAbiParameters(progressParameters, [canonical])) === keccak256(encodeAbiParameters(progressParameters, [head]));
419
568
  const receipts = await Promise.all((options.receipts ?? []).map((reference) => readReceipt({ ...reference, confirmations: Math.max(reference.confirmations ?? 1, confirmationDepth) }, { ...options, client }, block)));
569
+ assertActive();
420
570
  let markets: LifecycleMarketProgress[] = [];
421
571
  if (canonical.preparedMarkets > 0) {
422
572
  const directory = await readLifecycleContract<Address>(client, planned.plan.orchestrator, launchLifecycleAbi, "directory", [], confirmedBlock);
@@ -427,12 +577,14 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
427
577
  const live = await readLifecycleContract<MarketLiveStateV1>(client, adapter, lifecycleAdapterAbi, "readMarket", [planned.launchId, index], confirmedBlock);
428
578
  return { index, prepared, live };
429
579
  } catch (failure) {
430
- 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) };
431
582
  }
432
583
  }));
433
584
  }
434
585
  if (checkBlock) await assertLifecycleBlock(client, block, chainId);
435
586
  if (confirmedNumber !== block.number) await assertLifecycleBlock(client, confirmedBlock, chainId);
436
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();
437
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 };
438
590
  }