@black-market/sdk 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { concatHex, decodeFunctionData, encodeAbiParameters, encodeFunctionData,
2
2
  import { abyssLifecycleAdapterAbi, launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleOracleFactoryAbi, lifecycleRegistryAbi, poolFeeCollectorFactoryV1Abi, lifecycleV4HookAbi, lifecycleV4LockerAbi, poolHookDeployerV1Abi, sharedHookDeployerV1Abi, fixedFeePoolHookV1Abi, poolMarketAdapterV1Abi, sharedMarketAdapterV1Abi } from "./abi.js";
3
3
  import { decodePoolBoundV4LifecycleMarketConfig, encodePoolBoundHookParameters, hasLifecycleV4HookPermissions, hashPoolBoundV4MarketCommitment, poolBoundHookInitCodeHash, predictPoolBoundHookAddress, validateV4LifecycleMarket, V4_LIFECYCLE_CONFIG_SCHEMA, V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA } from "./markets.js";
4
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, lifecycleRpc, readLifecycleBlock, readLifecycleContract, readLifecycleProfileTopology, rpcHex, rpcObject, rpcQuantity } from "./rpc.js";
5
+ import { assertLifecycleBlock, lifecyclePinnedRpc, lifecycleRpc, readLifecycleBlock, readLifecycleContract, readLifecycleProfileTopology, rpcHex, rpcObject, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
6
6
  import { LifecyclePlanningError } from "./types.js";
7
7
  export const ABYSS_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint8,uint24,bytes32,uint160,(int24,int24,uint128,uint256)[])"));
8
8
  export async function predictLifecycleToken(options) {
@@ -12,7 +12,7 @@ export async function predictLifecycleToken(options) {
12
12
  return token;
13
13
  }
14
14
  async function readDependencyCode(client, address, block) {
15
- const code = rpcHex(await lifecycleRpc(client, "eth_getCode", [address, toHex(block.number)]), "lifecycle dependency code");
15
+ const code = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [address, toHex(block.number)], block), "lifecycle dependency code");
16
16
  if (address === zeroAddress || code === "0x")
17
17
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", `Lifecycle dependency ${address} has no deployed code`);
18
18
  return code;
