@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,15 +1,69 @@
1
1
  import { concatHex, decodeFunctionData, encodeAbiParameters, encodeFunctionData, keccak256, stringToHex, toHex, zeroAddress, zeroHash } 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 } from "./markets.js";
4
- import { hashLaunchIdentity, hashLaunchPlan, hashLaunchBounds, hashLaunchDependencies, hashLifecycleProfile, launchProgressV1Components, LifecycleMode, LifecyclePhase, LifecycleVenue, LIFECYCLE_REQUIRED_CAPABILITIES, LIFECYCLE_ERC404_CAPABILITY, LIFECYCLE_MULTI_POSITION_CAPABILITY } 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 } 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 } 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 } from "./types.js";
7
+ import { deriveKnownPoolBoundHookDeployment } from "./calibration.js";
8
+ import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
7
9
  export const ABYSS_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint8,uint24,bytes32,uint160,(int24,int24,uint128,uint256)[])"));
10
+ const lifecycleTokenPredictionAbi = [{
11
+ type: "function", name: "predictToken", stateMutability: "view",
12
+ inputs: [{ name: "launchId", type: "bytes32" }, { name: "config", type: "tuple", components: lifecycleTokenComponents }],
13
+ outputs: [{ type: "address" }],
14
+ }];
15
+ /** The known immutable factory accepts only launch identity and token config,
16
+ * avoiding full-plan prediction reads after salts or buy minima change. */
17
+ export async function readLifecycleTokenAtBlock(client, plan, block) {
18
+ const deployment = getKnownLifecycleDeployment({ chainId: plan.chainId, orchestrator: plan.orchestrator });
19
+ return deployment === undefined
20
+ ? readLifecycleContract(client, plan.orchestrator, launchLifecycleAbi, "predictToken", [plan], block)
21
+ : readLifecycleContract(client, deployment.tokenFactory, lifecycleTokenPredictionAbi, "predictToken", [hashLaunchIdentity(plan), plan.token], block);
22
+ }
8
23
  export async function predictLifecycleToken(options) {
9
- const block = await readLifecycleBlock(options.client);
10
- const token = await readLifecycleContract(options.client, options.plan.orchestrator, launchLifecycleAbi, "predictToken", [options.plan], block);
11
- await assertLifecycleBlock(options.client, block);
12
- return token;
24
+ return withLifecycleReadClient(options.client, async (client) => {
25
+ const block = await readLifecycleBlock(client);
26
+ const token = await readLifecycleTokenAtBlock(client, options.plan, block);
27
+ await assertLifecycleBlock(client, block, options.plan.chainId);
28
+ return token;
29
+ });
30
+ }
31
+ const certifiedProfilesByInvocation = new WeakMap();
32
+ const boundDeploymentsByInvocation = new WeakMap();
33
+ function assertDerivedReadClientOpen(client) {
34
+ try {
35
+ return assertLifecycleReadClientOpen(client);
36
+ }
37
+ catch (failure) {
38
+ certifiedProfilesByInvocation.delete(client);
39
+ boundDeploymentsByInvocation.delete(client);
40
+ throw failure;
41
+ }
42
+ }
43
+ function lifecycleProfileCertificateKey(orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId) {
44
+ const g = envelope?.graph;
45
+ const b = envelope?.bounds;
46
+ // Fixed ABI field order preserves every scalar, including flags delegated to
47
+ // registry eligibility. Keys never rehash runtime, chunk or creation bytes.
48
+ return JSON.stringify([
49
+ block.number.toString(), block.hash.toLowerCase(), chainId.toString(), orchestrator.toLowerCase(), registry.toLowerCase(), id.toLowerCase(),
50
+ [registration.adapterId.toLowerCase(), registration.configSchema.toLowerCase(), registration.dependencyDigest.toLowerCase(),
51
+ registration.venue.toLowerCase(), registration.factory.toLowerCase(), registration.hook.toLowerCase(), registration.capabilities.toString(), registration.enabled],
52
+ [adapter.implementation.toLowerCase(), adapter.codeHash.toLowerCase(), adapter.capabilities.toString(), adapter.configVersion, adapter.enabled],
53
+ [topology.hookTopology, topology.configVersion, topology.hookDeployer.toLowerCase(), topology.hookCreationCodeHash.toLowerCase()],
54
+ envelope === undefined ? null : [
55
+ envelope.artifactDigest.toLowerCase(), envelope.reviewManifestDigest.toLowerCase(), envelope.configBoundsDigest.toLowerCase(), envelope.termsDigest.toLowerCase(),
56
+ envelope.topology, envelope.configVersion, envelope.economicVersion, envelope.capabilities.toString(), envelope.flags.toString(),
57
+ envelope.callbackFlags, envelope.callbackMask, envelope.protocolTreasury.toLowerCase(), envelope.protocolFeeDenominator,
58
+ envelope.beneficiary.toLowerCase(), envelope.maximumDeveloperFeeBps,
59
+ [b.minimumTickSpacing, b.maximumTickSpacing, b.maximumPositions, b.maximumOracleCardinality, b.feeModeFlags],
60
+ [g.manager.toLowerCase(), g.hookRoot.toLowerCase(), g.oracleFactory.toLowerCase(), g.locker.toLowerCase(), g.collectorFactory.toLowerCase(),
61
+ g.collectorDeployer.toLowerCase(), g.hookDeployer.toLowerCase(), g.coreCodeHash.toLowerCase(), g.managerCodeHash.toLowerCase(),
62
+ g.hookRuntimeCodeHash.toLowerCase(), g.oracleFactoryCodeHash.toLowerCase(), g.lockerCodeHash.toLowerCase(), g.collectorFactoryCodeHash.toLowerCase(),
63
+ g.collectorDeployerCodeHash.toLowerCase(), g.hookDeployerCodeHash.toLowerCase(), g.hookCreationCodeHash.toLowerCase(),
64
+ g.codeChunk0.toLowerCase(), g.codeChunk0Hash.toLowerCase(), g.codeChunk1.toLowerCase(), g.codeChunk1Hash.toLowerCase(), g.sharedHookSalt.toLowerCase()],
65
+ ],
66
+ ]);
13
67
  }
