@black-market/sdk 0.5.2 → 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, type PoolBoundHookParametersV1 } 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, type AdapterRegistrationV1, type LaunchPlanV1, type LaunchProgressV1, type MarketIdentityV1, type MarketLiveStateV1, type PreparedMarketV1, type ProfileRegistrationV1, type ProfileTopologyV1, type LaunchEnvelopeV2, type LifecycleDeveloperTerms } from "./schema.js";
5
- import { assertLifecycleBlock, 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, type CanonicalLaunchProgress, type LifecycleBlock, type LifecycleMarketProgress, type LifecycleProfile, type LifecycleReceiptReference, type LifecycleReceiptStatus, type LifecycleRpcClient, type PoolBoundHookDeployment, type ReadLaunchProgressOptions } from "./types.js";
7
7
 
8
8
  export const ABYSS_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint8,uint24,bytes32,uint160,(int24,int24,uint128,uint256)[])"));
@@ -17,7 +17,7 @@ export async function predictLifecycleToken(options: { client: LifecycleRpcClien
17
17
  type BoundProfileGraph = { manager: Address; oracleFactory: Address; locker: Address; collectorFactory: Address; deployer: Address; creationCode: Hex };
18
18
 
19
19
  async function readDependencyCode(client: LifecycleRpcClient, address: Address, block: LifecycleBlock): Promise<Hex> {
20
- const code = rpcHex(await lifecycleRpc(client, "eth_getCode", [address, toHex(block.number)]), "lifecycle dependency code");
20
+ const code = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [address, toHex(block.number)], block), "lifecycle dependency code");
21
21
  if (address === zeroAddress || code === "0x") throw new LifecyclePlanningError("PROFILE_DEPENDENCY", `Lifecycle dependency ${address} has no deployed code`);
22
22
  return code;
23
23
  }
@@ -29,16 +29,20 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
29
29
  if (!shared && !bound && !abyss) throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
30
30
  const version = shared ? 4 : bound ? 5 : 1;
31
31
  if (adapter.configVersion !== version || topology.configVersion !== version || topology.hookTopology !== (shared ? 1 : bound ? 2 : 0)) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Registry topology/version differs from the supported config schema");
32
- const implementationCode = await readDependencyCode(client, adapter.implementation, block);
32
+ const [implementationCode, authority, digest] = await Promise.all([
33
+ readDependencyCode(client, adapter.implementation, block),
34
+ readLifecycleContract<Address>(client, adapter.implementation, lifecycleAdapterAbi, "core", [], block),
35
+ readLifecycleContract<Hex>(client, adapter.implementation, lifecycleAdapterAbi, "dependencyDigest", [], block),
36
+ ]);
33
37
  if (keccak256(implementationCode).toLowerCase() !== adapter.codeHash.toLowerCase()) throw new LifecyclePlanningError("PROFILE_CODE_HASH", "Adapter code hash differs from immutable registry approval");