@@ -26,19 +26,23 @@ async function certifyLifecycleProfile(client, orchestrator, registry, id, regis
26
26
  const version = shared ? 4 : bound ? 5 : 1;
27
27
  if (adapter.configVersion !== version || topology.configVersion !== version || topology.hookTopology !== (shared ? 1 : bound ? 2 : 0))
28
28
  throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Registry topology/version differs from the supported config schema");
29
- const implementationCode = await readDependencyCode(client, adapter.implementation, block);
29
+ const [implementationCode, authority, digest] = await Promise.all([
30
+ readDependencyCode(client, adapter.implementation, block),
31
+ readLifecycleContract(client, adapter.implementation, lifecycleAdapterAbi, "core", [], block),
32
+ readLifecycleContract(client, adapter.implementation, lifecycleAdapterAbi, "dependencyDigest", [], block),
33
+ ]);
30
34
  if (keccak256(implementationCode).toLowerCase() !== adapter.codeHash.toLowerCase())
31
35
  throw new LifecyclePlanningError("PROFILE_CODE_HASH", "Adapter code hash differs from immutable registry approval");
32
- const authority = await readLifecycleContract(client, adapter.implementation, lifecycleAdapterAbi, "core", [], block);
33
- const digest = await readLifecycleContract(client, adapter.implementation, lifecycleAdapterAbi, "dependencyDigest", [], block);
34
36
  if (authority.toLowerCase() !== orchestrator.toLowerCase() || digest.toLowerCase() !== registration.dependencyDigest.toLowerCase())
35
37
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Adapter authority or dependency digest differs from immutable registry approval");
36
38
  if (abyss) {
37
39
  if (topology.hookDeployer !== zeroAddress || topology.hookCreationCodeHash !== zeroHash)
38
40
  throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Abyss does not admit a V4 hook deployment graph");
39
- const factory = await readLifecycleContract(client, adapter.implementation, abyssLifecycleAdapterAbi, "factory", [], block);
40
- const schema = await readLifecycleContract(client, adapter.implementation, abyssLifecycleAdapterAbi, "CONFIG_SCHEMA", [], block);
41
- const actualVersion = await readLifecycleContract(client, adapter.implementation, abyssLifecycleAdapterAbi, "CONFIG_VERSION", [], block);
41
+ const [factory, schema, actualVersion] = await Promise.all([
42
+ readLifecycleContract(client, adapter.implementation, abyssLifecycleAdapterAbi, "factory", [], block),
43
+ readLifecycleContract(client, adapter.implementation, abyssLifecycleAdapterAbi, "CONFIG_SCHEMA", [], block),
44
+ readLifecycleContract(client, adapter.implementation, abyssLifecycleAdapterAbi, "CONFIG_VERSION", [], block),
45
+ ]);
42
46
  const domain = keccak256(stringToHex("BLACK_MARKET_ABYSS_CANONICAL_PROFILE_V1"));
43
47
  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());
44
48
  if (!known || schema.toLowerCase() !== ABYSS_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() || actualVersion !== 1 || registration.factory.toLowerCase() !== factory.toLowerCase() || registration.venue.toLowerCase() !== factory.toLowerCase() || registration.hook !== zeroAddress)
@@ -62,40 +66,49 @@ async function certifyLifecycleProfile(client, orchestrator, registry, id, regis
62
66
  envelope.protocolTreasury === zeroAddress || (envelope.protocolFeeDenominator !== 0 && (envelope.protocolFeeDenominator < 4 || envelope.protocolFeeDenominator > 10)))
63
67
  throw new LifecyclePlanningError("PROFILE_BOUNDS", "Registry envelope has unsupported executable bounds");
64
68
  const abi = bound ? poolMarketAdapterV1Abi : sharedMarketAdapterV1Abi;
65
- for (const [getter, expected] of [["PROFILE_ID", id], ["CONFIG_SCHEMA", registration.configSchema], ["implementationRegistry", registry]]) {
66
- const actual = await readLifecycleContract(client, adapter.implementation, abi, getter, [], block);
67
- if (actual.toLowerCase() !== expected.toLowerCase())
69
+ const [metadata, actualVersion] = await Promise.all([
70
+ Promise.all(["PROFILE_ID", "CONFIG_SCHEMA", "implementationRegistry"].map((getter) => readLifecycleContract(client, adapter.implementation, abi, getter, [], block))),
71
+ readLifecycleContract(client, adapter.implementation, abi, "CONFIG_VERSION", [], block),
72
+ ]);
73
+ for (const [index, expected] of [id, registration.configSchema, registry].entries()) {
74
+ if (metadata[index]?.toLowerCase() !== expected.toLowerCase())
68
75
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed adapter metadata differs from registry certification");
69
76
  }
70
- if (await readLifecycleContract(client, adapter.implementation, abi, "CONFIG_VERSION", [], block) !== version)
77
+ if (actualVersion !== version)
71
78
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed adapter config version differs from certification");
72
79
  const g = envelope.graph;
73
80
  if (registration.factory !== zeroAddress || registration.venue.toLowerCase() !== g.manager.toLowerCase() || registration.hook.toLowerCase() !== g.hookRoot.toLowerCase() ||
74
81
  topology.hookDeployer.toLowerCase() !== g.hookDeployer.toLowerCase() || topology.hookCreationCodeHash.toLowerCase() !== g.hookCreationCodeHash.toLowerCase())
75
82
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed venue/topology differs from the frozen envelope graph");
76
- for (const [getter, expected] of [["poolManager", g.manager], ["hookRoot", g.hookRoot], ["oracleFactory", g.oracleFactory], ["locker", g.locker], ["collectorFactory", g.collectorFactory], ["hookDeployer", g.hookDeployer]]) {
77
- const actual = await readLifecycleContract(client, adapter.implementation, abi, getter, [], block);
78
- if (actual.toLowerCase() !== expected.toLowerCase())
83
+ const deployerAbi = shared ? sharedHookDeployerV1Abi : poolHookDeployerV1Abi;
84
+ const codeBindings = [[orchestrator, g.coreCodeHash], [g.manager, g.managerCodeHash], [g.oracleFactory, g.oracleFactoryCodeHash],
85
+ [g.locker, g.lockerCodeHash], [g.collectorFactory, g.collectorFactoryCodeHash], [g.collectorDeployer, g.collectorDeployerCodeHash],
86
+ [g.hookDeployer, g.hookDeployerCodeHash]];
87
+ const [dependencies, dependencyCodes, collectorDeployer, launcher, lockerManager, creationCodeHash, chunk0, chunk1] = await Promise.all([
88
+ Promise.all(["poolManager", "hookRoot", "oracleFactory", "locker", "collectorFactory", "hookDeployer"].map((getter) => readLifecycleContract(client, adapter.implementation, abi, getter, [], block))),
89
+ Promise.all(codeBindings.map(([address]) => readDependencyCode(client, address, block))),
90
+ readLifecycleContract(client, g.collectorFactory, poolFeeCollectorFactoryV1Abi, "collectorDeployer", [], block),
91
+ readLifecycleContract(client, g.locker, lifecycleV4LockerAbi, "launcher", [], block),
92
+ readLifecycleContract(client, g.locker, lifecycleV4LockerAbi, "poolManager", [], block),
93
+ readLifecycleContract(client, g.hookDeployer, deployerAbi, "creationCodeHash", [], block),
94
+ readLifecycleContract(client, g.hookDeployer, deployerAbi, "codeChunk0", [], block),
95
+ readLifecycleContract(client, g.hookDeployer, deployerAbi, "codeChunk1", [], block),
96
+ ]);
97
+ for (const [index, expected] of [g.manager, g.hookRoot, g.oracleFactory, g.locker, g.collectorFactory, g.hookDeployer].entries()) {
98
+ if (dependencies[index]?.toLowerCase() !== expected.toLowerCase())
79
99
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Live adapter dependency differs from the frozen reviewed graph");
80
100
  }
81
- for (const [address, expectedHash] of [[orchestrator, g.coreCodeHash], [g.manager, g.managerCodeHash], [g.oracleFactory, g.oracleFactoryCodeHash],
82
- [g.locker, g.lockerCodeHash], [g.collectorFactory, g.collectorFactoryCodeHash], [g.collectorDeployer, g.collectorDeployerCodeHash],
83
- [g.hookDeployer, g.hookDeployerCodeHash]]) {
84
- if (keccak256(await readDependencyCode(client, address, block)).toLowerCase() !== expectedHash.toLowerCase())
101
+ for (const [index, [, expectedHash]] of codeBindings.entries()) {
102
+ if (keccak256(dependencyCodes[index]).toLowerCase() !== expectedHash.toLowerCase())
85
103
  throw new LifecyclePlanningError("PROFILE_CODE_HASH", "Live runtime differs from the frozen reviewed graph hash");
86
104
  }
87
- const collectorDeployer = await readLifecycleContract(client, g.collectorFactory, poolFeeCollectorFactoryV1Abi, "collectorDeployer", [], block);
88
- const launcher = await readLifecycleContract(client, g.locker, lifecycleV4LockerAbi, "launcher", [], block);
89
- const lockerManager = await readLifecycleContract(client, g.locker, lifecycleV4LockerAbi, "poolManager", [], block);
90
105
  if (collectorDeployer.toLowerCase() !== g.collectorDeployer.toLowerCase() || launcher.toLowerCase() !== adapter.implementation.toLowerCase() ||
91
106
  lockerManager.toLowerCase() !== g.manager.toLowerCase() || g.locker.toLowerCase() === g.manager.toLowerCase() || g.locker.toLowerCase() === g.hookRoot.toLowerCase())
92
107
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed collector or locker authority differs from the frozen graph");
93
- const deployerAbi = shared ? sharedHookDeployerV1Abi : poolHookDeployerV1Abi;
94
- const creationCodeHash = await readLifecycleContract(client, g.hookDeployer, deployerAbi, "creationCodeHash", [], block);
95
- const chunk0 = await readLifecycleContract(client, g.hookDeployer, deployerAbi, "codeChunk0", [], block);
96
- const chunk1 = await readLifecycleContract(client, g.hookDeployer, deployerAbi, "codeChunk1", [], block);
97
- const chunk0Code = await readDependencyCode(client, chunk0, block);
98
- const chunk1Code = chunk1 === zeroAddress ? "0x" : await readDependencyCode(client, chunk1, block);
108
+ const [chunk0Code, chunk1Code] = await Promise.all([
109
+ readDependencyCode(client, chunk0, block),
110
+ chunk1 === zeroAddress ? Promise.resolve("0x") : readDependencyCode(client, chunk1, block),
111
+ ]);
99
112
  if (chunk0.toLowerCase() !== g.codeChunk0.toLowerCase() || chunk1.toLowerCase() !== g.codeChunk1.toLowerCase() || chunk0.toLowerCase() === chunk1.toLowerCase() ||
100
113
  !chunk0Code.startsWith("0x00") || chunk0Code.length <= 4 || (chunk0Code.length - 2) / 2 > 24576 ||
101
114
  (chunk1 !== zeroAddress && (!chunk1Code.startsWith("0x00") || chunk1Code.length <= 4 || (chunk1Code.length - 2) / 2 > 24576 || (chunk0Code.length - 2) / 2 !== 24576)) ||
@@ -107,20 +120,24 @@ async function certifyLifecycleProfile(client, orchestrator, registry, id, regis
107
120
  hashLaunchDependencies({ chainId, core: orchestrator, registry, registrar: adapter.implementation, graph: g }).toLowerCase() !== digest.toLowerCase())
108
121
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed creation artifact, constructor bound or graph digest differs from admission");
109
122
  if (shared) {
110
- const rootCode = await readDependencyCode(client, g.hookRoot, block);
111
- const recordedHash = await readLifecycleContract(client, g.hookDeployer, deployerAbi, "deployedCodeHash", [g.hookRoot], block);
123
+ const [rootCode, recordedHash, remotePrediction, dependencies, requiredFlags, allFlags] = await Promise.all([
124
+ readDependencyCode(client, g.hookRoot, block),
125
+ readLifecycleContract(client, g.hookDeployer, deployerAbi, "deployedCodeHash", [g.hookRoot], block),
126
+ readLifecycleContract(client, g.hookDeployer, sharedHookDeployerV1Abi, "predict", [g.manager, adapter.implementation, g.oracleFactory, g.sharedHookSalt], block),
127
+ Promise.all(["registrar", "poolManager", "oracleFactory"].map((getter) => readLifecycleContract(client, g.hookRoot, lifecycleV4HookAbi, getter, [], block))),
128
+ readLifecycleContract(client, g.hookRoot, lifecycleV4HookAbi, "REQUIRED_HOOK_FLAGS", [], block),
129
+ readLifecycleContract(client, g.hookRoot, lifecycleV4HookAbi, "ALL_HOOK_MASK", [], block),
130
+ ]);
112
131
  const initCodeHash = keccak256(concatHex([creationCode, encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "address" }], [g.manager, adapter.implementation, g.oracleFactory])]));
113
132
  const prediction = predictPoolBoundHookAddress({ deployer: g.hookDeployer, initCodeHash, salt: g.sharedHookSalt });
114
- const remotePrediction = await readLifecycleContract(client, g.hookDeployer, sharedHookDeployerV1Abi, "predict", [g.manager, adapter.implementation, g.oracleFactory, g.sharedHookSalt], block);
115
133
  if (recordedHash === zeroHash || recordedHash.toLowerCase() !== g.hookRuntimeCodeHash.toLowerCase() || keccak256(rootCode).toLowerCase() !== recordedHash.toLowerCase() ||
116
134
  !hasLifecycleV4HookPermissions(g.hookRoot) || prediction.toLowerCase() !== g.hookRoot.toLowerCase() || remotePrediction.toLowerCase() !== prediction.toLowerCase())
117
135
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared root lacks exact typed-deployer runtime and constructor/salt provenance");
118
- for (const [getter, expected] of [["registrar", adapter.implementation], ["poolManager", g.manager], ["oracleFactory", g.oracleFactory]]) {
119
- if ((await readLifecycleContract(client, g.hookRoot, lifecycleV4HookAbi, getter, [], block)).toLowerCase() !== expected.toLowerCase())
136
+ for (const [index, expected] of [adapter.implementation, g.manager, g.oracleFactory].entries()) {
137
+ if (dependencies[index]?.toLowerCase() !== expected.toLowerCase())
120
138
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared hook immutable graph differs from admission");
121
139
  }
122
- if (await readLifecycleContract(client, g.hookRoot, lifecycleV4HookAbi, "REQUIRED_HOOK_FLAGS", [], block) !== 0x1afcn ||
123
- await readLifecycleContract(client, g.hookRoot, lifecycleV4HookAbi, "ALL_HOOK_MASK", [], block) !== 0x3fffn)
140
+ if (requiredFlags !== 0x1afcn || allFlags !== 0x3fffn)
124
141
  throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared hook callbacks differ from admission");
125
142
  }
126
143
  else if (g.hookRoot !== zeroAddress || g.hookRuntimeCodeHash !== zeroHash || g.sharedHookSalt !== zeroHash)
@@ -140,55 +157,77 @@ export async function validateV4LifecycleOracle(options) {
140
157
  }
141
158
  }
142
159
  export async function readLifecycleProfiles(options, pinnedBlock) {
143
- const block = pinnedBlock ?? await readLifecycleBlock(options.client);
144
- const chainId = rpcQuantity(await lifecycleRpc(options.client, "eth_chainId"), "chain ID");
145
- const registry = await readLifecycleContract(options.client, options.orchestrator, launchLifecycleAbi, "registry", [], block);
146
- if ((await readLifecycleContract(options.client, registry, lifecycleRegistryAbi, "core", [], block)).toLowerCase() !== options.orchestrator.toLowerCase())
147
- throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to the selected lifecycle orchestrator");
148
- const offset = options.offset ?? 0n;
149
- const limit = options.limit ?? 100n;
150
- if (offset < 0n || limit < 0n || limit > 100n)
151
- throw new LifecyclePlanningError("INVALID_PROFILE_PAGE", "Registry profile pages require unsigned offset and limit at most 100");
152
- const ids = options.profileIds ?? await readLifecycleContract(options.client, registry, lifecycleRegistryAbi, "profileIds", [offset, limit], block);
153
- const protocolMaximumDeveloperFeeBps = await readLifecycleContract(options.client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
154
- const profiles = [];
155
- for (const id of ids) {
156
- const registration = await readLifecycleContract(options.client, registry, lifecycleRegistryAbi, "profile", [id], block);
157
- const adapter = await readLifecycleContract(options.client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block);
158
- const schema = registration.configSchema.toLowerCase();
159
- 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";
160
- let topology = { hookTopology: 0, configVersion: adapter.configVersion, hookDeployer: zeroAddress, hookCreationCodeHash: zeroHash };
161
- let envelope;
162
- let developerTerms;
163
- let admitted = false;
164
- let reason;
165
- try {
166
- if (venueKind === "unknown")
167
- throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
168
- topology = await readLifecycleProfileTopology(options.client, registry, id, block);
169
- if (venueKind === "uniswap-v4") {
170
- envelope = await readLifecycleContract(options.client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block);
171
- const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = await readLifecycleContract(options.client, registry, lifecycleRegistryAbi, "developerTerms", [id], block);
172
- developerTerms = { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled };
173
- if (implementation.toLowerCase() !== adapter.implementation.toLowerCase() || beneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
174
- termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
175
- maximumDeveloperFeeBps > protocolMaximumDeveloperFeeBps)
176
- throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
160
+ return withLifecycleReadClient(options.client, async (client) => {
161
+ const [block, chainIdValue] = await Promise.all([
162
+ pinnedBlock ?? readLifecycleBlock(client),
163
+ lifecycleRpc(client, "eth_chainId"),
164
+ ]);
165
+ const chainId = rpcQuantity(chainIdValue, "chain ID");
166
+ const registry = await readLifecycleContract(client, options.orchestrator, launchLifecycleAbi, "registry", [], block);
167
+ const offset = options.offset ?? 0n;
168
+ const limit = options.limit ?? 100n;
169
+ if (offset < 0n || limit < 0n || limit > 100n)
170
+ throw new LifecyclePlanningError("INVALID_PROFILE_PAGE", "Registry profile pages require unsigned offset and limit at most 100");
171
+ const [authority, ids] = await Promise.all([
172
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "core", [], block),
173
+ options.profileIds ?? readLifecycleContract(client, registry, lifecycleRegistryAbi, "profileIds", [offset, limit], block),
174
+ ]);
175
+ if (authority.toLowerCase() !== options.orchestrator.toLowerCase())
176
+ throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to the selected lifecycle orchestrator");
177
+ let protocolMaximumDeveloperFeeBpsPromise;
178
+ const readProtocolMaximumDeveloperFeeBps = () => protocolMaximumDeveloperFeeBpsPromise ??=
179
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
180
+ const profiles = await Promise.all(ids.map(async (id) => {
181
+ const registration = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "profile", [id], block);
182
+ const adapter = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block);
183
+ const schema = registration.configSchema.toLowerCase();
184
+ 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";
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 (venueKind === "unknown")
193
+ throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
194
+ const [recordedTopology, reviewed] = await Promise.all([
195
+ readLifecycleProfileTopology(client, registry, id, block),
196
+ venueKind === "uniswap-v4" ? Promise.all([
197
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block),
198
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
199
+ readProtocolMaximumDeveloperFeeBps(),
200
+ ]) : undefined,
201
+ ]);
202
+ topology = recordedTopology;
203
+ if (reviewed !== undefined) {
204
+ envelope = reviewed[0];
205
+ protocolMaximumDeveloperFeeBps = reviewed[2];
206
+ const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = reviewed[1];
207
+ developerTerms = { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled };
208
+ if (implementation.toLowerCase() !== adapter.implementation.toLowerCase() || beneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
209
+ termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
210
+ maximumDeveloperFeeBps > reviewed[2])
211
+ throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
212
+ }
213
+ await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
214
+ const eligible = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, topology.configVersion, LIFECYCLE_REQUIRED_CAPABILITIES], block);
215
+ if (eligible.toLowerCase() !== adapter.implementation.toLowerCase() || (developerTerms !== undefined && !developerTerms.enabled))
216
+ throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
217
+ admitted = true;
177
218
  }
178
- await certifyLifecycleProfile(options.client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
179
- const eligible = await readLifecycleContract(options.client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, topology.configVersion, LIFECYCLE_REQUIRED_CAPABILITIES], block);
180
- if (eligible.toLowerCase() !== adapter.implementation.toLowerCase() || (developerTerms !== undefined && !developerTerms.enabled))
181
- throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
182
- admitted = true;
183
- }
184
- catch (failure) {
185
- reason = failure instanceof Error ? failure.message : String(failure);
186
- }
187
- profiles.push({ id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind });
188
- }
189
- if (pinnedBlock === undefined)
190
- await assertLifecycleBlock(options.client, block);
191
- return profiles;
219
+ catch (failure) {
220
+ reason = failure instanceof Error ? failure.message : String(failure);
221
+ }
222
+ return { id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind };
223
+ }));
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
+ if (pinnedBlock === undefined)
228
+ await assertLifecycleBlock(client, block, chainId);
229
+ return profiles;
230
+ });
192
231
  }