14
68
  async function readDependencyCode(client, address, block) {
15
69
  const code = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [address, toHex(block.number)], block), "lifecycle dependency code");
@@ -17,13 +71,39 @@ async function readDependencyCode(client, address, block) {
17
71
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", `Lifecycle dependency ${address} has no deployed code`);
18
72
  return code;
19
73
  }
20
- async function certifyLifecycleProfile(client, orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId) {
74
+ async function certifyLifecycleProfile(client, orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility) {
75
+ if (!assertDerivedReadClientOpen(client))
76
+ return certifyLifecycleProfileFresh(client, orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
77
+ const key = lifecycleProfileCertificateKey(orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
78
+ let certificates = certifiedProfilesByInvocation.get(client);
79
+ if (certificates === undefined) {
80
+ certificates = new Map();
81
+ certifiedProfilesByInvocation.set(client, certificates);
82
+ }
83
+ let pending = certificates.get(key);
84
+ if (pending === undefined) {
85
+ pending = certifyLifecycleProfileFresh(client, orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
86
+ certificates.set(key, pending);
87
+ const owned = pending;
88
+ void pending.catch(() => { if (certificates.get(key) === owned)
89
+ certificates.delete(key); });
90
+ }
91
+ try {
92
+ return await pending;
93
+ }
94
+ finally {
95
+ assertDerivedReadClientOpen(client);
96
+ }
97
+ }
98
+ async function certifyLifecycleProfileFresh(client, orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility) {
21
99
  const shared = registration.configSchema.toLowerCase() === V4_LIFECYCLE_CONFIG_SCHEMA.toLowerCase();
22
- const bound = registration.configSchema.toLowerCase() === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA.toLowerCase();
100
+ const boundVersion = registration.configSchema.toLowerCase() === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V5.toLowerCase() ? 5
101
+ : registration.configSchema.toLowerCase() === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6.toLowerCase() ? 6 : undefined;
102
+ const bound = boundVersion !== undefined;
23
103
  const abyss = registration.configSchema.toLowerCase() === ABYSS_LIFECYCLE_CONFIG_SCHEMA.toLowerCase();
24
104
  if (!shared && !bound && !abyss)
25
105
  throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
26
- const version = shared ? 4 : bound ? 5 : 1;
106
+ const version = shared ? 4 : boundVersion ?? 1;
27
107
  if (adapter.configVersion !== version || topology.configVersion !== version || topology.hookTopology !== (shared ? 1 : bound ? 2 : 0))
28
108
  throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Registry topology/version differs from the supported config schema");
29
109
  if (abyss) {
@@ -36,10 +116,12 @@ async function certifyLifecycleProfile(client, orchestrator, registry, id, regis
36
116
  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());
37
117
  if (!known || registration.venue.toLowerCase() !== factory.toLowerCase() || registration.hook !== zeroAddress)
38
118
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Abyss profile is not the exact canonical factory/variant/schema binding");
39
- const eligible = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, version, LIFECYCLE_REQUIRED_CAPABILITIES], block);
119
+ if (eligibility !== undefined && "failure" in eligibility)
120
+ throw eligibility.failure;
121
+ const eligible = eligibility?.eligible ?? await readLifecycleContract(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, version, LIFECYCLE_REQUIRED_CAPABILITIES], block);
40
122
  if (eligible.toLowerCase() !== adapter.implementation.toLowerCase())
41
123
  throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
42
- return { topology };
124
+ return Object.freeze({ topology: Object.freeze({ ...topology }) });
43
125
  }
44
126
  if (envelope === undefined || envelope.topology !== topology.hookTopology || envelope.configVersion !== version || envelope.economicVersion !== 3 ||
45
127
  envelope.flags !== 0n || envelope.callbackFlags !== 0x1afc || envelope.callbackMask !== 0x3fff ||
@@ -61,7 +143,7 @@ async function certifyLifecycleProfile(client, orchestrator, registry, id, regis
61
143
  if (registration.factory !== zeroAddress || registration.venue.toLowerCase() !== g.manager.toLowerCase() || registration.hook.toLowerCase() !== g.hookRoot.toLowerCase() ||
62
144
  topology.hookDeployer.toLowerCase() !== g.hookDeployer.toLowerCase() || topology.hookCreationCodeHash.toLowerCase() !== g.hookCreationCodeHash.toLowerCase())
63
145
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed venue/topology differs from the frozen envelope graph");
64
- const deployerAbi = shared ? sharedHookDeployerV1Abi : poolHookDeployerV1Abi;
146
+ const deployerAbi = shared ? sharedHookDeployerV1Abi : boundVersion === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
65
147
  const codeBindings = [[orchestrator, g.coreCodeHash], [g.manager, g.managerCodeHash], [g.oracleFactory, g.oracleFactoryCodeHash],
66
148
  [g.locker, g.lockerCodeHash], [g.collectorFactory, g.collectorFactoryCodeHash], [g.collectorDeployer, g.collectorDeployerCodeHash],
67
149
  [g.hookDeployer, g.hookDeployerCodeHash]];
@@ -118,7 +200,7 @@ async function certifyLifecycleProfile(client, orchestrator, registry, id, regis
118
200
  keccak256(chunk0Code).toLowerCase() !== g.codeChunk0Hash.toLowerCase() || (chunk1 === zeroAddress ? zeroHash : keccak256(chunk1Code)).toLowerCase() !== g.codeChunk1Hash.toLowerCase())
119
201
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed deployer chunks differ from exact immutable STOP-prefixed bytecode evidence");
120
202
  const creationCode = concatHex([`0x${chunk0Code.slice(4)}`, chunk1 === zeroAddress ? "0x" : `0x${chunk1Code.slice(4)}`]);
121
- if ((creationCode.length - 2) / 2 + (shared ? 96 : 576) > 49152 || creationCodeHash === zeroHash ||
203
+ if ((creationCode.length - 2) / 2 + (shared ? 96 : boundVersion === 5 ? 576 : 640) > 49152 || creationCodeHash === zeroHash ||
122
204
  creationCodeHash.toLowerCase() !== g.hookCreationCodeHash.toLowerCase() || keccak256(creationCode).toLowerCase() !== creationCodeHash.toLowerCase() ||
123
205
  hashLaunchDependencies({ chainId, core: orchestrator, registry, registrar: adapter.implementation, graph: g }).toLowerCase() !== registration.dependencyDigest.toLowerCase())
124
206
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed creation artifact, constructor bound or graph digest differs from admission");
@@ -140,7 +222,7 @@ async function certifyLifecycleProfile(client, orchestrator, registry, id, regis
140
222
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Bound topology cannot reuse a shared root");
141
223
  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()))
142
224
  throw new LifecyclePlanningError("PROFILE_TERMS", "Stable author identity is an excluded reviewed graph destination");
143
- return { topology, boundGraph: bound ? { manager: g.manager, oracleFactory: g.oracleFactory, locker: g.locker, collectorFactory: g.collectorFactory, deployer: g.hookDeployer, creationCode } : undefined };
225
+ 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 });
144
226
  }
145
227
  /** Validate the market-selected registered oracle against the certified factory at the pinned block. */
146
228
  export async function validateV4LifecycleOracle(options) {
@@ -154,89 +236,152 @@ export async function validateV4LifecycleOracle(options) {
154
236
  }
155
237
  export async function readLifecycleProfiles(options, pinnedBlock) {
156
238
  return withLifecycleReadClient(options.client, (client) => lifecycleStage(client, "profile.certification", async () => {
157
- const [block, chainIdValue] = await Promise.all([
158
- pinnedBlock ?? readLifecycleBlock(client),
159
- lifecycleRpc(client, "eth_chainId"),
160
- ]);
161
- const chainId = rpcQuantity(chainIdValue, "chain ID");
162
- const registry = await readLifecycleContract(client, options.orchestrator, launchLifecycleAbi, "registry", [], block);
163
- const offset = options.offset ?? 0n;
164
- const limit = options.limit ?? 100n;
165
- if (offset < 0n || limit < 0n || limit > 100n)
166
- throw new LifecyclePlanningError("INVALID_PROFILE_PAGE", "Registry profile pages require unsigned offset and limit at most 100");
167
- let protocolMaximumDeveloperFeeBpsPromise;
168
- const readProtocolMaximumDeveloperFeeBps = () => protocolMaximumDeveloperFeeBpsPromise ??=
169
- readLifecycleContract(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
170
- const readProfile = async (id) => {
171
- const registration = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "profile", [id], block);
172
- const schema = registration.configSchema.toLowerCase();
173
- 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";
174
- const [adapter, certification] = await Promise.all([
175
- readLifecycleContract(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block),
176
- venueKind === "unknown" ? undefined : Promise.all([
177
- readLifecycleProfileTopology(client, registry, id, block),
178
- venueKind === "uniswap-v4" ? Promise.all([
179
- readLifecycleContract(client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block),
180
- readLifecycleContract(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
181
- readProtocolMaximumDeveloperFeeBps(),
182
- ]) : undefined,
183
- ]).then((values) => ({ values }), (failure) => ({ failure })),
184
- ]);
185
- let topology = { hookTopology: 0, configVersion: adapter.configVersion, hookDeployer: zeroAddress, hookCreationCodeHash: zeroHash };
186
- let envelope;
187
- let developerTerms;
188
- let protocolMaximumDeveloperFeeBps;
189
- let admitted = false;
190
- let reason;
191
- try {
192
- if (certification === undefined)
193
- throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
194
- if ("failure" in certification)
195
- throw certification.failure;
196
- const [recordedTopology, reviewed] = certification.values;
197
- topology = recordedTopology;
198
- if (reviewed !== undefined) {
199
- envelope = reviewed[0];
200
- protocolMaximumDeveloperFeeBps = reviewed[2];
201
- const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = reviewed[1];
202
- developerTerms = { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled };
203
- if (implementation.toLowerCase() !== adapter.implementation.toLowerCase() || beneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
204
- termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
205
- maximumDeveloperFeeBps > reviewed[2])
206
- throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
207
- }
208
- await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
209
- if (developerTerms !== undefined && !developerTerms.enabled)
210
- throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
211
- admitted = true;
212
- }
213
- catch (failure) {
214
- reason = failure instanceof Error ? failure.message : String(failure);
215
- }
216
- return { id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind };
217
- };
218
- const [authority, profiles] = await Promise.all([
219
- readLifecycleContract(client, registry, lifecycleRegistryAbi, "core", [], block),
220
- Promise.resolve(options.profileIds ?? readLifecycleContract(client, registry, lifecycleRegistryAbi, "profileIds", [offset, limit], block)).then((ids) => Promise.all(ids.map(readProfile))),
239
+ const chainIdPromise = lifecycleRpc(client, "eth_chainId").then((value) => rpcQuantity(value, "chain ID"));
240
+ // Block-bound discovery does not depend on chain identity. Join both branches
241
+ // immediately so a failed chain read cannot become detached or a profile refusal.
242
+ const [chainId, { block, profiles }] = await Promise.all([
243
+ chainIdPromise,
244
+ (async () => {
245
+ const block = pinnedBlock ?? await readLifecycleBlock(client);
246
+ const registry = await readLifecycleContract(client, options.orchestrator, launchLifecycleAbi, "registry", [], block);
247
+ const offset = options.offset ?? 0n;
248
+ const limit = options.limit ?? 100n;
249
+ if (offset < 0n || limit < 0n || limit > 100n)
250
+ throw new LifecyclePlanningError("INVALID_PROFILE_PAGE", "Registry profile pages require unsigned offset and limit at most 100");
251
+ let protocolMaximumDeveloperFeeBpsPromise;
252
+ const readProtocolMaximumDeveloperFeeBps = () => protocolMaximumDeveloperFeeBpsPromise ??=
253
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
254
+ const readProfile = async (id) => {
255
+ const registration = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "profile", [id], block);
256
+ const schema = registration.configSchema.toLowerCase();
257
+ 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";
258
+ const [adapter, certification] = await Promise.all([
259
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block),
260
+ venueKind === "unknown" ? undefined : Promise.all([
261
+ readLifecycleProfileTopology(client, registry, id, block),
262
+ venueKind === "uniswap-v4" ? Promise.all([
263
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block),
264
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
265
+ readProtocolMaximumDeveloperFeeBps(),
266
+ ]) : undefined,
267
+ // The exact Abyss arguments follow from registration, independently
268
+ // of adapter/topology responses. Handle refusal immediately, but
269
+ // keep its original place after canonical/schema/topology checks.
270
+ venueKind === "abyss" ? chainIdPromise.then(() => readLifecycleContract(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, 1, LIFECYCLE_REQUIRED_CAPABILITIES], block)).then((eligible) => ({ eligible }), (failure) => ({ failure })) : undefined,
271
+ ]).then((values) => ({ values }), (failure) => ({ failure })),
272
+ ]);
273
+ const chainId = await chainIdPromise;
274
+ let topology = { hookTopology: 0, configVersion: adapter.configVersion, hookDeployer: zeroAddress, hookCreationCodeHash: zeroHash };
275
+ let envelope;
276
+ let developerTerms;
277
+ let protocolMaximumDeveloperFeeBps;
278
+ let admitted = false;
279
+ let reason;
280
+ try {
281
+ if (certification === undefined)
282
+ throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
283
+ if ("failure" in certification)
284
+ throw certification.failure;
285
+ const [recordedTopology, reviewed, eligibility] = certification.values;
286
+ topology = recordedTopology;
287
+ if (reviewed !== undefined) {
288
+ envelope = reviewed[0];
289
+ protocolMaximumDeveloperFeeBps = reviewed[2];
290
+ const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = reviewed[1];
291
+ developerTerms = { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled };
292
+ if (implementation.toLowerCase() !== adapter.implementation.toLowerCase() || beneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
293
+ termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
294
+ maximumDeveloperFeeBps > reviewed[2])
295
+ throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
296
+ }
297
+ await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
298
+ if (developerTerms !== undefined && !developerTerms.enabled)
299
+ throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
300
+ admitted = true;
301
+ }
302
+ catch (failure) {
303
+ reason = lifecycleFailureReason(failure);
304
+ }
305
+ return { id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind };
306
+ };
307
+ const [authority, profiles] = await Promise.all([
308
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "core", [], block),
309
+ Promise.resolve(options.profileIds ?? readLifecycleContract(client, registry, lifecycleRegistryAbi, "profileIds", [offset, limit], block)).then((ids) => Promise.all(ids.map(readProfile))),
310
+ ]);
311
+ if (authority.toLowerCase() !== options.orchestrator.toLowerCase())
312
+ throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to the selected lifecycle orchestrator");
313
+ // A required V4 cap read still rejects the invocation on failure; Abyss has no developer-fee terms.
314
+ if (protocolMaximumDeveloperFeeBpsPromise !== undefined)
315
+ await protocolMaximumDeveloperFeeBpsPromise;
316
+ return { block, profiles };
317
+ })(),
221
318
  ]);
222
- if (authority.toLowerCase() !== options.orchestrator.toLowerCase())
223
- throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to the selected lifecycle orchestrator");
224
- // A required V4 cap read still rejects the invocation on failure; Abyss has no developer-fee terms.
225
- if (protocolMaximumDeveloperFeeBpsPromise !== undefined)
226
- await protocolMaximumDeveloperFeeBpsPromise;
227
319
  if (pinnedBlock === undefined)
228
320
  await assertLifecycleBlock(client, block, chainId);
229
321
  return profiles;
230
322
  }));
231
323
  }