34
- const authority = await readLifecycleContract<Address>(client, adapter.implementation, lifecycleAdapterAbi, "core", [], block);
35
- const digest = await readLifecycleContract<Hex>(client, adapter.implementation, lifecycleAdapterAbi, "dependencyDigest", [], block);
36
38
  if (authority.toLowerCase() !== orchestrator.toLowerCase() || digest.toLowerCase() !== registration.dependencyDigest.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Adapter authority or dependency digest differs from immutable registry approval");
37
39
  if (abyss) {
38
40
  if (topology.hookDeployer !== zeroAddress || topology.hookCreationCodeHash !== zeroHash) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Abyss does not admit a V4 hook deployment graph");
39
- const factory = await readLifecycleContract<Address>(client, adapter.implementation, abyssLifecycleAdapterAbi, "factory", [], block);
40
- const schema = await readLifecycleContract<Hex>(client, adapter.implementation, abyssLifecycleAdapterAbi, "CONFIG_SCHEMA", [], block);
41
- const actualVersion = await readLifecycleContract<number>(client, adapter.implementation, abyssLifecycleAdapterAbi, "CONFIG_VERSION", [], block);
41
+ const [factory, schema, actualVersion] = await Promise.all([
42
+ readLifecycleContract<Address>(client, adapter.implementation, abyssLifecycleAdapterAbi, "factory", [], block),
43
+ readLifecycleContract<Hex>(client, adapter.implementation, abyssLifecycleAdapterAbi, "CONFIG_SCHEMA", [], block),
44
+ readLifecycleContract<number>(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) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Abyss profile is not the exact canonical factory/variant/schema binding");
@@ -60,34 +64,43 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
60
64
  b.maximumOracleCardinality < 2 || b.maximumOracleCardinality > 4096 || b.feeModeFlags === 0 || (b.feeModeFlags & ~3) !== 0 ||
61
65
  envelope.protocolTreasury === zeroAddress || (envelope.protocolFeeDenominator !== 0 && (envelope.protocolFeeDenominator < 4 || envelope.protocolFeeDenominator > 10))) throw new LifecyclePlanningError("PROFILE_BOUNDS", "Registry envelope has unsupported executable bounds");
62
66
  const abi = bound ? poolMarketAdapterV1Abi : sharedMarketAdapterV1Abi;
63
- for (const [getter, expected] of [["PROFILE_ID", id], ["CONFIG_SCHEMA", registration.configSchema], ["implementationRegistry", registry]] as const) {
64
- const actual = await readLifecycleContract<Hex>(client, adapter.implementation, abi, getter, [], block);
65
- if (actual.toLowerCase() !== expected.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed adapter metadata differs from registry certification");
67
+ const [metadata, actualVersion] = await Promise.all([
68
+ Promise.all((["PROFILE_ID", "CONFIG_SCHEMA", "implementationRegistry"] as const).map((getter) => readLifecycleContract<Hex>(client, adapter.implementation, abi, getter, [], block))),
69
+ readLifecycleContract<number>(client, adapter.implementation, abi, "CONFIG_VERSION", [], block),
70
+ ]);
71
+ for (const [index, expected] of [id, registration.configSchema, registry].entries()) {
72
+ if (metadata[index]?.toLowerCase() !== expected.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed adapter metadata differs from registry certification");
66
73
  }
67
- if (await readLifecycleContract<number>(client, adapter.implementation, abi, "CONFIG_VERSION", [], block) !== version) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed adapter config version differs from certification");
74
+ if (actualVersion !== version) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed adapter config version differs from certification");
68
75
  const g = envelope.graph;
69
76
  if (registration.factory !== zeroAddress || registration.venue.toLowerCase() !== g.manager.toLowerCase() || registration.hook.toLowerCase() !== g.hookRoot.toLowerCase() ||
70
77
  topology.hookDeployer.toLowerCase() !== g.hookDeployer.toLowerCase() || topology.hookCreationCodeHash.toLowerCase() !== g.hookCreationCodeHash.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed venue/topology differs from the frozen envelope graph");
71
- for (const [getter, expected] of [["poolManager", g.manager], ["hookRoot", g.hookRoot], ["oracleFactory", g.oracleFactory], ["locker", g.locker], ["collectorFactory", g.collectorFactory], ["hookDeployer", g.hookDeployer]] as const) {
72
- const actual = await readLifecycleContract<Address>(client, adapter.implementation, abi, getter, [], block);
73
- if (actual.toLowerCase() !== expected.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Live adapter dependency differs from the frozen reviewed graph");
74
- }
75
- for (const [address, expectedHash] of [[orchestrator, g.coreCodeHash], [g.manager, g.managerCodeHash], [g.oracleFactory, g.oracleFactoryCodeHash],
78
+ const deployerAbi = shared ? sharedHookDeployerV1Abi : poolHookDeployerV1Abi;
79
+ const codeBindings = [[orchestrator, g.coreCodeHash], [g.manager, g.managerCodeHash], [g.oracleFactory, g.oracleFactoryCodeHash],
76
80
  [g.locker, g.lockerCodeHash], [g.collectorFactory, g.collectorFactoryCodeHash], [g.collectorDeployer, g.collectorDeployerCodeHash],
77
- [g.hookDeployer, g.hookDeployerCodeHash]] as const) {
78
- if (keccak256(await readDependencyCode(client, address, block)).toLowerCase() !== expectedHash.toLowerCase()) throw new LifecyclePlanningError("PROFILE_CODE_HASH", "Live runtime differs from the frozen reviewed graph hash");
81
+ [g.hookDeployer, g.hookDeployerCodeHash]] as const;
82
+ const [dependencies, dependencyCodes, collectorDeployer, launcher, lockerManager, creationCodeHash, chunk0, chunk1] = await Promise.all([
83
+ Promise.all((["poolManager", "hookRoot", "oracleFactory", "locker", "collectorFactory", "hookDeployer"] as const).map((getter) => readLifecycleContract<Address>(client, adapter.implementation, abi, getter, [], block))),
84
+ Promise.all(codeBindings.map(([address]) => readDependencyCode(client, address, block))),
85
+ readLifecycleContract<Address>(client, g.collectorFactory, poolFeeCollectorFactoryV1Abi, "collectorDeployer", [], block),
86
+ readLifecycleContract<Address>(client, g.locker, lifecycleV4LockerAbi, "launcher", [], block),
87
+ readLifecycleContract<Address>(client, g.locker, lifecycleV4LockerAbi, "poolManager", [], block),
88
+ readLifecycleContract<Hex>(client, g.hookDeployer, deployerAbi, "creationCodeHash", [], block),
89
+ readLifecycleContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk0", [], block),
90
+ readLifecycleContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk1", [], block),
91
+ ]);
92
+ for (const [index, expected] of [g.manager, g.hookRoot, g.oracleFactory, g.locker, g.collectorFactory, g.hookDeployer].entries()) {
93
+ if (dependencies[index]?.toLowerCase() !== expected.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Live adapter dependency differs from the frozen reviewed graph");
94
+ }
95
+ for (const [index, [, expectedHash]] of codeBindings.entries()) {
96
+ if (keccak256(dependencyCodes[index]!).toLowerCase() !== expectedHash.toLowerCase()) throw new LifecyclePlanningError("PROFILE_CODE_HASH", "Live runtime differs from the frozen reviewed graph hash");
79
97
  }
80
- const collectorDeployer = await readLifecycleContract<Address>(client, g.collectorFactory, poolFeeCollectorFactoryV1Abi, "collectorDeployer", [], block);
81
- const launcher = await readLifecycleContract<Address>(client, g.locker, lifecycleV4LockerAbi, "launcher", [], block);
82
- const lockerManager = await readLifecycleContract<Address>(client, g.locker, lifecycleV4LockerAbi, "poolManager", [], block);
83
98
  if (collectorDeployer.toLowerCase() !== g.collectorDeployer.toLowerCase() || launcher.toLowerCase() !== adapter.implementation.toLowerCase() ||
84
99
  lockerManager.toLowerCase() !== g.manager.toLowerCase() || g.locker.toLowerCase() === g.manager.toLowerCase() || g.locker.toLowerCase() === g.hookRoot.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed collector or locker authority differs from the frozen graph");
85
- const deployerAbi = shared ? sharedHookDeployerV1Abi : poolHookDeployerV1Abi;
86
- const creationCodeHash = await readLifecycleContract<Hex>(client, g.hookDeployer, deployerAbi, "creationCodeHash", [], block);
87
- const chunk0 = await readLifecycleContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk0", [], block);
88
- const chunk1 = await readLifecycleContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk1", [], block);
89
- const chunk0Code = await readDependencyCode(client, chunk0, block);
90
- const chunk1Code = chunk1 === zeroAddress ? "0x" : await readDependencyCode(client, chunk1, block);
100
+ const [chunk0Code, chunk1Code] = await Promise.all([
101
+ readDependencyCode(client, chunk0, block),
102
+ chunk1 === zeroAddress ? Promise.resolve<Hex>("0x") : readDependencyCode(client, chunk1, block),
103
+ ]);
91
104
  if (chunk0.toLowerCase() !== g.codeChunk0.toLowerCase() || chunk1.toLowerCase() !== g.codeChunk1.toLowerCase() || chunk0.toLowerCase() === chunk1.toLowerCase() ||
92
105
  !chunk0Code.startsWith("0x00") || chunk0Code.length <= 4 || (chunk0Code.length - 2) / 2 > 24576 ||
93
106
  (chunk1 !== zeroAddress && (!chunk1Code.startsWith("0x00") || chunk1Code.length <= 4 || (chunk1Code.length - 2) / 2 > 24576 || (chunk0Code.length - 2) / 2 !== 24576)) ||
@@ -97,18 +110,22 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
97
110
  creationCodeHash.toLowerCase() !== g.hookCreationCodeHash.toLowerCase() || keccak256(creationCode).toLowerCase() !== creationCodeHash.toLowerCase() ||
98
111
  hashLaunchDependencies({ chainId, core: orchestrator, registry, registrar: adapter.implementation, graph: g }).toLowerCase() !== digest.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed creation artifact, constructor bound or graph digest differs from admission");
99
112
  if (shared) {
100
- const rootCode = await readDependencyCode(client, g.hookRoot, block);
101
- const recordedHash = await readLifecycleContract<Hex>(client, g.hookDeployer, deployerAbi, "deployedCodeHash", [g.hookRoot], block);
113
+ const [rootCode, recordedHash, remotePrediction, dependencies, requiredFlags, allFlags] = await Promise.all([
114
+ readDependencyCode(client, g.hookRoot, block),
115
+ readLifecycleContract<Hex>(client, g.hookDeployer, deployerAbi, "deployedCodeHash", [g.hookRoot], block),
116
+ readLifecycleContract<Address>(client, g.hookDeployer, sharedHookDeployerV1Abi, "predict", [g.manager, adapter.implementation, g.oracleFactory, g.sharedHookSalt], block),
117
+ Promise.all((["registrar", "poolManager", "oracleFactory"] as const).map((getter) => readLifecycleContract<Address>(client, g.hookRoot, lifecycleV4HookAbi, getter, [], block))),
118
+ readLifecycleContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "REQUIRED_HOOK_FLAGS", [], block),
119
+ readLifecycleContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "ALL_HOOK_MASK", [], block),
120
+ ]);
102
121
  const initCodeHash = keccak256(concatHex([creationCode, encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "address" }], [g.manager, adapter.implementation, g.oracleFactory])]));
103
122
  const prediction = predictPoolBoundHookAddress({ deployer: g.hookDeployer, initCodeHash, salt: g.sharedHookSalt });
104
- const remotePrediction = await readLifecycleContract<Address>(client, g.hookDeployer, sharedHookDeployerV1Abi, "predict", [g.manager, adapter.implementation, g.oracleFactory, g.sharedHookSalt], block);
105
123
  if (recordedHash === zeroHash || recordedHash.toLowerCase() !== g.hookRuntimeCodeHash.toLowerCase() || keccak256(rootCode).toLowerCase() !== recordedHash.toLowerCase() ||
106
124
  !hasLifecycleV4HookPermissions(g.hookRoot) || prediction.toLowerCase() !== g.hookRoot.toLowerCase() || remotePrediction.toLowerCase() !== prediction.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared root lacks exact typed-deployer runtime and constructor/salt provenance");
107
- for (const [getter, expected] of [["registrar", adapter.implementation], ["poolManager", g.manager], ["oracleFactory", g.oracleFactory]] as const) {
108
- if ((await readLifecycleContract<Address>(client, g.hookRoot, lifecycleV4HookAbi, getter, [], block)).toLowerCase() !== expected.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared hook immutable graph differs from admission");
125
+ for (const [index, expected] of [adapter.implementation, g.manager, g.oracleFactory].entries()) {
126
+ if (dependencies[index]?.toLowerCase() !== expected.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared hook immutable graph differs from admission");
109
127
  }
110
- if (await readLifecycleContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "REQUIRED_HOOK_FLAGS", [], block) !== 0x1afcn ||
111
- await readLifecycleContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "ALL_HOOK_MASK", [], block) !== 0x3fffn) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared hook callbacks differ from admission");
128
+ if (requiredFlags !== 0x1afcn || allFlags !== 0x3fffn) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared hook callbacks differ from admission");
112
129
  } else if (g.hookRoot !== zeroAddress || g.hookRuntimeCodeHash !== zeroHash || g.sharedHookSalt !== zeroHash) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Bound topology cannot reuse a shared root");
113
130
  if ([zeroAddress, orchestrator, registry, adapter.implementation, g.manager, g.hookRoot, g.locker, g.collectorFactory, g.collectorDeployer, g.hookDeployer].some((address) => address.toLowerCase() === envelope.beneficiary.toLowerCase())) throw new LifecyclePlanningError("PROFILE_TERMS", "Stable author identity is an excluded reviewed graph destination");
114
131
  return { topology, boundGraph: bound ? { manager: g.manager, oracleFactory: g.oracleFactory, locker: g.locker, collectorFactory: g.collectorFactory, deployer: g.hookDeployer, creationCode } : undefined };
@@ -127,43 +144,64 @@ export async function validateV4LifecycleOracle(options: {
127
144
  }
128
145
 
129
146
  export async function readLifecycleProfiles(options: { client: LifecycleRpcClient; orchestrator: Address; profileIds?: readonly Hex[]; offset?: bigint; limit?: bigint }, pinnedBlock?: LifecycleBlock): Promise<LifecycleProfile[]> {
130
- const block = pinnedBlock ?? await readLifecycleBlock(options.client);
131
- const chainId = rpcQuantity(await lifecycleRpc(options.client, "eth_chainId"), "chain ID");
132
- const registry = await readLifecycleContract<Address>(options.client, options.orchestrator, launchLifecycleAbi, "registry", [], block);
133
- if ((await readLifecycleContract<Address>(options.client, registry, lifecycleRegistryAbi, "core", [], block)).toLowerCase() !== options.orchestrator.toLowerCase()) throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to the selected lifecycle orchestrator");
134
- const offset = options.offset ?? 0n; const limit = options.limit ?? 100n;
135
- if (offset < 0n || limit < 0n || limit > 100n) throw new LifecyclePlanningError("INVALID_PROFILE_PAGE", "Registry profile pages require unsigned offset and limit at most 100");
136
- const ids = options.profileIds ?? await readLifecycleContract<readonly Hex[]>(options.client, registry, lifecycleRegistryAbi, "profileIds", [offset, limit], block);
137
- const protocolMaximumDeveloperFeeBps = await readLifecycleContract<number>(options.client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
138
- const profiles: LifecycleProfile[] = [];
139
- for (const id of ids) {
140
- const registration = await readLifecycleContract<ProfileRegistrationV1>(options.client, registry, lifecycleRegistryAbi, "profile", [id], block);
141
- const adapter = await readLifecycleContract<AdapterRegistrationV1>(options.client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block);
142
- const schema = registration.configSchema.toLowerCase();
143
- 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";
144
- let topology: ProfileTopologyV1 = { hookTopology: 0, configVersion: adapter.configVersion, hookDeployer: zeroAddress, hookCreationCodeHash: zeroHash };
145
- let envelope: LaunchEnvelopeV2 | undefined; let developerTerms: LifecycleDeveloperTerms | undefined;
146
- let admitted = false; let reason: string | undefined;
147
- try {
148
- if (venueKind === "unknown") throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
149
- topology = await readLifecycleProfileTopology(options.client, registry, id, block);
150
- if (venueKind === "uniswap-v4") {
151
- envelope = await readLifecycleContract<LaunchEnvelopeV2>(options.client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block);
152
- const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = await readLifecycleContract<readonly [Address, Address, number, Hex, boolean]>(options.client, registry, lifecycleRegistryAbi, "developerTerms", [id], block);
153
- developerTerms = { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled };
154
- if (implementation.toLowerCase() !== adapter.implementation.toLowerCase() || beneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
155
- termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
156
- maximumDeveloperFeeBps > protocolMaximumDeveloperFeeBps) throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
157
- }
158
- await certifyLifecycleProfile(options.client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
159
- const eligible = await readLifecycleContract<Address>(options.client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, topology.configVersion, LIFECYCLE_REQUIRED_CAPABILITIES], block);
160
- if (eligible.toLowerCase() !== adapter.implementation.toLowerCase() || (developerTerms !== undefined && !developerTerms.enabled)) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
161
- admitted = true;
162
- } catch (failure) { reason = failure instanceof Error ? failure.message : String(failure); }
163
- profiles.push({ id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind });
164
- }
165
- if (pinnedBlock === undefined) await assertLifecycleBlock(options.client, block);
166
- return profiles;
147
+ return withLifecycleReadClient(options.client, async (client) => {
148
+ const [block, chainIdValue] = await Promise.all([
149
+ pinnedBlock ?? readLifecycleBlock(client),
150
+ lifecycleRpc(client, "eth_chainId"),
151
+ ]);
152
+ const chainId = rpcQuantity(chainIdValue, "chain ID");
153
+ const registry = await readLifecycleContract<Address>(client, options.orchestrator, launchLifecycleAbi, "registry", [], block);
154
+ const offset = options.offset ?? 0n; const limit = options.limit ?? 100n;
155
+ if (offset < 0n || limit < 0n || limit > 100n) throw new LifecyclePlanningError("INVALID_PROFILE_PAGE", "Registry profile pages require unsigned offset and limit at most 100");
156
+ const [authority, ids] = await Promise.all([
157
+ readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
158
+ options.profileIds ?? readLifecycleContract<readonly Hex[]>(client, registry, lifecycleRegistryAbi, "profileIds", [offset, limit], block),
159
+ ]);
160
+ if (authority.toLowerCase() !== options.orchestrator.toLowerCase()) throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to the selected lifecycle orchestrator");
161
+ let protocolMaximumDeveloperFeeBpsPromise: Promise<number> | undefined;
162
+ const readProtocolMaximumDeveloperFeeBps = () => protocolMaximumDeveloperFeeBpsPromise ??=
163
+ readLifecycleContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
164
+ const profiles = await Promise.all(ids.map(async (id): Promise<LifecycleProfile> => {
165
+ const registration = await readLifecycleContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [id], block);
166
+ const adapter = await readLifecycleContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block);
167
+ const schema = registration.configSchema.toLowerCase();
168
+ 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";
169
+ let topology: ProfileTopologyV1 = { hookTopology: 0, configVersion: adapter.configVersion, hookDeployer: zeroAddress, hookCreationCodeHash: zeroHash };
170
+ let envelope: LaunchEnvelopeV2 | undefined; let developerTerms: LifecycleDeveloperTerms | undefined;
171
+ let protocolMaximumDeveloperFeeBps: number | undefined;
172
+ let admitted = false; let reason: string | undefined;
173
+ try {
174
+ if (venueKind === "unknown") throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
175
+ const [recordedTopology, reviewed] = await Promise.all([
176
+ readLifecycleProfileTopology(client, registry, id, block),
177
+ venueKind === "uniswap-v4" ? Promise.all([
178
+ readLifecycleContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block),
179
+ readLifecycleContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
180
+ readProtocolMaximumDeveloperFeeBps(),
181
+ ]) : undefined,
182
+ ]);
183
+ topology = recordedTopology;
184
+ if (reviewed !== undefined) {
185
+ envelope = reviewed[0];
186
+ protocolMaximumDeveloperFeeBps = reviewed[2];
187
+ const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = reviewed[1];
188
+ developerTerms = { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled };
189
+ if (implementation.toLowerCase() !== adapter.implementation.toLowerCase() || beneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
190
+ termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
191
+ maximumDeveloperFeeBps > reviewed[2]) throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
192
+ }
193
+ await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
194
+ const eligible = await readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, topology.configVersion, LIFECYCLE_REQUIRED_CAPABILITIES], block);
195
+ if (eligible.toLowerCase() !== adapter.implementation.toLowerCase() || (developerTerms !== undefined && !developerTerms.enabled)) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
196
+ admitted = true;
197
+ } catch (failure) { reason = failure instanceof Error ? failure.message : String(failure); }
198
+ return { id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind };
199
+ }));
200
+ // A required V4 cap read still rejects the invocation on failure; Abyss has no developer-fee terms.
201
+ if (protocolMaximumDeveloperFeeBpsPromise !== undefined) await protocolMaximumDeveloperFeeBpsPromise;
202
+ if (pinnedBlock === undefined) await assertLifecycleBlock(client, block, chainId);
203
+ return profiles;
204
+ });
167
205
  }
168
206
 
169
207
  async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, block: LifecycleBlock): Promise<{ deployment: PoolBoundHookDeployment; parameters: PoolBoundHookParametersV1 }> {
@@ -173,17 +211,22 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
173
211
  const chainId = rpcQuantity(await lifecycleRpc(client, "eth_chainId"), "chain ID");
174
212
  if (chainId !== plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed plan");
175
213
  const registry = await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "registry", [], block);
176
- if ((await readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block)).toLowerCase() !== plan.orchestrator.toLowerCase()) throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to this lifecycle core");
177
- const registration = await readLifecycleContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [market.profileId], block);
214
+ const [authority, registration] = await Promise.all([
215
+ readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
216
+ readLifecycleContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [market.profileId], block),
217
+ ]);
218
+ if (authority.toLowerCase() !== plan.orchestrator.toLowerCase()) throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to this lifecycle core");
178
219
  const adapter = await readLifecycleContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block);
179
220
  if (registration.adapterId.toLowerCase() !== market.adapterId.toLowerCase()) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market adapter differs from the profile's immutable registration");
180
- const recorded = await readLifecycleProfileTopology(client, registry, market.profileId, block);
181
- const envelope = await readLifecycleContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [market.profileId], block);
182
- const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = await readLifecycleContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [market.profileId], block);
183
- const protocolMaximumDeveloperFeeBps = await readLifecycleContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
221
+ const [recorded, envelope, [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled], protocolMaximumDeveloperFeeBps, token] = await Promise.all([
222
+ readLifecycleProfileTopology(client, registry, market.profileId, block),
223
+ readLifecycleContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [market.profileId], block),
224
+ readLifecycleContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [market.profileId], block),
225
+ readLifecycleContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block),
226
+ readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "predictToken", [plan], block),
227
+ ]);
184
228
  const { boundGraph } = await certifyLifecycleProfile(client, plan.orchestrator, registry, market.profileId, registration, adapter, recorded, envelope, block, chainId);