193
232
  async function readPoolBoundHookDeploymentDetails(options, block) {
194
233
  const { client, plan, marketIndex } = options;
@@ -199,20 +238,25 @@ async function readPoolBoundHookDeploymentDetails(options, block) {
199
238
  if (chainId !== plan.chainId)
200
239
  throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed plan");
201
240
  const registry = await readLifecycleContract(client, plan.orchestrator, launchLifecycleAbi, "registry", [], block);
202
- if ((await readLifecycleContract(client, registry, lifecycleRegistryAbi, "core", [], block)).toLowerCase() !== plan.orchestrator.toLowerCase())
241
+ const [authority, registration] = await Promise.all([
242
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "core", [], block),
243
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "profile", [market.profileId], block),
244
+ ]);
245
+ if (authority.toLowerCase() !== plan.orchestrator.toLowerCase())
203
246
  throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to this lifecycle core");
204
- const registration = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "profile", [market.profileId], block);
205
247
  const adapter = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block);
206
248
  if (registration.adapterId.toLowerCase() !== market.adapterId.toLowerCase())
207
249
  throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market adapter differs from the profile's immutable registration");
208
- const recorded = await readLifecycleProfileTopology(client, registry, market.profileId, block);
209
- const envelope = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "profileEnvelope", [market.profileId], block);
210
- const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "developerTerms", [market.profileId], block);
211
- const protocolMaximumDeveloperFeeBps = await readLifecycleContract(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
250
+ const [recorded, envelope, [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled], protocolMaximumDeveloperFeeBps, token] = await Promise.all([
251
+ readLifecycleProfileTopology(client, registry, market.profileId, block),
252
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "profileEnvelope", [market.profileId], block),
253
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "developerTerms", [market.profileId], block),
254
+ readLifecycleContract(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block),
255
+ readLifecycleContract(client, plan.orchestrator, launchLifecycleAbi, "predictToken", [plan], block),
256
+ ]);
212
257
  const { boundGraph } = await certifyLifecycleProfile(client, plan.orchestrator, registry, market.profileId, registration, adapter, recorded, envelope, block, chainId);
