@black-market/sdk 0.6.0 → 0.6.2
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.
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +198 -133
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +204 -143
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.d.ts +8 -1
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +149 -10
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +23 -14
- package/dist/lifecycle/simulation.js.map +1 -1
- package/dist/lifecycle/types.d.ts +15 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/docs/launch-lifecycle.md +107 -0
- package/package.json +1 -1
- package/src/lifecycle/planner.ts +164 -109
- package/src/lifecycle/progress.ts +187 -126
- package/src/lifecycle/rpc.ts +146 -11
- package/src/lifecycle/simulation.ts +23 -13
- package/src/lifecycle/types.ts +19 -0
|
@@ -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, lifecycleStage, 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
|
|
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
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 (
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
101
|
-
|
|
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 [
|
|
108
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
let
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
147
|
+
return withLifecycleReadClient(options.client, (client) => lifecycleStage(client, "profile.certification", async () => {
|
|
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
|
-
|
|
177
|
-
|
|
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
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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]
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
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
|
|
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 [
|
|
214
|
-
[
|
|
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 (
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
306
|
-
|
|
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
|
|
336
|
-
|
|
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
|
|
346
|
-
|
|
347
|
-
|
|
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(
|
|
355
|
-
const headAccountNonce =
|
|
356
|
-
const pendingAccountNonce = rpcQuantity(
|
|
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
|
|
360
|
-
|
|
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
|
-
|
|
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
|
-
|
|
429
|
+
return { index, prepared, live };
|
|
370
430
|
} catch (failure) {
|
|
371
|
-
|
|
431
|
+
return { index, prepared, error: failure instanceof Error ? failure.message : String(failure) };
|
|
372
432
|
}
|
|
373
|
-
}
|
|
433
|
+
}));
|
|
374
434
|
}
|
|
375
|
-
if (
|
|
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
|
}
|