185
229
  if (boundGraph === undefined) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Market does not have a certified pool-bound deployer graph");
186
- const token = await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "predictToken", [plan], block);
187
230
  const config = decodePoolBoundV4LifecycleMarketConfig(market.config);
188
231
  validateV4LifecycleMarket({ market, config, token, profile: {
189
232
  id: market.profileId, registration, adapter, topology: recorded, envelope,
@@ -199,33 +242,35 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
199
242
  marketCommitment: hashPoolBoundV4MarketCommitment({ chainId, core: plan.orchestrator, registrar: adapter.implementation, token, market }),
200
243
  expectedPositionCount: config.positions.length,
201
244
  };
202
- const [parameters, parameterSalt] = await readLifecycleContract<readonly [PoolBoundHookParametersV1, Hex]>(client, boundGraph.collectorFactory, poolFeeCollectorFactoryV1Abi, "poolBoundHookParameters", [adapter.implementation, token, market], block);
203
- const [deployer, initCodeHash, salt, predictedHook] = await readLifecycleContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block);
204
- const remoteInitCodeHash = await readLifecycleContract<Hex>(client, boundGraph.deployer, poolHookDeployerV1Abi, "initCodeHash", [expected], block);
205
- const remotePrediction = await readLifecycleContract<Address>(client, boundGraph.deployer, poolHookDeployerV1Abi, "predict", [expected, config.hookSalt], block);
245
+ const [[parameters, parameterSalt], [deployer, initCodeHash, salt, predictedHook], remoteInitCodeHash, remotePrediction] = await Promise.all([
246
+ readLifecycleContract<readonly [PoolBoundHookParametersV1, Hex]>(client, boundGraph.collectorFactory, poolFeeCollectorFactoryV1Abi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
247
+ readLifecycleContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block),
248
+ readLifecycleContract<Hex>(client, boundGraph.deployer, poolHookDeployerV1Abi, "initCodeHash", [expected], block),
249
+ readLifecycleContract<Address>(client, boundGraph.deployer, poolHookDeployerV1Abi, "predict", [expected, config.hookSalt], block),
250
+ ]);
206
251
  const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected);