213
258
  if (boundGraph === undefined)
214
259
  throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Market does not have a certified pool-bound deployer graph");
215
- const token = await readLifecycleContract(client, plan.orchestrator, launchLifecycleAbi, "predictToken", [plan], block);
216
260
  const config = decodePoolBoundV4LifecycleMarketConfig(market.config);
217
261
  validateV4LifecycleMarket({ market, config, token, profile: {
218
262
  id: market.profileId, registration, adapter, topology: recorded, envelope,
@@ -228,37 +272,39 @@ async function readPoolBoundHookDeploymentDetails(options, block) {
228
272
  marketCommitment: hashPoolBoundV4MarketCommitment({ chainId, core: plan.orchestrator, registrar: adapter.implementation, token, market }),
229
273
  expectedPositionCount: config.positions.length,
230
274
  };
231
- const [parameters, parameterSalt] = await readLifecycleContract(client, boundGraph.collectorFactory, poolFeeCollectorFactoryV1Abi, "poolBoundHookParameters", [adapter.implementation, token, market], block);
232
- const [deployer, initCodeHash, salt, predictedHook] = await readLifecycleContract(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block);
233
- const remoteInitCodeHash = await readLifecycleContract(client, boundGraph.deployer, poolHookDeployerV1Abi, "initCodeHash", [expected], block);
234
- const remotePrediction = await readLifecycleContract(client, boundGraph.deployer, poolHookDeployerV1Abi, "predict", [expected, config.hookSalt], block);
275
+ const [[parameters, parameterSalt], [deployer, initCodeHash, salt, predictedHook], remoteInitCodeHash, remotePrediction] = await Promise.all([
276
+ readLifecycleContract(client, boundGraph.collectorFactory, poolFeeCollectorFactoryV1Abi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
277
+ 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),
280
+ ]);
235
281
  const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected);
236
282
  const localPrediction = predictPoolBoundHookAddress({ deployer: boundGraph.deployer, initCodeHash: localInitCodeHash, salt: config.hookSalt });
237
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())
238
284
  throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Bound hook metadata differs from the exact certified constructor economics, factory, initcode, salt or CREATE2 prediction");
239
- const hookCode = rpcHex(await lifecycleRpc(client, "eth_getCode", [predictedHook, toHex(block.number)]), "bound hook code");
285
+ const hookCode = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [predictedHook, toHex(block.number)], block), "bound hook code");
240
286
  if (hookCode !== "0x") {
241
- const deployedCodeHash = await readLifecycleContract(client, deployer, poolHookDeployerV1Abi, "deployedCodeHash", [predictedHook], block);
287
+ const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions] = await Promise.all([
288
+ readLifecycleContract(client, deployer, poolHookDeployerV1Abi, "deployedCodeHash", [predictedHook], block),
289
+ Promise.all(["poolManager", "registrar", "oracleFactory", "core", "liquidityLocker", "token"].map((getter) => readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block))),
290
+ readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block),
291
+ readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block),
292
+ readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "boundPoolId", [], block),
293
+ readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "deploymentConfigHash", [], block),
294
+ readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "marketCommitment", [], block),
295
+ readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "openingSqrtPriceX96", [], block),
296
+ readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "expectedPositionCount", [], block),
297
+ ]);
242
298
  if ((hookCode.length - 2) / 2 > 24576 || deployedCodeHash === zeroHash || deployedCodeHash.toLowerCase() !== keccak256(hookCode).toLowerCase())