232
- async function readPoolBoundHookDeploymentDetails(options, block) {
324
+ async function readPoolBoundHookDeploymentDetails(options, block, commitment) {
233
325
  const { client, plan, marketIndex } = options;
234
326
  const market = plan.markets[marketIndex];
235
- if (market === undefined || market.configVersion !== 5)
236
- throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market index must select reviewed pool-bound config version 5");
237
- const chainId = rpcQuantity(await lifecycleRpc(client, "eth_chainId"), "chain ID");
327
+ if (market === undefined || !isPoolBoundV4ConfigVersion(market.configVersion))
328
+ throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market index must select explicit pool-bound config version 5 or 6");
329
+ let chainId;
330
+ try {
331
+ chainId = rpcQuantity(await lifecycleRpc(client, "eth_chainId"), "chain ID");
332
+ }
333
+ finally {
334
+ assertDerivedReadClientOpen(client);
335
+ }
336
+ if (hashLaunchPlan(plan) !== commitment)
337
+ throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while observing the hook deployment chain");
238
338
  if (chainId !== plan.chainId)
239
339
  throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed plan");
340
+ if (getKnownLifecycleProfile({ chainId, orchestrator: plan.orchestrator, profileId: market.profileId }) !== undefined) {
341
+ const token = await readLifecycleTokenAtBlock(client, plan, block);
342
+ const details = deriveKnownPoolBoundHookDeployment({ plan, marketIndex, token });
343
+ if (details === undefined)
344
+ throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Known profile has no bound-hook construction metadata");
345
+ if (hashLaunchPlan(plan) !== commitment)
346
+ throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while deriving hook deployment metadata");
347
+ assertDerivedReadClientOpen(client);
348
+ return Object.freeze({ deployment: Object.freeze(details.deployment), parameters: Object.freeze(details.parameters), configVersion: details.configVersion });
349
+ }
350
+ if (!assertDerivedReadClientOpen(client)) {
351
+ const details = await readPoolBoundHookDeploymentDetailsFresh(options, block, chainId);
352
+ if (hashLaunchPlan(plan) !== commitment)
353
+ throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
354
+ return details;
355
+ }
356
+ const key = JSON.stringify([block.number.toString(), block.hash.toLowerCase(), chainId.toString(), commitment.toLowerCase(), marketIndex]);
357
+ let deployments = boundDeploymentsByInvocation.get(client);
358
+ if (deployments === undefined) {
359
+ deployments = new Map();
360
+ boundDeploymentsByInvocation.set(client, deployments);
361
+ }
362
+ let pending = deployments.get(key);
363
+ if (pending === undefined) {
364
+ pending = readPoolBoundHookDeploymentDetailsFresh(options, block, chainId).then((details) => {
365
+ if (hashLaunchPlan(plan) !== commitment)
366
+ throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
367
+ assertDerivedReadClientOpen(client);
368
+ return details;
369
+ });
370
+ deployments.set(key, pending);
371
+ const owned = pending;
372
+ void pending.catch(() => { if (deployments.get(key) === owned)
373
+ deployments.delete(key); });
374
+ }
375
+ try {
376
+ return await pending;
377
+ }
378
+ finally {
379
+ assertDerivedReadClientOpen(client);
380
+ }
381
+ }
382
+ async function readPoolBoundHookDeploymentDetailsFresh(options, block, chainId) {
383
+ const { client, plan, marketIndex } = options;
384
+ const market = plan.markets[marketIndex];
240
385
  const registry = await readLifecycleContract(client, plan.orchestrator, launchLifecycleAbi, "registry", [], block);
241
386
  const [authority, registration] = await Promise.all([
242
387
  readLifecycleContract(client, registry, lifecycleRegistryAbi, "core", [], block),
@@ -257,35 +402,35 @@ async function readPoolBoundHookDeploymentDetails(options, block) {
257
402
  const { boundGraph } = await certifyLifecycleProfile(client, plan.orchestrator, registry, market.profileId, registration, adapter, recorded, envelope, block, chainId);
258
403
  if (boundGraph === undefined)
259
404
  throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Market does not have a certified pool-bound deployer graph");
260
- const config = decodePoolBoundV4LifecycleMarketConfig(market.config);
405
+ if (!isPoolBoundV4ConfigVersion(market.configVersion))
406
+ throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market must select an explicit pool-bound version");
407
+ const config = decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion);
261
408
  validateV4LifecycleMarket({ market, config, token, profile: {
262
409
  id: market.profileId, registration, adapter, topology: recorded, envelope,
263
410
  developerTerms: { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled },
264
411
  protocolMaximumDeveloperFeeBps, venueKind: "uniswap-v4", admitted: enabled,
265
412
  } });
266
413
  await validateV4LifecycleOracle({ client, envelope, oracleConfigId: config.oracleConfigId, block });
267
- const expected = {
268
- poolManager: boundGraph.manager, registrar: adapter.implementation, oracleFactory: boundGraph.oracleFactory, core: plan.orchestrator,
269
- liquidityLocker: boundGraph.locker, token, quoteCurrency: market.quoteAsset, lpFeePips: config.lpFeePips, tickSpacing: config.tickSpacing,
270
- sqrtPriceX96: config.sqrtPriceX96, hookFeePips: config.hookFeePips, feeMode: config.feeMode, protocolFeeDenominator: config.protocolFeeDenominator,
271
- treasury: config.treasury, externalLiquidityDisabled: config.externalLiquidityDisabled, oracleConfigId: config.oracleConfigId,
272
- marketCommitment: hashPoolBoundV4MarketCommitment({ chainId, core: plan.orchestrator, registrar: adapter.implementation, token, market }),
273
- expectedPositionCount: config.positions.length,
274
- };
414
+ const expected = buildPoolBoundHookParameters({
415
+ chainId, core: plan.orchestrator, poolManager: boundGraph.manager, registrar: adapter.implementation,
416
+ oracleFactory: boundGraph.oracleFactory, liquidityLocker: boundGraph.locker, token, market, config,
417
+ });
418
+ const deployerAbi = config.version === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
419
+ const collectorAbi = config.version === 5 ? poolFeeCollectorFactoryV1Abi : poolFeeCollectorFactoryConfigV6Abi;
275
420
  const [[parameters, parameterSalt], [deployer, initCodeHash, salt, predictedHook], remoteInitCodeHash, remotePrediction] = await Promise.all([
276
- readLifecycleContract(client, boundGraph.collectorFactory, poolFeeCollectorFactoryV1Abi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
421
+ readLifecycleContract(client, boundGraph.collectorFactory, collectorAbi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
277
422
  readLifecycleContract(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block),
278
- readLifecycleContract(client, boundGraph.deployer, poolHookDeployerV1Abi, "initCodeHash", [expected], block),
279
- readLifecycleContract(client, boundGraph.deployer, poolHookDeployerV1Abi, "predict", [expected, config.hookSalt], block),
423
+ readLifecycleContract(client, boundGraph.deployer, deployerAbi, "initCodeHash", [expected], block),
424
+ readLifecycleContract(client, boundGraph.deployer, deployerAbi, "predict", [expected, config.hookSalt], block),
280
425
  ]);
281
- const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected);
426
+ const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected, config.version);
282
427
  const localPrediction = predictPoolBoundHookAddress({ deployer: boundGraph.deployer, initCodeHash: localInitCodeHash, salt: config.hookSalt });
283
- 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())
284
- throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Bound hook metadata differs from the exact certified constructor economics, factory, initcode, salt or CREATE2 prediction");
428
+ 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())
429
+ throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Bound hook metadata differs from the exact certified version, constructor economics, factory, initcode, salt or CREATE2 prediction");
285
430
  const hookCode = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [predictedHook, toHex(block.number)], block), "bound hook code");