207
252
  const localPrediction = predictPoolBoundHookAddress({ deployer: boundGraph.deployer, initCodeHash: localInitCodeHash, salt: config.hookSalt });
208
253
  if (encodePoolBoundHookParameters(parameters).toLowerCase() !== encodePoolBoundHookParameters(expected).toLowerCase() || parameterSalt.toLowerCase() !== config.hookSalt.toLowerCase() || salt.toLowerCase() !== config.hookSalt.toLowerCase() || deployer.toLowerCase() !== boundGraph.deployer.toLowerCase() || initCodeHash.toLowerCase() !== localInitCodeHash.toLowerCase() || remoteInitCodeHash.toLowerCase() !== localInitCodeHash.toLowerCase() || predictedHook.toLowerCase() !== localPrediction.toLowerCase() || remotePrediction.toLowerCase() !== localPrediction.toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Bound hook metadata differs from the exact certified constructor economics, factory, initcode, salt or CREATE2 prediction");
209
- const hookCode = rpcHex(await lifecycleRpc(client, "eth_getCode", [predictedHook, toHex(block.number)]), "bound hook code");
254
+ const hookCode = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [predictedHook, toHex(block.number)], block), "bound hook code");
210
255
  if (hookCode !== "0x") {
211
- const deployedCodeHash = await readLifecycleContract<Hex>(client, deployer, poolHookDeployerV1Abi, "deployedCodeHash", [predictedHook], block);
256
+ const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions] = await Promise.all([
257
+ readLifecycleContract<Hex>(client, deployer, poolHookDeployerV1Abi, "deployedCodeHash", [predictedHook], block),
258
+ Promise.all((["poolManager", "registrar", "oracleFactory", "core", "liquidityLocker", "token"] as const).map((getter) => readLifecycleContract<Address>(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block))),
259
+ readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block),
260
+ readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block),
261
+ readLifecycleContract<Hex>(client, predictedHook, fixedFeePoolHookV1Abi, "boundPoolId", [], block),
262
+ readLifecycleContract<Hex>(client, predictedHook, fixedFeePoolHookV1Abi, "deploymentConfigHash", [], block),
263
+ readLifecycleContract<Hex>(client, predictedHook, fixedFeePoolHookV1Abi, "marketCommitment", [], block),
264
+ readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "openingSqrtPriceX96", [], block),
265
+ readLifecycleContract<number>(client, predictedHook, fixedFeePoolHookV1Abi, "expectedPositionCount", [], block),
266
+ ]);
212
267
  if ((hookCode.length - 2) / 2 > 24576 || deployedCodeHash === zeroHash || deployedCodeHash.toLowerCase() !== keccak256(hookCode).toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook lacks this exact typed deployer's bounded runtime-code provenance");
213
- for (const [getter, expectedAddress] of [
214
- ["poolManager", expected.poolManager], ["registrar", expected.registrar], ["oracleFactory", expected.oracleFactory],
215
- ["core", expected.core], ["liquidityLocker", expected.liquidityLocker], ["token", expected.token],
216
- ] as const) {
217
- const actual = await readLifecycleContract<Address>(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block);
218
- if (actual.toLowerCase() !== expectedAddress.toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook immutable dependency graph differs from the committed constructor");
268
+ for (const [index, expectedAddress] of [expected.poolManager, expected.registrar, expected.oracleFactory, expected.core, expected.liquidityLocker, expected.token].entries()) {
269
+ if (dependencies[index]?.toLowerCase() !== expectedAddress.toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook immutable dependency graph differs from the committed constructor");
219
270
  }
220
- if (await readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block) !== 0x1afcn ||
221
- await readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block) !== 0x3fffn) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing bound hook callback declarations differ from the reviewed envelope");
271
+ if (requiredFlags !== 0x1afcn || allFlags !== 0x3fffn) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing bound hook callback declarations differ from the reviewed envelope");
222
272
  const [currency0, currency1] = BigInt(token) < BigInt(market.quoteAsset) ? [token, market.quoteAsset] : [market.quoteAsset, token];
223
273
  const expectedPoolId = keccak256(encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }], [currency0, currency1, config.lpFeePips, config.tickSpacing, predictedHook]));