243
299
  throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook lacks this exact typed deployer's bounded runtime-code provenance");
244
- for (const [getter, expectedAddress] of [
245
- ["poolManager", expected.poolManager], ["registrar", expected.registrar], ["oracleFactory", expected.oracleFactory],
246
- ["core", expected.core], ["liquidityLocker", expected.liquidityLocker], ["token", expected.token],
247
- ]) {
248
- const actual = await readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block);
249
- if (actual.toLowerCase() !== expectedAddress.toLowerCase())
300
+ for (const [index, expectedAddress] of [expected.poolManager, expected.registrar, expected.oracleFactory, expected.core, expected.liquidityLocker, expected.token].entries()) {
301
+ if (dependencies[index]?.toLowerCase() !== expectedAddress.toLowerCase())
250
302
  throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook immutable dependency graph differs from the committed constructor");
251
303
  }
252
- if (await readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block) !== 0x1afcn ||
253
- await readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block) !== 0x3fffn)
304
+ if (requiredFlags !== 0x1afcn || allFlags !== 0x3fffn)
254
305
  throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing bound hook callback declarations differ from the reviewed envelope");
255
306
  const [currency0, currency1] = BigInt(token) < BigInt(market.quoteAsset) ? [token, market.quoteAsset] : [market.quoteAsset, token];