286
431
  if (hookCode !== "0x") {
287
- const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions] = await Promise.all([
288
- readLifecycleContract(client, deployer, poolHookDeployerV1Abi, "deployedCodeHash", [predictedHook], block),
432
+ const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions, feePolicy] = await Promise.all([
433
+ readLifecycleContract(client, deployer, deployerAbi, "deployedCodeHash", [predictedHook], block),
289
434
  Promise.all(["poolManager", "registrar", "oracleFactory", "core", "liquidityLocker", "token"].map((getter) => readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block))),
290
435
  readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block),
291
436
  readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block),
@@ -294,6 +439,10 @@ async function readPoolBoundHookDeploymentDetails(options, block) {
294
439
  readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "marketCommitment", [], block),
295
440
  readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "openingSqrtPriceX96", [], block),
296
441
  readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "expectedPositionCount", [], block),
442
+ config.version === 6 ? Promise.all([
443
+ readLifecycleContract(client, predictedHook, fixedFeePoolHookConfigV6Abi, "minimumHookFeePips", [], block),
444
+ readLifecycleContract(client, predictedHook, fixedFeePoolHookConfigV6Abi, "feeSensitivityPipsSecondsPerTick", [], block),
445
+ ]) : undefined,
297
446
  ]);
298
447
  if ((hookCode.length - 2) / 2 > 24576 || deployedCodeHash === zeroHash || deployedCodeHash.toLowerCase() !== keccak256(hookCode).toLowerCase())