224
- const actualPoolId = await readLifecycleContract<Hex>(client, predictedHook, fixedFeePoolHookV1Abi, "boundPoolId", [], block);
225
- const deploymentConfigHash = await readLifecycleContract<Hex>(client, predictedHook, fixedFeePoolHookV1Abi, "deploymentConfigHash", [], block);
226
- const marketCommitment = await readLifecycleContract<Hex>(client, predictedHook, fixedFeePoolHookV1Abi, "marketCommitment", [], block);
227
- const opening = await readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "openingSqrtPriceX96", [], block);
228
- const positions = await readLifecycleContract<number>(client, predictedHook, fixedFeePoolHookV1Abi, "expectedPositionCount", [], block);
229
274
  if (actualPoolId.toLowerCase() !== expectedPoolId.toLowerCase() || deploymentConfigHash.toLowerCase() !== keccak256(encodePoolBoundHookParameters(expected)).toLowerCase() || marketCommitment.toLowerCase() !== expected.marketCommitment.toLowerCase() || opening !== expected.sqrtPriceX96 || positions !== expected.expectedPositionCount) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook key, economic commitment or immutable opening geometry changed");
230
275
  }
231
276
  return { deployment: { deployer, initCodeHash, salt, predictedHook }, parameters: expected };
@@ -235,21 +280,25 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
235
280
  export async function readPoolBoundHookDeployment(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, pinnedBlock?: LifecycleBlock): Promise<PoolBoundHookDeployment> {
236
281
  const commitment = hashLaunchPlan(options.plan);
237
282
  const block = pinnedBlock ?? await readLifecycleBlock(options.client);
238
- const { deployment } = await readPoolBoundHookDeploymentDetails(options, block);
239
- if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
240
- if (pinnedBlock === undefined) await assertLifecycleBlock(options.client, block);
241
- return deployment;
283
+ return withLifecycleReadClient(options.client, async (client) => {
284
+ const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
285
+ if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
286
+ if (pinnedBlock === undefined) await assertLifecycleBlock(client, block, options.plan.chainId);
287
+ return deployment;
288
+ });
242
289
  }