256
307
  const expectedPoolId = keccak256(encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }], [currency0, currency1, config.lpFeePips, config.tickSpacing, predictedHook]));
257
- const actualPoolId = await readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "boundPoolId", [], block);
258
- const deploymentConfigHash = await readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "deploymentConfigHash", [], block);
259
- const marketCommitment = await readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "marketCommitment", [], block);
260
- const opening = await readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "openingSqrtPriceX96", [], block);
261
- const positions = await readLifecycleContract(client, predictedHook, fixedFeePoolHookV1Abi, "expectedPositionCount", [], block);
262
308
  if (actualPoolId.toLowerCase() !== expectedPoolId.toLowerCase() || deploymentConfigHash.toLowerCase() !== keccak256(encodePoolBoundHookParameters(expected)).toLowerCase() || marketCommitment.toLowerCase() !== expected.marketCommitment.toLowerCase() || opening !== expected.sqrtPriceX96 || positions !== expected.expectedPositionCount)
263
309
  throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook key, economic commitment or immutable opening geometry changed");
264
310
  }
@@ -268,24 +314,28 @@ async function readPoolBoundHookDeploymentDetails(options, block) {
268
314
  export async function readPoolBoundHookDeployment(options, pinnedBlock) {
269
315
  const commitment = hashLaunchPlan(options.plan);
270
316
  const block = pinnedBlock ?? await readLifecycleBlock(options.client);
271
- const { deployment } = await readPoolBoundHookDeploymentDetails(options, block);
272
- if (hashLaunchPlan(options.plan) !== commitment)
273
- throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
274
- if (pinnedBlock === undefined)
275
- await assertLifecycleBlock(options.client, block);
276
- return deployment;
317
+ return withLifecycleReadClient(options.client, async (client) => {
318
+ const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
319
+ if (hashLaunchPlan(options.plan) !== commitment)
320
+ throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
321
+ if (pinnedBlock === undefined)
322
+ await assertLifecycleBlock(client, block, options.plan.chainId);
323
+ return deployment;
324
+ });
277
325
  }
278
326
  /** Optional permissionless predeployment calldata; no token deployment, pool registration or wallet broadcast. */
279
327
  export async function buildPoolBoundHookDeploymentTransaction(options) {
280
328
  const commitment = hashLaunchPlan(options.plan);
281
329
  const block = await readLifecycleBlock(options.client);
282
- const { deployment, parameters } = await readPoolBoundHookDeploymentDetails(options, block);
283
- if (!hasLifecycleV4HookPermissions(deployment.predictedHook))
284
- throw new LifecyclePlanningError("INVALID_HOOK_BITS", "Finalize a salt with the exact lifecycle hook permissions before predeployment");
285
- if (hashLaunchPlan(options.plan) !== commitment)
286
- throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while constructing hook deployment calldata");
287
- await assertLifecycleBlock(options.client, block);
288
- return { to: deployment.deployer, data: encodeFunctionData({ abi: poolHookDeployerV1Abi, functionName: "deploy", args: [parameters, deployment.salt] }), value: 0n, deployment };
330
+ return withLifecycleReadClient(options.client, async (client) => {
331
+ const { deployment, parameters } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
332
+ if (!hasLifecycleV4HookPermissions(deployment.predictedHook))
333
+ throw new LifecyclePlanningError("INVALID_HOOK_BITS", "Finalize a salt with the exact lifecycle hook permissions before predeployment");
334
+ if (hashLaunchPlan(options.plan) !== commitment)
335
+ throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while constructing hook deployment calldata");
336
+ 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 };
338
+ });
289
339
  }