299
448
  throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook lacks this exact typed deployer's bounded runtime-code provenance");
@@ -305,22 +454,24 @@ async function readPoolBoundHookDeploymentDetails(options, block) {
305
454
  throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing bound hook callback declarations differ from the reviewed envelope");
306
455
  const [currency0, currency1] = BigInt(token) < BigInt(market.quoteAsset) ? [token, market.quoteAsset] : [market.quoteAsset, token];
307
456
  const expectedPoolId = keccak256(encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }], [currency0, currency1, config.lpFeePips, config.tickSpacing, predictedHook]));
308
- if (actualPoolId.toLowerCase() !== expectedPoolId.toLowerCase() || deploymentConfigHash.toLowerCase() !== keccak256(encodePoolBoundHookParameters(expected)).toLowerCase() || marketCommitment.toLowerCase() !== expected.marketCommitment.toLowerCase() || opening !== expected.sqrtPriceX96 || positions !== expected.expectedPositionCount)
309
- throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook key, economic commitment or immutable opening geometry changed");
457
+ if (actualPoolId.toLowerCase() !== expectedPoolId.toLowerCase() || deploymentConfigHash.toLowerCase() !== keccak256(encodePoolBoundHookParameters(expected, config.version)).toLowerCase() || marketCommitment.toLowerCase() !== expected.marketCommitment.toLowerCase() || opening !== expected.sqrtPriceX96 || positions !== expected.expectedPositionCount ||
458
+ (config.version === 6 && (feePolicy?.[0] !== config.minimumHookFeePips || feePolicy?.[1] !== config.feeSensitivityPipsSecondsPerTick)))
459
+ throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook key, economic commitment, versioned fee policy or immutable opening geometry changed");
310
460
  }