243
290
 
244
291
  /** Optional permissionless predeployment calldata; no token deployment, pool registration or wallet broadcast. */
245
292
  export async function buildPoolBoundHookDeploymentTransaction(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }): Promise<{ to: Address; data: Hex; value: bigint; deployment: PoolBoundHookDeployment }> {
246
293
  const commitment = hashLaunchPlan(options.plan);
247
294
  const block = await readLifecycleBlock(options.client);
248
- const { deployment, parameters } = await readPoolBoundHookDeploymentDetails(options, block);
249
- if (!hasLifecycleV4HookPermissions(deployment.predictedHook)) throw new LifecyclePlanningError("INVALID_HOOK_BITS", "Finalize a salt with the exact lifecycle hook permissions before predeployment");
250
- if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while constructing hook deployment calldata");
251
- await assertLifecycleBlock(options.client, block);
252
- return { to: deployment.deployer, data: encodeFunctionData({ abi: poolHookDeployerV1Abi, functionName: "deploy", args: [parameters, deployment.salt] }), value: 0n, deployment };
295
+ return withLifecycleReadClient(options.client, async (client) => {
296
+ const { deployment, parameters } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
297
+ if (!hasLifecycleV4HookPermissions(deployment.predictedHook)) throw new LifecyclePlanningError("INVALID_HOOK_BITS", "Finalize a salt with the exact lifecycle hook permissions before predeployment");
298
+ if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while constructing hook deployment calldata");
299
+ await assertLifecycleBlock(client, block, options.plan.chainId);
300
+ return { to: deployment.deployer, data: encodeFunctionData({ abi: poolHookDeployerV1Abi, functionName: "deploy", args: [parameters, deployment.salt] }), value: 0n, deployment };
301
+ });
253
302
  }