290
340
  export function validateLifecycleMarketIdentity(identity, plan, token, marketIndex) {
291
341
  const market = plan.markets[marketIndex];
@@ -352,8 +402,10 @@ async function readReceipt(reference, options, block) {
352
402
  if (observed === null || rpcHex(rpcObject(observed, "observed receipt block").hash, "observed receipt block hash").toLowerCase() !== reference.observedBlockHash.toLowerCase())
353
403
  return { ...base, status: "reorged", blockNumber: observedNumber, blockHash: reference.observedBlockHash };
354
404
  }
355
- const receiptValue = await lifecycleRpc(options.client, "eth_getTransactionReceipt", [effectiveHash]);
356
- const transactionValue = await lifecycleRpc(options.client, "eth_getTransactionByHash", [effectiveHash]);
405
+ const [receiptValue, transactionValue] = await Promise.all([
406
+ lifecycleRpc(options.client, "eth_getTransactionReceipt", [effectiveHash]),
407
+ lifecycleRpc(options.client, "eth_getTransactionByHash", [effectiveHash]),
408
+ ]);
357
409
  if (receiptValue === null) {
358
410
  // An absent receipt with an unchanged canonical observed block is not a
359
411
  // reorg: the transaction is simply not (yet) mined. A missing/changed
@@ -389,8 +441,11 @@ async function readReceipt(reference, options, block) {
389
441
  }
390
442
  /** Reads confirmation-bound canonical state and receipt provenance; local step counters are never used. */
391
443
  export async function readLaunchProgress(options, pinnedBlock) {
392
- const { client, planned } = options;
393
- const block = pinnedBlock ?? await readLifecycleBlock(client);
444
+ const block = pinnedBlock ?? await readLifecycleBlock(options.client);
445
+ return withLifecycleReadClient(options.client, (client) => readLaunchProgressAtBlock(options, client, block, pinnedBlock === undefined));
446
+ }
447
+ async function readLaunchProgressAtBlock(options, client, block, checkBlock) {
448
+ const { planned } = options;
394
449
  const confirmationDepth = options.confirmations ?? planned.confirmations;
395
450
  if (!Number.isSafeInteger(confirmationDepth) || confirmationDepth < 1)
396
451
  throw new LifecyclePlanningError("INVALID_CONFIRMATIONS", "Confirmation depth must be a positive integer");
@@ -403,9 +458,15 @@ export async function readLaunchProgress(options, pinnedBlock) {
403
458
  throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
404
459
  if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase())
405
460
  throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its commitment");
406
- const head = await readLifecycleContract(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], block, planned.account);
407
- const canonical = confirmedNumber === block.number ? head : await readLifecycleContract(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], confirmedBlock, planned.account);
408
- const token = await readLifecycleContract(client, planned.plan.orchestrator, launchLifecycleAbi, "predictToken", [planned.plan], block);
461
+ const [head, confirmedProgress, token, confirmedNonce, headNonce, pendingNonce] = await Promise.all([
462
+ readLifecycleContract(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], block, planned.account),
463
+ 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),
465
+ lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(confirmedBlock.number)]),
466
+ confirmedNumber === block.number ? undefined : lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(block.number)]),
467
+ lifecycleRpc(client, "eth_getTransactionCount", [planned.account, "pending"]),
468
+ ]);
469
+ const canonical = confirmedProgress ?? head;
409
470
  if (token.toLowerCase() !== planned.predictedToken.toLowerCase())