311
- return { deployment: { deployer, initCodeHash, salt, predictedHook }, parameters: expected };
461
+ return Object.freeze({ deployment: Object.freeze({ deployer, initCodeHash, salt, predictedHook }), parameters: Object.freeze(expected), configVersion: config.version });
312
462
  }
313
- /** Works with an unmined candidate salt and after preparation; never accepts a caller-selected root. */
463
+ /** Known identities use frozen local construction data; unlisted identities use
464
+ * explicit live certification. Exact launch execution, not either DTO, authorizes. */
314
465
  export async function readPoolBoundHookDeployment(options, pinnedBlock) {
315
466
  const commitment = hashLaunchPlan(options.plan);
316
467
  const block = pinnedBlock ?? await readLifecycleBlock(options.client);
317
468
  return withLifecycleReadClient(options.client, async (client) => {
318
- const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
469
+ const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block, commitment);
319
470
  if (hashLaunchPlan(options.plan) !== commitment)
320
471
  throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
321
472
  if (pinnedBlock === undefined)
322
473
  await assertLifecycleBlock(client, block, options.plan.chainId);
323
- return deployment;
474
+ return { ...deployment };
324
475
  });
325
476
  }
326
477
  /** Optional permissionless predeployment calldata; no token deployment, pool registration or wallet broadcast. */