254
303
 
255
304
  export function validateLifecycleMarketIdentity(identity: MarketIdentityV1, plan: LaunchPlanV1, token: Address, marketIndex: number): void {
@@ -302,8 +351,10 @@ async function readReceipt(reference: LifecycleReceiptReference, options: ReadLa
302
351
  const observed = await lifecycleRpc(options.client, "eth_getBlockByNumber", [toHex(observedNumber), false]);
303
352
  if (observed === null || rpcHex(rpcObject(observed, "observed receipt block").hash, "observed receipt block hash").toLowerCase() !== reference.observedBlockHash.toLowerCase()) return { ...base, status: "reorged", blockNumber: observedNumber, blockHash: reference.observedBlockHash };
304
353
  }
305
- const receiptValue = await lifecycleRpc(options.client, "eth_getTransactionReceipt", [effectiveHash]);
306
- const transactionValue = await lifecycleRpc(options.client, "eth_getTransactionByHash", [effectiveHash]);
354
+ const [receiptValue, transactionValue] = await Promise.all([
355
+ lifecycleRpc(options.client, "eth_getTransactionReceipt", [effectiveHash]),
356
+ lifecycleRpc(options.client, "eth_getTransactionByHash", [effectiveHash]),
357
+ ]);
307
358
  if (receiptValue === null) {
308
359
  // An absent receipt with an unchanged canonical observed block is not a
309
360
  // reorg: the transaction is simply not (yet) mined. A missing/changed
@@ -332,8 +383,12 @@ async function readReceipt(reference: LifecycleReceiptReference, options: ReadLa
332
383
 
333
384
  /** Reads confirmation-bound canonical state and receipt provenance; local step counters are never used. */
334
385
  export async function readLaunchProgress(options: ReadLaunchProgressOptions, pinnedBlock?: LifecycleBlock): Promise<CanonicalLaunchProgress> {
335
- const { client, planned } = options;
336
- const block = pinnedBlock ?? await readLifecycleBlock(client);
386
+ const block = pinnedBlock ?? await readLifecycleBlock(options.client);
387
+ return withLifecycleReadClient(options.client, (client) => readLaunchProgressAtBlock(options, client, block, pinnedBlock === undefined));
388
+ }
389
+
390
+ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, client: LifecycleRpcClient, block: LifecycleBlock, checkBlock: boolean): Promise<CanonicalLaunchProgress> {
391
+ const { planned } = options;
337
392
  const confirmationDepth = options.confirmations ?? planned.confirmations;
338
393
  if (!Number.isSafeInteger(confirmationDepth) || confirmationDepth < 1) throw new LifecyclePlanningError("INVALID_CONFIRMATIONS", "Confirmation depth must be a positive integer");
339
394
  const confirmedNumber = block.number >= BigInt(confirmationDepth - 1) ? block.number - BigInt(confirmationDepth - 1) : 0n;
@@ -342,37 +397,43 @@ export async function readLaunchProgress(options: ReadLaunchProgressOptions, pin
342
397
  if (chainId !== planned.plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed launch");
343
398
  if (planned.account.toLowerCase() !== planned.plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
344
399
  if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its commitment");
345
- const head = await readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], block, planned.account);
346
- const canonical = confirmedNumber === block.number ? head : await readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], confirmedBlock, planned.account);
347
- const token = await readLifecycleContract<Address>(client, planned.plan.orchestrator, launchLifecycleAbi, "predictToken", [planned.plan], block);
400
+ const [head, confirmedProgress, token, confirmedNonce, headNonce, pendingNonce] = await Promise.all([
401
+ readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], block, planned.account),
402
+ confirmedNumber === block.number ? undefined : readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], confirmedBlock, planned.account),
403
+ readLifecycleContract<Address>(client, planned.plan.orchestrator, launchLifecycleAbi, "predictToken", [planned.plan], block),
404
+ lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(confirmedBlock.number)]),
405
+ confirmedNumber === block.number ? undefined : lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(block.number)]),
406
+ lifecycleRpc(client, "eth_getTransactionCount", [planned.account, "pending"]),
407
+ ]);
408
+ const canonical = confirmedProgress ?? head;
348
409
  if (token.toLowerCase() !== planned.predictedToken.toLowerCase()) throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed for the committed token configuration");