410
471
  throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed for the committed token configuration");
411
472
  for (const progress of [canonical, head])
@@ -417,33 +478,33 @@ export async function readLaunchProgress(options, pinnedBlock) {
417
478
  if (progress.preparedMarkets > progress.marketCount || progress.phase === LifecyclePhase.Activating || (progress.phase === LifecyclePhase.Ready && progress.preparedMarkets !== progress.marketCount))
418
479
  throw new LifecyclePlanningError("INVALID_PROGRESS", "Canonical progress violates the committed ordered state machine");
419
480
  }
420
- const confirmedAccountNonce = rpcQuantity(await lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(confirmedBlock.number)]), "confirmed account nonce");
421
- const headAccountNonce = confirmedNumber === block.number ? confirmedAccountNonce : rpcQuantity(await lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(block.number)]), "head account nonce");
422
- const pendingAccountNonce = rpcQuantity(await lifecycleRpc(client, "eth_getTransactionCount", [planned.account, "pending"]), "pending account nonce");
481
+ const confirmedAccountNonce = rpcQuantity(confirmedNonce, "confirmed account nonce");
482
+ const headAccountNonce = headNonce === undefined ? confirmedAccountNonce : rpcQuantity(headNonce, "head account nonce");
483
+ const pendingAccountNonce = rpcQuantity(pendingNonce, "pending account nonce");
423
484
  const progressParameters = [{ type: "tuple", components: launchProgressV1Components }];
424
485
  const confirmationSafe = confirmedAccountNonce === headAccountNonce && pendingAccountNonce === headAccountNonce && keccak256(encodeAbiParameters(progressParameters, [canonical])) === keccak256(encodeAbiParameters(progressParameters, [head]));
425
- const receipts = [];
426
- for (const reference of options.receipts ?? [])
427
- receipts.push(await readReceipt({ ...reference, confirmations: Math.max(reference.confirmations ?? 1, confirmationDepth) }, options, block));
428
- const markets = [];
486
+ const receipts = await Promise.all((options.receipts ?? []).map((reference) => readReceipt({ ...reference, confirmations: Math.max(reference.confirmations ?? 1, confirmationDepth) }, { ...options, client }, block)));
487
+ let markets = [];
429
488
  if (canonical.preparedMarkets > 0) {
430
489
  const directory = await readLifecycleContract(client, planned.plan.orchestrator, launchLifecycleAbi, "directory", [], confirmedBlock);
431
- for (let index = 0; index < canonical.preparedMarkets; index += 1) {
490
+ markets = await Promise.all(Array.from({ length: canonical.preparedMarkets }, async (_, index) => {
432
491
  const [adapter, prepared] = await readLifecycleContract(client, directory, lifecycleDirectoryAbi, "market", [planned.launchId, index], confirmedBlock);
433
492
  validateLifecycleMarketIdentity(prepared.identity, planned.plan, token, index);
434
493
  try {
435
494
  const live = await readLifecycleContract(client, adapter, lifecycleAdapterAbi, "readMarket", [planned.launchId, index], confirmedBlock);
436
- markets.push({ index, prepared, live });
495
+ return { index, prepared, live };
437
496
  }
438
497
  catch (failure) {
439
- markets.push({ index, prepared, error: failure instanceof Error ? failure.message : String(failure) });
498
+ return { index, prepared, error: failure instanceof Error ? failure.message : String(failure) };
440
499
  }
441
- }
500
+ }));
442
501
  }
443
- if (pinnedBlock === undefined)
444
- await assertLifecycleBlock(client, block);
502
+ if (checkBlock)
503
+ await assertLifecycleBlock(client, block, chainId);
445
504
  if (confirmedNumber !== block.number)
446
- await assertLifecycleBlock(client, confirmedBlock);
505
+ await assertLifecycleBlock(client, confirmedBlock, chainId);
506
+ if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase())
507
+ throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan changed during asynchronous progress reads");
447
508
  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 };
448
509
  }
449
510
  //# sourceMappingURL=progress.js.map