@@ -328,13 +479,14 @@ export async function buildPoolBoundHookDeploymentTransaction(options) {
328
479
  const commitment = hashLaunchPlan(options.plan);
329
480
  const block = await readLifecycleBlock(options.client);
330
481
  return withLifecycleReadClient(options.client, async (client) => {
331
- const { deployment, parameters } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
482
+ const { deployment, parameters, configVersion } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block, commitment);
332
483
  if (!hasLifecycleV4HookPermissions(deployment.predictedHook))
333
484
  throw new LifecyclePlanningError("INVALID_HOOK_BITS", "Finalize a salt with the exact lifecycle hook permissions before predeployment");
334
485
  if (hashLaunchPlan(options.plan) !== commitment)
335
486
  throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while constructing hook deployment calldata");
336
487
  await assertLifecycleBlock(client, block, options.plan.chainId);
337
- return { to: deployment.deployer, data: encodeFunctionData({ abi: poolHookDeployerV1Abi, functionName: "deploy", args: [parameters, deployment.salt] }), value: 0n, deployment };
488
+ const abi = configVersion === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
489
+ return { to: deployment.deployer, data: encodeFunctionData({ abi, functionName: "deploy", args: [parameters, deployment.salt] }), value: 0n, deployment: { ...deployment } };
338
490
  });
339
491
  }