349
410
  for (const progress of [canonical, head]) if (progress.phase !== LifecyclePhase.None) {
350
411
  if (progress.planHash.toLowerCase() !== planned.planHash.toLowerCase() || progress.launchId.toLowerCase() !== planned.launchId.toLowerCase() || progress.creator.toLowerCase() !== planned.account.toLowerCase() || progress.nonce !== planned.plan.nonce || progress.token.toLowerCase() !== token.toLowerCase() || progress.marketCount !== planned.plan.markets.length || progress.buyCount !== planned.plan.buys.length || progress.deadline !== planned.plan.deadline) throw new LifecyclePlanningError("PLAN_REPLAY", "This creator nonce is already bound to a different economic launch");
351
412
  if (progress.mode !== (planned.mode === "atomic" ? LifecycleMode.Atomic : LifecycleMode.Staged)) throw new LifecyclePlanningError("MODE_MISMATCH", "Execution mode differs from the already recorded launch");
352
413
  if (progress.preparedMarkets > progress.marketCount || progress.phase === LifecyclePhase.Activating || (progress.phase === LifecyclePhase.Ready && progress.preparedMarkets !== progress.marketCount)) throw new LifecyclePlanningError("INVALID_PROGRESS", "Canonical progress violates the committed ordered state machine");
353
414
  }
354
- const confirmedAccountNonce = rpcQuantity(await lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(confirmedBlock.number)]), "confirmed account nonce");
355
- const headAccountNonce = confirmedNumber === block.number ? confirmedAccountNonce : rpcQuantity(await lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(block.number)]), "head account nonce");
356
- const pendingAccountNonce = rpcQuantity(await lifecycleRpc(client, "eth_getTransactionCount", [planned.account, "pending"]), "pending account nonce");
415
+ const confirmedAccountNonce = rpcQuantity(confirmedNonce, "confirmed account nonce");
416
+ const headAccountNonce = headNonce === undefined ? confirmedAccountNonce : rpcQuantity(headNonce, "head account nonce");
417
+ const pendingAccountNonce = rpcQuantity(pendingNonce, "pending account nonce");
357
418
  const progressParameters = [{ type: "tuple", components: launchProgressV1Components }] as const;
358
419
  const confirmationSafe = confirmedAccountNonce === headAccountNonce && pendingAccountNonce === headAccountNonce && keccak256(encodeAbiParameters(progressParameters, [canonical])) === keccak256(encodeAbiParameters(progressParameters, [head]));
359
- const receipts: LifecycleReceiptStatus[] = [];
360
- for (const reference of options.receipts ?? []) receipts.push(await readReceipt({ ...reference, confirmations: Math.max(reference.confirmations ?? 1, confirmationDepth) }, options, block));
361
- const markets: LifecycleMarketProgress[] = [];
420
+ const receipts = await Promise.all((options.receipts ?? []).map((reference) => readReceipt({ ...reference, confirmations: Math.max(reference.confirmations ?? 1, confirmationDepth) }, { ...options, client }, block)));
421
+ let markets: LifecycleMarketProgress[] = [];
362
422
  if (canonical.preparedMarkets > 0) {
363
423
  const directory = await readLifecycleContract<Address>(client, planned.plan.orchestrator, launchLifecycleAbi, "directory", [], confirmedBlock);
364
- for (let index = 0; index < canonical.preparedMarkets; index += 1) {
424
+ markets = await Promise.all(Array.from({ length: canonical.preparedMarkets }, async (_, index): Promise<LifecycleMarketProgress> => {
365
425
  const [adapter, prepared] = await readLifecycleContract<readonly [Address, PreparedMarketV1]>(client, directory, lifecycleDirectoryAbi, "market", [planned.launchId, index], confirmedBlock);
366
426
  validateLifecycleMarketIdentity(prepared.identity, planned.plan, token, index);
367
427
  try {
368
428
  const live = await readLifecycleContract<MarketLiveStateV1>(client, adapter, lifecycleAdapterAbi, "readMarket", [planned.launchId, index], confirmedBlock);
369
- markets.push({ index, prepared, live });
429
+ return { index, prepared, live };
370
430
  } catch (failure) {
371
- markets.push({ index, prepared, error: failure instanceof Error ? failure.message : String(failure) });
431
+ return { index, prepared, error: failure instanceof Error ? failure.message : String(failure) };
372
432
  }
373
- }
433
+ }));
374
434
  }
375
- if (pinnedBlock === undefined) await assertLifecycleBlock(client, block);
376
- if (confirmedNumber !== block.number) await assertLifecycleBlock(client, confirmedBlock);
435
+ if (checkBlock) await assertLifecycleBlock(client, block, chainId);
436
+ if (confirmedNumber !== block.number) await assertLifecycleBlock(client, confirmedBlock, chainId);
437
+ if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan changed during asynchronous progress reads");
377
438
  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 };
378
439
  }