340
492
  export function validateLifecycleMarketIdentity(identity, plan, token, marketIndex) {
@@ -441,17 +593,28 @@ async function readReceipt(reference, options, block) {
441
593
  }
442
594
  /** Reads confirmation-bound canonical state and receipt provenance; local step counters are never used. */
443
595
  export async function readLaunchProgress(options, pinnedBlock) {
596
+ if (options.signal?.aborted)
597
+ throw Object.assign(new Error("Lifecycle progress read cancelled"), { name: "AbortError" });
444
598
  const block = pinnedBlock ?? await readLifecycleBlock(options.client);
445
599
  return withLifecycleReadClient(options.client, (client) => readLaunchProgressAtBlock(options, client, block, pinnedBlock === undefined));
446
600
  }
447
601
  async function readLaunchProgressAtBlock(options, client, block, checkBlock) {
448
602
  const { planned } = options;
603
+ const assertActive = () => {
604
+ if (options.signal?.aborted)
605
+ throw Object.assign(new Error("Lifecycle progress read cancelled"), { name: "AbortError" });
606
+ };
607
+ assertActive();
449
608
  const confirmationDepth = options.confirmations ?? planned.confirmations;
450
609
  if (!Number.isSafeInteger(confirmationDepth) || confirmationDepth < 1)
451
610
  throw new LifecyclePlanningError("INVALID_CONFIRMATIONS", "Confirmation depth must be a positive integer");
452
611
  const confirmedNumber = block.number >= BigInt(confirmationDepth - 1) ? block.number - BigInt(confirmationDepth - 1) : 0n;
453
- const confirmedBlock = confirmedNumber === block.number ? block : await readLifecycleBlock(client, toHex(confirmedNumber));
454
- const chainId = rpcQuantity(await lifecycleRpc(client, "eth_chainId"), "chain ID");
612
+ const [confirmedBlock, chainIdValue] = await Promise.all([
613
+ confirmedNumber === block.number ? block : readLifecycleBlock(client, toHex(confirmedNumber)),
614
+ readLifecycleChainId(client),
615
+ ]);
616
+ assertActive();
617
+ const chainId = chainIdValue;
455
618
  if (chainId !== planned.plan.chainId)
456
619
  throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed launch");
457
620
  if (planned.account.toLowerCase() !== planned.plan.creator.toLowerCase())
@@ -461,11 +624,12 @@ async function readLaunchProgressAtBlock(options, client, block, checkBlock) {
461
624
  const [head, confirmedProgress, token, confirmedNonce, headNonce, pendingNonce] = await Promise.all([
462
625
  readLifecycleContract(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], block, planned.account),
463
626
  confirmedNumber === block.number ? undefined : readLifecycleContract(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], confirmedBlock, planned.account),
464
- readLifecycleContract(client, planned.plan.orchestrator, launchLifecycleAbi, "predictToken", [planned.plan], block),
627
+ readLifecycleTokenAtBlock(client, planned.plan, block),
465
628
  lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(confirmedBlock.number)]),
466
629
  confirmedNumber === block.number ? undefined : lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(block.number)]),
467
630
  lifecycleRpc(client, "eth_getTransactionCount", [planned.account, "pending"]),
468
631
  ]);
632
+ assertActive();
469
633
  const canonical = confirmedProgress ?? head;
470
634
  if (token.toLowerCase() !== planned.predictedToken.toLowerCase())
471
635
  throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed for the committed token configuration");
@@ -484,6 +648,7 @@ async function readLaunchProgressAtBlock(options, client, block, checkBlock) {
484
648
  const progressParameters = [{ type: "tuple", components: launchProgressV1Components }];
485
649
  const confirmationSafe = confirmedAccountNonce === headAccountNonce && pendingAccountNonce === headAccountNonce && keccak256(encodeAbiParameters(progressParameters, [canonical])) === keccak256(encodeAbiParameters(progressParameters, [head]));
486
650
  const receipts = await Promise.all((options.receipts ?? []).map((reference) => readReceipt({ ...reference, confirmations: Math.max(reference.confirmations ?? 1, confirmationDepth) }, { ...options, client }, block)));
651
+ assertActive();
487
652
  let markets = [];
488
653
  if (canonical.preparedMarkets > 0) {
489
654
  const directory = await readLifecycleContract(client, planned.plan.orchestrator, launchLifecycleAbi, "directory", [], confirmedBlock);
@@ -495,7 +660,9 @@ async function readLaunchProgressAtBlock(options, client, block, checkBlock) {
495
660
  return { index, prepared, live };
496
661
  }
497
662
  catch (failure) {
498
- return { index, prepared, error: failure instanceof Error ? failure.message : String(failure) };
663
+ if (failure instanceof Error && failure.name === "AbortError")
664
+ throw failure;
665
+ return { index, prepared, error: lifecycleFailureReason(failure) };
499
666
  }
500
667
  }));
501
668
  }
@@ -505,6 +672,7 @@ async function readLaunchProgressAtBlock(options, client, block, checkBlock) {
505
672
  await assertLifecycleBlock(client, confirmedBlock, chainId);
506
673
  if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase())
507
674
  throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan changed during asynchronous progress reads");
675
+ assertActive();
508
676
  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 };
509
677
  }
510
678
  //# sourceMappingURL=progress.js.map