@black-market/sdk 0.6.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -4
- package/dist/addresses.d.ts +1 -1
- package/dist/addresses.js +4 -4
- package/dist/launch.d.ts +61 -0
- package/dist/launch.d.ts.map +1 -1
- package/dist/launch.js +171 -6
- package/dist/launch.js.map +1 -1
- package/dist/lifecycle/abi.d.ts +1999 -253
- package/dist/lifecycle/abi.d.ts.map +1 -1
- package/dist/lifecycle/abi.js +43 -14
- package/dist/lifecycle/abi.js.map +1 -1
- package/dist/lifecycle/allocations.d.ts +11 -0
- package/dist/lifecycle/allocations.d.ts.map +1 -0
- package/dist/lifecycle/allocations.js +36 -0
- package/dist/lifecycle/allocations.js.map +1 -0
- package/dist/lifecycle/calibration.d.ts +19 -0
- package/dist/lifecycle/calibration.d.ts.map +1 -0
- package/dist/lifecycle/calibration.js +65 -0
- package/dist/lifecycle/calibration.js.map +1 -0
- package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.d.ts +11 -0
- package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.d.ts.map +1 -0
- package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.js +10 -0
- package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.js.map +1 -0
- package/dist/lifecycle/index.d.ts +4 -2
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -2
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/markets.d.ts +170 -6
- package/dist/lifecycle/markets.d.ts.map +1 -1
- package/dist/lifecycle/markets.js +164 -37
- package/dist/lifecycle/markets.js.map +1 -1
- package/dist/lifecycle/planner.d.ts +7 -14
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +354 -400
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +62 -0
- package/dist/lifecycle/presets.d.ts.map +1 -0
- package/dist/lifecycle/presets.js +195 -0
- package/dist/lifecycle/presets.js.map +1 -0
- package/dist/lifecycle/progress.d.ts +5 -1
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +212 -53
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.d.ts +13 -2
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +92 -8
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/simulation.d.ts +14 -4
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +383 -116
- package/dist/lifecycle/simulation.js.map +1 -1
- package/dist/lifecycle/transactions.d.ts +6 -0
- package/dist/lifecycle/transactions.d.ts.map +1 -0
- package/dist/lifecycle/transactions.js +40 -0
- package/dist/lifecycle/transactions.js.map +1 -0
- package/dist/lifecycle/types.d.ts +39 -2
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/docs/launch-lifecycle.md +228 -54
- package/examples/README.md +108 -18
- package/examples/generate-lifecycle-commitment-fixture.mjs +3 -1
- package/examples/launch-example-support.mjs +1 -1
- package/examples/launch-example.mjs +73 -26
- package/examples/launch-lifecycle-smoke.mjs +13 -11
- package/examples/launch.mjs +12 -11
- package/examples/quickstart.mjs +1 -1
- package/package.json +1 -1
- package/src/addresses.ts +4 -4
- package/src/launch.ts +213 -6
- package/src/lifecycle/abi.ts +43 -14
- package/src/lifecycle/allocations.ts +35 -0
- package/src/lifecycle/calibration.ts +59 -0
- package/src/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.ts +11 -0
- package/src/lifecycle/index.ts +4 -2
- package/src/lifecycle/markets.ts +169 -35
- package/src/lifecycle/planner.ts +326 -330
- package/src/lifecycle/presets.ts +243 -0
- package/src/lifecycle/progress.ts +193 -50
- package/src/lifecycle/rpc.ts +97 -9
- package/src/lifecycle/simulation.ts +370 -109
- package/src/lifecycle/transactions.ts +34 -0
- package/src/lifecycle/types.ts +35 -3
|
@@ -1,20 +1,79 @@
|
|
|
1
1
|
import { concatHex, decodeFunctionData, encodeAbiParameters, encodeFunctionData, keccak256, stringToHex, toHex, zeroAddress, zeroHash, type Address, type Hash, type Hex } from "viem";
|
|
2
|
-
import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleOracleFactoryAbi, lifecycleRegistryAbi, poolFeeCollectorFactoryV1Abi, lifecycleV4HookAbi, lifecycleV4LockerAbi, poolHookDeployerV1Abi, sharedHookDeployerV1Abi, fixedFeePoolHookV1Abi, poolMarketAdapterV1Abi, sharedMarketAdapterV1Abi } from "./abi.js";
|
|
3
|
-
import { decodePoolBoundV4LifecycleMarketConfig, encodePoolBoundHookParameters, hasLifecycleV4HookPermissions,
|
|
4
|
-
import { hashLaunchIdentity, hashLaunchPlan, hashLaunchBounds, hashLaunchDependencies, hashLifecycleProfile, launchProgressV1Components, LifecycleMode, LifecyclePhase, LifecycleVenue, LIFECYCLE_REQUIRED_CAPABILITIES, LIFECYCLE_ERC404_CAPABILITY, LIFECYCLE_MULTI_POSITION_CAPABILITY, type AdapterRegistrationV1, type LaunchPlanV1, type LaunchProgressV1, type MarketIdentityV1, type MarketLiveStateV1, type PreparedMarketV1, type ProfileRegistrationV1, type ProfileTopologyV1, type LaunchEnvelopeV2, type LifecycleDeveloperTerms } from "./schema.js";
|
|
5
|
-
import { assertLifecycleBlock, lifecyclePinnedRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleContract, readLifecycleProfileTopology, rpcHex, rpcObject, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
2
|
+
import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleOracleFactoryAbi, lifecycleRegistryAbi, poolFeeCollectorFactoryV1Abi, poolFeeCollectorFactoryConfigV6Abi, lifecycleV4HookAbi, lifecycleV4LockerAbi, poolHookDeployerV1Abi, poolHookDeployerConfigV6Abi, sharedHookDeployerV1Abi, fixedFeePoolHookV1Abi, fixedFeePoolHookConfigV6Abi, poolMarketAdapterV1Abi, sharedMarketAdapterV1Abi } from "./abi.js";
|
|
3
|
+
import { buildPoolBoundHookParameters, decodePoolBoundV4LifecycleMarketConfig, encodePoolBoundHookParameters, hasLifecycleV4HookPermissions, isPoolBoundV4ConfigVersion, poolBoundHookInitCodeHash, predictPoolBoundHookAddress, validateV4LifecycleMarket, V4_LIFECYCLE_CONFIG_SCHEMA, V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V5, V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6, type PoolBoundHookParameters, type PoolBoundLifecycleConfigVersion } from "./markets.js";
|
|
4
|
+
import { hashLaunchIdentity, hashLaunchPlan, hashLaunchBounds, hashLaunchDependencies, hashLifecycleProfile, lifecycleTokenComponents, launchProgressV1Components, LifecycleMode, LifecyclePhase, LifecycleVenue, LIFECYCLE_REQUIRED_CAPABILITIES, LIFECYCLE_ERC404_CAPABILITY, LIFECYCLE_MULTI_POSITION_CAPABILITY, type AdapterRegistrationV1, type LaunchPlanV1, type LaunchProgressV1, type MarketIdentityV1, type MarketLiveStateV1, type PreparedMarketV1, type ProfileRegistrationV1, type ProfileTopologyV1, type LaunchEnvelopeV2, type LifecycleDeveloperTerms } from "./schema.js";
|
|
5
|
+
import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleChainId, readLifecycleContract, readLifecycleProfileTopology, rpcHex, rpcObject, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
6
6
|
import { LifecyclePlanningError, type CanonicalLaunchProgress, type LifecycleBlock, type LifecycleMarketProgress, type LifecycleProfile, type LifecycleReceiptReference, type LifecycleReceiptStatus, type LifecycleRpcClient, type PoolBoundHookDeployment, type ReadLaunchProgressOptions } from "./types.js";
|
|
7
|
+
import { deriveKnownPoolBoundHookDeployment } from "./calibration.js";
|
|
8
|
+
import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
|
|
7
9
|
|
|
8
10
|
export const ABYSS_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint8,uint24,bytes32,uint160,(int24,int24,uint128,uint256)[])"));
|
|
9
11
|
|
|
12
|
+
const lifecycleTokenPredictionAbi = [{
|
|
13
|
+
type: "function", name: "predictToken", stateMutability: "view",
|
|
14
|
+
inputs: [{ name: "launchId", type: "bytes32" }, { name: "config", type: "tuple", components: lifecycleTokenComponents }],
|
|
15
|
+
outputs: [{ type: "address" }],
|
|
16
|
+
}] as const;
|
|
17
|
+
|
|
18
|
+
/** The known immutable factory accepts only launch identity and token config,
|
|
19
|
+
* avoiding full-plan prediction reads after salts or buy minima change. */
|
|
20
|
+
export async function readLifecycleTokenAtBlock(client: LifecycleRpcClient, plan: LaunchPlanV1, block: LifecycleBlock): Promise<Address> {
|
|
21
|
+
const deployment = getKnownLifecycleDeployment({ chainId: plan.chainId, orchestrator: plan.orchestrator });
|
|
22
|
+
return deployment === undefined
|
|
23
|
+
? readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "predictToken", [plan], block)
|
|
24
|
+
: readLifecycleContract<Address>(client, deployment.tokenFactory, lifecycleTokenPredictionAbi, "predictToken", [hashLaunchIdentity(plan), plan.token], block);
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
export async function predictLifecycleToken(options: { client: LifecycleRpcClient; plan: LaunchPlanV1 }): Promise<Address> {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
return withLifecycleReadClient(options.client, async (client) => {
|
|
29
|
+
const block = await readLifecycleBlock(client);
|
|
30
|
+
const token = await readLifecycleTokenAtBlock(client, options.plan, block);
|
|
31
|
+
await assertLifecycleBlock(client, block, options.plan.chainId);
|
|
32
|
+
return token;
|
|
33
|
+
});
|
|
15
34
|
}
|
|
16
35
|
|
|
17
36
|
type BoundProfileGraph = { manager: Address; oracleFactory: Address; locker: Address; collectorFactory: Address; deployer: Address; creationCode: Hex };
|
|
37
|
+
type LifecycleProfileCertificate = { readonly topology: Readonly<ProfileTopologyV1>; readonly boundGraph?: Readonly<BoundProfileGraph> };
|
|
38
|
+
type LifecycleEligibilityObservation = { eligible: Address } | { failure: unknown };
|
|
39
|
+
type BoundHookDeploymentDetails = { readonly deployment: Readonly<PoolBoundHookDeployment>; readonly parameters: Readonly<PoolBoundHookParameters>; readonly configVersion: PoolBoundLifecycleConfigVersion };
|
|
40
|
+
const certifiedProfilesByInvocation = new WeakMap<LifecycleRpcClient, Map<string, Promise<LifecycleProfileCertificate>>>();
|
|
41
|
+
const boundDeploymentsByInvocation = new WeakMap<LifecycleRpcClient, Map<string, Promise<BoundHookDeploymentDetails>>>();
|
|
42
|
+
|
|
43
|
+
function assertDerivedReadClientOpen(client: LifecycleRpcClient): boolean {
|
|
44
|
+
try { return assertLifecycleReadClientOpen(client); }
|
|
45
|
+
catch (failure) {
|
|
46
|
+
certifiedProfilesByInvocation.delete(client);
|
|
47
|
+
boundDeploymentsByInvocation.delete(client);
|
|
48
|
+
throw failure;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function lifecycleProfileCertificateKey(orchestrator: Address, registry: Address, id: Hex, registration: ProfileRegistrationV1, adapter: AdapterRegistrationV1, topology: ProfileTopologyV1, envelope: LaunchEnvelopeV2 | undefined, block: LifecycleBlock, chainId: bigint): string {
|
|
53
|
+
const g = envelope?.graph;
|
|
54
|
+
const b = envelope?.bounds;
|
|
55
|
+
// Fixed ABI field order preserves every scalar, including flags delegated to
|
|
56
|
+
// registry eligibility. Keys never rehash runtime, chunk or creation bytes.
|
|
57
|
+
return JSON.stringify([
|
|
58
|
+
block.number.toString(), block.hash.toLowerCase(), chainId.toString(), orchestrator.toLowerCase(), registry.toLowerCase(), id.toLowerCase(),
|
|
59
|
+
[registration.adapterId.toLowerCase(), registration.configSchema.toLowerCase(), registration.dependencyDigest.toLowerCase(),
|
|
60
|
+
registration.venue.toLowerCase(), registration.factory.toLowerCase(), registration.hook.toLowerCase(), registration.capabilities.toString(), registration.enabled],
|
|
61
|
+
[adapter.implementation.toLowerCase(), adapter.codeHash.toLowerCase(), adapter.capabilities.toString(), adapter.configVersion, adapter.enabled],
|
|
62
|
+
[topology.hookTopology, topology.configVersion, topology.hookDeployer.toLowerCase(), topology.hookCreationCodeHash.toLowerCase()],
|
|
63
|
+
envelope === undefined ? null : [
|
|
64
|
+
envelope.artifactDigest.toLowerCase(), envelope.reviewManifestDigest.toLowerCase(), envelope.configBoundsDigest.toLowerCase(), envelope.termsDigest.toLowerCase(),
|
|
65
|
+
envelope.topology, envelope.configVersion, envelope.economicVersion, envelope.capabilities.toString(), envelope.flags.toString(),
|
|
66
|
+
envelope.callbackFlags, envelope.callbackMask, envelope.protocolTreasury.toLowerCase(), envelope.protocolFeeDenominator,
|
|
67
|
+
envelope.beneficiary.toLowerCase(), envelope.maximumDeveloperFeeBps,
|
|
68
|
+
[b!.minimumTickSpacing, b!.maximumTickSpacing, b!.maximumPositions, b!.maximumOracleCardinality, b!.feeModeFlags],
|
|
69
|
+
[g!.manager.toLowerCase(), g!.hookRoot.toLowerCase(), g!.oracleFactory.toLowerCase(), g!.locker.toLowerCase(), g!.collectorFactory.toLowerCase(),
|
|
70
|
+
g!.collectorDeployer.toLowerCase(), g!.hookDeployer.toLowerCase(), g!.coreCodeHash.toLowerCase(), g!.managerCodeHash.toLowerCase(),
|
|
71
|
+
g!.hookRuntimeCodeHash.toLowerCase(), g!.oracleFactoryCodeHash.toLowerCase(), g!.lockerCodeHash.toLowerCase(), g!.collectorFactoryCodeHash.toLowerCase(),
|
|
72
|
+
g!.collectorDeployerCodeHash.toLowerCase(), g!.hookDeployerCodeHash.toLowerCase(), g!.hookCreationCodeHash.toLowerCase(),
|
|
73
|
+
g!.codeChunk0.toLowerCase(), g!.codeChunk0Hash.toLowerCase(), g!.codeChunk1.toLowerCase(), g!.codeChunk1Hash.toLowerCase(), g!.sharedHookSalt.toLowerCase()],
|
|
74
|
+
],
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
18
77
|
|
|
19
78
|
async function readDependencyCode(client: LifecycleRpcClient, address: Address, block: LifecycleBlock): Promise<Hex> {
|
|
20
79
|
const code = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [address, toHex(block.number)], block), "lifecycle dependency code");
|
|
@@ -22,12 +81,33 @@ async function readDependencyCode(client: LifecycleRpcClient, address: Address,
|
|
|
22
81
|
return code;
|
|
23
82
|
}
|
|
24
83
|
|
|
25
|
-
async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator: Address, registry: Address, id: Hex, registration: ProfileRegistrationV1, adapter: AdapterRegistrationV1, topology: ProfileTopologyV1, envelope: LaunchEnvelopeV2 | undefined, block: LifecycleBlock, chainId: bigint): Promise<
|
|
84
|
+
async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator: Address, registry: Address, id: Hex, registration: ProfileRegistrationV1, adapter: AdapterRegistrationV1, topology: ProfileTopologyV1, envelope: LaunchEnvelopeV2 | undefined, block: LifecycleBlock, chainId: bigint, eligibility?: LifecycleEligibilityObservation): Promise<LifecycleProfileCertificate> {
|
|
85
|
+
if (!assertDerivedReadClientOpen(client)) return certifyLifecycleProfileFresh(client, orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
|
|
86
|
+
const key = lifecycleProfileCertificateKey(orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
|
|
87
|
+
let certificates = certifiedProfilesByInvocation.get(client);
|
|
88
|
+
if (certificates === undefined) {
|
|
89
|
+
certificates = new Map();
|
|
90
|
+
certifiedProfilesByInvocation.set(client, certificates);
|
|
91
|
+
}
|
|
92
|
+
let pending = certificates.get(key);
|
|
93
|
+
if (pending === undefined) {
|
|
94
|
+
pending = certifyLifecycleProfileFresh(client, orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
|
|
95
|
+
certificates.set(key, pending);
|
|
96
|
+
const owned = pending;
|
|
97
|
+
void pending.catch(() => { if (certificates.get(key) === owned) certificates.delete(key); });
|
|
98
|
+
}
|
|
99
|
+
try { return await pending; }
|
|
100
|
+
finally { assertDerivedReadClientOpen(client); }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function certifyLifecycleProfileFresh(client: LifecycleRpcClient, orchestrator: Address, registry: Address, id: Hex, registration: ProfileRegistrationV1, adapter: AdapterRegistrationV1, topology: ProfileTopologyV1, envelope: LaunchEnvelopeV2 | undefined, block: LifecycleBlock, chainId: bigint, eligibility?: LifecycleEligibilityObservation): Promise<LifecycleProfileCertificate> {
|
|
26
104
|
const shared = registration.configSchema.toLowerCase() === V4_LIFECYCLE_CONFIG_SCHEMA.toLowerCase();
|
|
27
|
-
const
|
|
105
|
+
const boundVersion = registration.configSchema.toLowerCase() === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V5.toLowerCase() ? 5
|
|
106
|
+
: registration.configSchema.toLowerCase() === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6.toLowerCase() ? 6 : undefined;
|
|
107
|
+
const bound = boundVersion !== undefined;
|
|
28
108
|
const abyss = registration.configSchema.toLowerCase() === ABYSS_LIFECYCLE_CONFIG_SCHEMA.toLowerCase();
|
|
29
109
|
if (!shared && !bound && !abyss) throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
|
|
30
|
-
const version = shared ? 4 :
|
|
110
|
+
const version = shared ? 4 : boundVersion ?? 1;
|
|
31
111
|
if (adapter.configVersion !== version || topology.configVersion !== version || topology.hookTopology !== (shared ? 1 : bound ? 2 : 0)) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Registry topology/version differs from the supported config schema");
|
|
32
112
|
if (abyss) {
|
|
33
113
|
if (topology.hookDeployer !== zeroAddress || topology.hookCreationCodeHash !== zeroHash) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Abyss does not admit a V4 hook deployment graph");
|
|
@@ -37,9 +117,10 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
|
|
|
37
117
|
const domain = keccak256(stringToHex("BLACK_MARKET_ABYSS_CANONICAL_PROFILE_V1"));
|
|
38
118
|
const known = [0, 1, 2, 3].some((variant) => keccak256(encodeAbiParameters([{ type: "bytes32" }, { type: "uint256" }, { type: "address" }, { type: "uint8" }], [domain, chainId, factory, variant])).toLowerCase() === id.toLowerCase());
|
|
39
119
|
if (!known || registration.venue.toLowerCase() !== factory.toLowerCase() || registration.hook !== zeroAddress) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Abyss profile is not the exact canonical factory/variant/schema binding");
|
|
40
|
-
|
|
120
|
+
if (eligibility !== undefined && "failure" in eligibility) throw eligibility.failure;
|
|
121
|
+
const eligible = eligibility?.eligible ?? await readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, version, LIFECYCLE_REQUIRED_CAPABILITIES], block);
|
|
41
122
|
if (eligible.toLowerCase() !== adapter.implementation.toLowerCase()) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
|
|
42
|
-
return { topology };
|
|
123
|
+
return Object.freeze({ topology: Object.freeze({ ...topology }) });
|
|
43
124
|
}
|
|
44
125
|
if (envelope === undefined || envelope.topology !== topology.hookTopology || envelope.configVersion !== version || envelope.economicVersion !== 3 ||
|
|
45
126
|
envelope.flags !== 0n || envelope.callbackFlags !== 0x1afc || envelope.callbackMask !== 0x3fff ||
|
|
@@ -59,7 +140,7 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
|
|
|
59
140
|
const g = envelope.graph;
|
|
60
141
|
if (registration.factory !== zeroAddress || registration.venue.toLowerCase() !== g.manager.toLowerCase() || registration.hook.toLowerCase() !== g.hookRoot.toLowerCase() ||
|
|
61
142
|
topology.hookDeployer.toLowerCase() !== g.hookDeployer.toLowerCase() || topology.hookCreationCodeHash.toLowerCase() !== g.hookCreationCodeHash.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed venue/topology differs from the frozen envelope graph");
|
|
62
|
-
const deployerAbi = shared ? sharedHookDeployerV1Abi : poolHookDeployerV1Abi;
|
|
143
|
+
const deployerAbi = shared ? sharedHookDeployerV1Abi : boundVersion === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
|
|
63
144
|
const codeBindings = [[orchestrator, g.coreCodeHash], [g.manager, g.managerCodeHash], [g.oracleFactory, g.oracleFactoryCodeHash],
|
|
64
145
|
[g.locker, g.lockerCodeHash], [g.collectorFactory, g.collectorFactoryCodeHash], [g.collectorDeployer, g.collectorDeployerCodeHash],
|
|
65
146
|
[g.hookDeployer, g.hookDeployerCodeHash]] as const;
|
|
@@ -109,7 +190,7 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
|
|
|
109
190
|
(chunk1 !== zeroAddress && (!chunk1Code.startsWith("0x00") || chunk1Code.length <= 4 || (chunk1Code.length - 2) / 2 > 24576 || (chunk0Code.length - 2) / 2 !== 24576)) ||
|
|
110
191
|
keccak256(chunk0Code).toLowerCase() !== g.codeChunk0Hash.toLowerCase() || (chunk1 === zeroAddress ? zeroHash : keccak256(chunk1Code)).toLowerCase() !== g.codeChunk1Hash.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed deployer chunks differ from exact immutable STOP-prefixed bytecode evidence");
|
|
111
192
|
const creationCode = concatHex([`0x${chunk0Code.slice(4)}`, chunk1 === zeroAddress ? "0x" : `0x${chunk1Code.slice(4)}`]);
|
|
112
|
-
if ((creationCode.length - 2) / 2 + (shared ? 96 : 576) > 49152 || creationCodeHash === zeroHash ||
|
|
193
|
+
if ((creationCode.length - 2) / 2 + (shared ? 96 : boundVersion === 5 ? 576 : 640) > 49152 || creationCodeHash === zeroHash ||
|
|
113
194
|
creationCodeHash.toLowerCase() !== g.hookCreationCodeHash.toLowerCase() || keccak256(creationCode).toLowerCase() !== creationCodeHash.toLowerCase() ||
|
|
114
195
|
hashLaunchDependencies({ chainId, core: orchestrator, registry, registrar: adapter.implementation, graph: g }).toLowerCase() !== registration.dependencyDigest.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed creation artifact, constructor bound or graph digest differs from admission");
|
|
115
196
|
if (sharedRoot !== undefined) {
|
|
@@ -124,7 +205,7 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
|
|
|
124
205
|
if (requiredFlags !== 0x1afcn || allFlags !== 0x3fffn) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared hook callbacks differ from admission");
|
|
125
206
|
} else if (g.hookRoot !== zeroAddress || g.hookRuntimeCodeHash !== zeroHash || g.sharedHookSalt !== zeroHash) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Bound topology cannot reuse a shared root");
|
|
126
207
|
if ([zeroAddress, orchestrator, registry, adapter.implementation, g.manager, g.hookRoot, g.locker, g.collectorFactory, g.collectorDeployer, g.hookDeployer].some((address) => address.toLowerCase() === envelope.beneficiary.toLowerCase())) throw new LifecyclePlanningError("PROFILE_TERMS", "Stable author identity is an excluded reviewed graph destination");
|
|
127
|
-
return { topology, boundGraph: bound ? { manager: g.manager, oracleFactory: g.oracleFactory, locker: g.locker, collectorFactory: g.collectorFactory, deployer: g.hookDeployer, creationCode } : undefined };
|
|
208
|
+
return Object.freeze({ topology: Object.freeze({ ...topology }), boundGraph: bound ? Object.freeze({ manager: g.manager, oracleFactory: g.oracleFactory, locker: g.locker, collectorFactory: g.collectorFactory, deployer: g.hookDeployer, creationCode }) : undefined });
|
|
128
209
|
}
|
|
129
210
|
|
|
130
211
|
/** Validate the market-selected registered oracle against the certified factory at the pinned block. */
|
|
@@ -157,7 +238,7 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
|
|
|
157
238
|
const readProfile = async (id: Hex): Promise<LifecycleProfile> => {
|
|
158
239
|
const registration = await readLifecycleContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [id], block);
|
|
159
240
|
const schema = registration.configSchema.toLowerCase();
|
|
160
|
-
const venueKind = schema === V4_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() || schema ===
|
|
241
|
+
const venueKind = schema === V4_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() || schema === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V5.toLowerCase() || schema === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6.toLowerCase() ? "uniswap-v4" : schema === ABYSS_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() ? "abyss" : "unknown";
|
|
161
242
|
const [adapter, certification] = await Promise.all([
|
|
162
243
|
readLifecycleContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block),
|
|
163
244
|
venueKind === "unknown" ? undefined : Promise.all([
|
|
@@ -167,6 +248,12 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
|
|
|
167
248
|
readLifecycleContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
|
|
168
249
|
readProtocolMaximumDeveloperFeeBps(),
|
|
169
250
|
]) : undefined,
|
|
251
|
+
// The exact Abyss arguments follow from registration, independently
|
|
252
|
+
// of adapter/topology responses. Handle refusal immediately, but
|
|
253
|
+
// keep its original place after canonical/schema/topology checks.
|
|
254
|
+
venueKind === "abyss" ? chainIdPromise.then(() =>
|
|
255
|
+
readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, 1, LIFECYCLE_REQUIRED_CAPABILITIES], block)
|
|
256
|
+
).then((eligible) => ({ eligible }), (failure: unknown) => ({ failure })) : undefined,
|
|
170
257
|
]).then((values) => ({ values }), (failure: unknown) => ({ failure })),
|
|
171
258
|
]);
|
|
172
259
|
const chainId = await chainIdPromise;
|
|
@@ -177,7 +264,7 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
|
|
|
177
264
|
try {
|
|
178
265
|
if (certification === undefined) throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
|
|
179
266
|
if ("failure" in certification) throw certification.failure;
|
|
180
|
-
const [recordedTopology, reviewed] = certification.values;
|
|
267
|
+
const [recordedTopology, reviewed, eligibility] = certification.values;
|
|
181
268
|
topology = recordedTopology;
|
|
182
269
|
if (reviewed !== undefined) {
|
|
183
270
|
envelope = reviewed[0];
|
|
@@ -188,10 +275,10 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
|
|
|
188
275
|
termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
|
|
189
276
|
maximumDeveloperFeeBps > reviewed[2]) throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
|
|
190
277
|
}
|
|
191
|
-
await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
|
|
278
|
+
await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
|
|
192
279
|
if (developerTerms !== undefined && !developerTerms.enabled) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
|
|
193
280
|
admitted = true;
|
|
194
|
-
} catch (failure) { reason =
|
|
281
|
+
} catch (failure) { reason = lifecycleFailureReason(failure); }
|
|
195
282
|
return { id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind };
|
|
196
283
|
};
|
|
197
284
|
const [authority, profiles] = await Promise.all([
|
|
@@ -209,12 +296,52 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
|
|
|
209
296
|
}));
|
|
210
297
|
}
|
|
211
298
|
|
|
212
|
-
async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, block: LifecycleBlock): Promise<
|
|
299
|
+
async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, block: LifecycleBlock, commitment: Hex): Promise<BoundHookDeploymentDetails> {
|
|
213
300
|
const { client, plan, marketIndex } = options;
|
|
214
301
|
const market = plan.markets[marketIndex];
|
|
215
|
-
if (market === undefined || market.configVersion
|
|
216
|
-
|
|
302
|
+
if (market === undefined || !isPoolBoundV4ConfigVersion(market.configVersion)) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market index must select explicit pool-bound config version 5 or 6");
|
|
303
|
+
let chainId: bigint;
|
|
304
|
+
try { chainId = rpcQuantity(await lifecycleRpc(client, "eth_chainId"), "chain ID"); }
|
|
305
|
+
finally { assertDerivedReadClientOpen(client); }
|
|
306
|
+
if (hashLaunchPlan(plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while observing the hook deployment chain");
|
|
217
307
|
if (chainId !== plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed plan");
|
|
308
|
+
if (getKnownLifecycleProfile({ chainId, orchestrator: plan.orchestrator, profileId: market.profileId }) !== undefined) {
|
|
309
|
+
const token = await readLifecycleTokenAtBlock(client, plan, block);
|
|
310
|
+
const details = deriveKnownPoolBoundHookDeployment({ plan, marketIndex, token });
|
|
311
|
+
if (details === undefined) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Known profile has no bound-hook construction metadata");
|
|
312
|
+
if (hashLaunchPlan(plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while deriving hook deployment metadata");
|
|
313
|
+
assertDerivedReadClientOpen(client);
|
|
314
|
+
return Object.freeze({ deployment: Object.freeze(details.deployment), parameters: Object.freeze(details.parameters), configVersion: details.configVersion });
|
|
315
|
+
}
|
|
316
|
+
if (!assertDerivedReadClientOpen(client)) {
|
|
317
|
+
const details = await readPoolBoundHookDeploymentDetailsFresh(options, block, chainId);
|
|
318
|
+
if (hashLaunchPlan(plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
|
|
319
|
+
return details;
|
|
320
|
+
}
|
|
321
|
+
const key = JSON.stringify([block.number.toString(), block.hash.toLowerCase(), chainId.toString(), commitment.toLowerCase(), marketIndex]);
|
|
322
|
+
let deployments = boundDeploymentsByInvocation.get(client);
|
|
323
|
+
if (deployments === undefined) {
|
|
324
|
+
deployments = new Map();
|
|
325
|
+
boundDeploymentsByInvocation.set(client, deployments);
|
|
326
|
+
}
|
|
327
|
+
let pending = deployments.get(key);
|
|
328
|
+
if (pending === undefined) {
|
|
329
|
+
pending = readPoolBoundHookDeploymentDetailsFresh(options, block, chainId).then((details) => {
|
|
330
|
+
if (hashLaunchPlan(plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
|
|
331
|
+
assertDerivedReadClientOpen(client);
|
|
332
|
+
return details;
|
|
333
|
+
});
|
|
334
|
+
deployments.set(key, pending);
|
|
335
|
+
const owned = pending;
|
|
336
|
+
void pending.catch(() => { if (deployments.get(key) === owned) deployments.delete(key); });
|
|
337
|
+
}
|
|
338
|
+
try { return await pending; }
|
|
339
|
+
finally { assertDerivedReadClientOpen(client); }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function readPoolBoundHookDeploymentDetailsFresh(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, block: LifecycleBlock, chainId: bigint): Promise<BoundHookDeploymentDetails> {
|
|
343
|
+
const { client, plan, marketIndex } = options;
|
|
344
|
+
const market = plan.markets[marketIndex]!;
|
|
218
345
|
const registry = await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "registry", [], block);
|
|
219
346
|
const [authority, registration] = await Promise.all([
|
|
220
347
|
readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
|
|
@@ -232,34 +359,33 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
|
|
|
232
359
|
]);
|
|
233
360
|
const { boundGraph } = await certifyLifecycleProfile(client, plan.orchestrator, registry, market.profileId, registration, adapter, recorded, envelope, block, chainId);
|
|
234
361
|
if (boundGraph === undefined) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Market does not have a certified pool-bound deployer graph");
|
|
235
|
-
|
|
362
|
+
if (!isPoolBoundV4ConfigVersion(market.configVersion)) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market must select an explicit pool-bound version");
|
|
363
|
+
const config = decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion);
|
|
236
364
|
validateV4LifecycleMarket({ market, config, token, profile: {
|
|
237
365
|
id: market.profileId, registration, adapter, topology: recorded, envelope,
|
|
238
366
|
developerTerms: { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled },
|
|
239
367
|
protocolMaximumDeveloperFeeBps, venueKind: "uniswap-v4", admitted: enabled,
|
|
240
368
|
} });
|
|
241
369
|
await validateV4LifecycleOracle({ client, envelope, oracleConfigId: config.oracleConfigId, block });
|
|
242
|
-
const expected
|
|
243
|
-
|
|
244
|
-
liquidityLocker: boundGraph.locker, token,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
expectedPositionCount: config.positions.length,
|
|
249
|
-
};
|
|
370
|
+
const expected = buildPoolBoundHookParameters({
|
|
371
|
+
chainId, core: plan.orchestrator, poolManager: boundGraph.manager, registrar: adapter.implementation,
|
|
372
|
+
oracleFactory: boundGraph.oracleFactory, liquidityLocker: boundGraph.locker, token, market, config,
|
|
373
|
+
});
|
|
374
|
+
const deployerAbi = config.version === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
|
|
375
|
+
const collectorAbi = config.version === 5 ? poolFeeCollectorFactoryV1Abi : poolFeeCollectorFactoryConfigV6Abi;
|
|
250
376
|
const [[parameters, parameterSalt], [deployer, initCodeHash, salt, predictedHook], remoteInitCodeHash, remotePrediction] = await Promise.all([
|
|
251
|
-
readLifecycleContract<readonly [
|
|
377
|
+
readLifecycleContract<readonly [PoolBoundHookParameters, Hex]>(client, boundGraph.collectorFactory, collectorAbi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
|
|
252
378
|
readLifecycleContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block),
|
|
253
|
-
readLifecycleContract<Hex>(client, boundGraph.deployer,
|
|
254
|
-
readLifecycleContract<Address>(client, boundGraph.deployer,
|
|
379
|
+
readLifecycleContract<Hex>(client, boundGraph.deployer, deployerAbi, "initCodeHash", [expected], block),
|
|
380
|
+
readLifecycleContract<Address>(client, boundGraph.deployer, deployerAbi, "predict", [expected, config.hookSalt], block),
|
|
255
381
|
]);
|
|
256
|
-
const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected);
|
|
382
|
+
const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected, config.version);
|
|
257
383
|
const localPrediction = predictPoolBoundHookAddress({ deployer: boundGraph.deployer, initCodeHash: localInitCodeHash, salt: config.hookSalt });
|
|
258
|
-
if (encodePoolBoundHookParameters(parameters).toLowerCase() !== encodePoolBoundHookParameters(expected).toLowerCase() || parameterSalt.toLowerCase() !== config.hookSalt.toLowerCase() || salt.toLowerCase() !== config.hookSalt.toLowerCase() || deployer.toLowerCase() !== boundGraph.deployer.toLowerCase() || initCodeHash.toLowerCase() !== localInitCodeHash.toLowerCase() || remoteInitCodeHash.toLowerCase() !== localInitCodeHash.toLowerCase() || predictedHook.toLowerCase() !== localPrediction.toLowerCase() || remotePrediction.toLowerCase() !== localPrediction.toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Bound hook metadata differs from the exact certified constructor economics, factory, initcode, salt or CREATE2 prediction");
|
|
384
|
+
if (encodePoolBoundHookParameters(parameters, config.version).toLowerCase() !== encodePoolBoundHookParameters(expected, config.version).toLowerCase() || parameterSalt.toLowerCase() !== config.hookSalt.toLowerCase() || salt.toLowerCase() !== config.hookSalt.toLowerCase() || deployer.toLowerCase() !== boundGraph.deployer.toLowerCase() || initCodeHash.toLowerCase() !== localInitCodeHash.toLowerCase() || remoteInitCodeHash.toLowerCase() !== localInitCodeHash.toLowerCase() || predictedHook.toLowerCase() !== localPrediction.toLowerCase() || remotePrediction.toLowerCase() !== localPrediction.toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Bound hook metadata differs from the exact certified version, constructor economics, factory, initcode, salt or CREATE2 prediction");
|
|
259
385
|
const hookCode = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [predictedHook, toHex(block.number)], block), "bound hook code");
|
|
260
386
|
if (hookCode !== "0x") {
|
|
261
|
-
const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions] = await Promise.all([
|
|
262
|
-
readLifecycleContract<Hex>(client, deployer,
|
|
387
|
+
const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions, feePolicy] = await Promise.all([
|
|
388
|
+
readLifecycleContract<Hex>(client, deployer, deployerAbi, "deployedCodeHash", [predictedHook], block),
|
|
263
389
|
Promise.all((["poolManager", "registrar", "oracleFactory", "core", "liquidityLocker", "token"] as const).map((getter) => readLifecycleContract<Address>(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block))),
|
|
264
390
|
readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block),
|
|
265
391
|
readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block),
|
|
@@ -268,6 +394,10 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
|
|
|
268
394
|
readLifecycleContract<Hex>(client, predictedHook, fixedFeePoolHookV1Abi, "marketCommitment", [], block),
|
|
269
395
|
readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "openingSqrtPriceX96", [], block),
|
|
270
396
|
readLifecycleContract<number>(client, predictedHook, fixedFeePoolHookV1Abi, "expectedPositionCount", [], block),
|
|
397
|
+
config.version === 6 ? Promise.all([
|
|
398
|
+
readLifecycleContract<number>(client, predictedHook, fixedFeePoolHookConfigV6Abi, "minimumHookFeePips", [], block),
|
|
399
|
+
readLifecycleContract<number>(client, predictedHook, fixedFeePoolHookConfigV6Abi, "feeSensitivityPipsSecondsPerTick", [], block),
|
|
400
|
+
]) : undefined,
|
|
271
401
|
]);
|
|
272
402
|
if ((hookCode.length - 2) / 2 > 24576 || deployedCodeHash === zeroHash || deployedCodeHash.toLowerCase() !== keccak256(hookCode).toLowerCase()) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook lacks this exact typed deployer's bounded runtime-code provenance");
|
|
273
403
|
for (const [index, expectedAddress] of [expected.poolManager, expected.registrar, expected.oracleFactory, expected.core, expected.liquidityLocker, expected.token].entries()) {
|
|
@@ -276,20 +406,22 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
|
|
|
276
406
|
if (requiredFlags !== 0x1afcn || allFlags !== 0x3fffn) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing bound hook callback declarations differ from the reviewed envelope");
|
|
277
407
|
const [currency0, currency1] = BigInt(token) < BigInt(market.quoteAsset) ? [token, market.quoteAsset] : [market.quoteAsset, token];
|
|
278
408
|
const expectedPoolId = keccak256(encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }], [currency0, currency1, config.lpFeePips, config.tickSpacing, predictedHook]));
|
|
279
|
-
if (actualPoolId.toLowerCase() !== expectedPoolId.toLowerCase() || deploymentConfigHash.toLowerCase() !== keccak256(encodePoolBoundHookParameters(expected)).toLowerCase() || marketCommitment.toLowerCase() !== expected.marketCommitment.toLowerCase() || opening !== expected.sqrtPriceX96 || positions !== expected.expectedPositionCount
|
|
409
|
+
if (actualPoolId.toLowerCase() !== expectedPoolId.toLowerCase() || deploymentConfigHash.toLowerCase() !== keccak256(encodePoolBoundHookParameters(expected, config.version)).toLowerCase() || marketCommitment.toLowerCase() !== expected.marketCommitment.toLowerCase() || opening !== expected.sqrtPriceX96 || positions !== expected.expectedPositionCount ||
|
|
410
|
+
(config.version === 6 && (feePolicy?.[0] !== config.minimumHookFeePips || feePolicy?.[1] !== config.feeSensitivityPipsSecondsPerTick))) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing hook key, economic commitment, versioned fee policy or immutable opening geometry changed");
|
|
280
411
|
}
|
|
281
|
-
return { deployment: { deployer, initCodeHash, salt, predictedHook }, parameters: expected };
|
|
412
|
+
return Object.freeze({ deployment: Object.freeze({ deployer, initCodeHash, salt, predictedHook }), parameters: Object.freeze(expected), configVersion: config.version });
|
|
282
413
|
}
|
|
283
414
|
|
|
284
|
-
/**
|
|
415
|
+
/** Known identities use frozen local construction data; unlisted identities use
|
|
416
|
+
* explicit live certification. Exact launch execution, not either DTO, authorizes. */
|
|
285
417
|
export async function readPoolBoundHookDeployment(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, pinnedBlock?: LifecycleBlock): Promise<PoolBoundHookDeployment> {
|
|
286
418
|
const commitment = hashLaunchPlan(options.plan);
|
|
287
419
|
const block = pinnedBlock ?? await readLifecycleBlock(options.client);
|
|
288
420
|
return withLifecycleReadClient(options.client, async (client) => {
|
|
289
|
-
const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
|
|
421
|
+
const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block, commitment);
|
|
290
422
|
if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
|
|
291
423
|
if (pinnedBlock === undefined) await assertLifecycleBlock(client, block, options.plan.chainId);
|
|
292
|
-
return deployment;
|
|
424
|
+
return { ...deployment };
|
|
293
425
|
});
|
|
294
426
|
}
|
|
295
427
|
|
|
@@ -298,11 +430,12 @@ export async function buildPoolBoundHookDeploymentTransaction(options: { client:
|
|
|
298
430
|
const commitment = hashLaunchPlan(options.plan);
|
|
299
431
|
const block = await readLifecycleBlock(options.client);
|
|
300
432
|
return withLifecycleReadClient(options.client, async (client) => {
|
|
301
|
-
const { deployment, parameters } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
|
|
433
|
+
const { deployment, parameters, configVersion } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block, commitment);
|
|
302
434
|
if (!hasLifecycleV4HookPermissions(deployment.predictedHook)) throw new LifecyclePlanningError("INVALID_HOOK_BITS", "Finalize a salt with the exact lifecycle hook permissions before predeployment");
|
|
303
435
|
if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while constructing hook deployment calldata");
|
|
304
436
|
await assertLifecycleBlock(client, block, options.plan.chainId);
|
|
305
|
-
|
|
437
|
+
const abi = configVersion === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
|
|
438
|
+
return { to: deployment.deployer, data: encodeFunctionData({ abi, functionName: "deploy", args: [parameters, deployment.salt] }), value: 0n, deployment: { ...deployment } };
|
|
306
439
|
});
|
|
307
440
|
}
|
|
308
441
|
|
|
@@ -388,31 +521,38 @@ async function readReceipt(reference: LifecycleReceiptReference, options: ReadLa
|
|
|
388
521
|
|
|
389
522
|
/** Reads confirmation-bound canonical state and receipt provenance; local step counters are never used. */
|
|
390
523
|
export async function readLaunchProgress(options: ReadLaunchProgressOptions, pinnedBlock?: LifecycleBlock): Promise<CanonicalLaunchProgress> {
|
|
524
|
+
if (options.signal?.aborted) throw Object.assign(new Error("Lifecycle progress read cancelled"), { name: "AbortError" });
|
|
391
525
|
const block = pinnedBlock ?? await readLifecycleBlock(options.client);
|
|
392
526
|
return withLifecycleReadClient(options.client, (client) => readLaunchProgressAtBlock(options, client, block, pinnedBlock === undefined));
|
|
393
527
|
}
|
|
394
528
|
|
|
395
529
|
async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, client: LifecycleRpcClient, block: LifecycleBlock, checkBlock: boolean): Promise<CanonicalLaunchProgress> {
|
|
396
530
|
const { planned } = options;
|
|
531
|
+
const assertActive = () => {
|
|
532
|
+
if (options.signal?.aborted) throw Object.assign(new Error("Lifecycle progress read cancelled"), { name: "AbortError" });
|
|
533
|
+
};
|
|
534
|
+
assertActive();
|
|
397
535
|
const confirmationDepth = options.confirmations ?? planned.confirmations;
|
|
398
536
|
if (!Number.isSafeInteger(confirmationDepth) || confirmationDepth < 1) throw new LifecyclePlanningError("INVALID_CONFIRMATIONS", "Confirmation depth must be a positive integer");
|
|
399
537
|
const confirmedNumber = block.number >= BigInt(confirmationDepth - 1) ? block.number - BigInt(confirmationDepth - 1) : 0n;
|
|
400
538
|
const [confirmedBlock, chainIdValue] = await Promise.all([
|
|
401
539
|
confirmedNumber === block.number ? block : readLifecycleBlock(client, toHex(confirmedNumber)),
|
|
402
|
-
|
|
540
|
+
readLifecycleChainId(client),
|
|
403
541
|
]);
|
|
404
|
-
|
|
542
|
+
assertActive();
|
|
543
|
+
const chainId = chainIdValue;
|
|
405
544
|
if (chainId !== planned.plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed launch");
|
|
406
545
|
if (planned.account.toLowerCase() !== planned.plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
|
|
407
546
|
if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its commitment");
|
|
408
547
|
const [head, confirmedProgress, token, confirmedNonce, headNonce, pendingNonce] = await Promise.all([
|
|
409
548
|
readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], block, planned.account),
|
|
410
549
|
confirmedNumber === block.number ? undefined : readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], confirmedBlock, planned.account),
|
|
411
|
-
|
|
550
|
+
readLifecycleTokenAtBlock(client, planned.plan, block),
|
|
412
551
|
lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(confirmedBlock.number)]),
|
|
413
552
|
confirmedNumber === block.number ? undefined : lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(block.number)]),
|
|
414
553
|
lifecycleRpc(client, "eth_getTransactionCount", [planned.account, "pending"]),
|
|
415
554
|
]);
|
|
555
|
+
assertActive();
|
|
416
556
|
const canonical = confirmedProgress ?? head;
|
|
417
557
|
if (token.toLowerCase() !== planned.predictedToken.toLowerCase()) throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed for the committed token configuration");
|
|
418
558
|
for (const progress of [canonical, head]) if (progress.phase !== LifecyclePhase.None) {
|
|
@@ -426,6 +566,7 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
|
|
|
426
566
|
const progressParameters = [{ type: "tuple", components: launchProgressV1Components }] as const;
|
|
427
567
|
const confirmationSafe = confirmedAccountNonce === headAccountNonce && pendingAccountNonce === headAccountNonce && keccak256(encodeAbiParameters(progressParameters, [canonical])) === keccak256(encodeAbiParameters(progressParameters, [head]));
|
|
428
568
|
const receipts = await Promise.all((options.receipts ?? []).map((reference) => readReceipt({ ...reference, confirmations: Math.max(reference.confirmations ?? 1, confirmationDepth) }, { ...options, client }, block)));
|
|
569
|
+
assertActive();
|
|
429
570
|
let markets: LifecycleMarketProgress[] = [];
|
|
430
571
|
if (canonical.preparedMarkets > 0) {
|
|
431
572
|
const directory = await readLifecycleContract<Address>(client, planned.plan.orchestrator, launchLifecycleAbi, "directory", [], confirmedBlock);
|
|
@@ -436,12 +577,14 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
|
|
|
436
577
|
const live = await readLifecycleContract<MarketLiveStateV1>(client, adapter, lifecycleAdapterAbi, "readMarket", [planned.launchId, index], confirmedBlock);
|
|
437
578
|
return { index, prepared, live };
|
|
438
579
|
} catch (failure) {
|
|
439
|
-
|
|
580
|
+
if (failure instanceof Error && failure.name === "AbortError") throw failure;
|
|
581
|
+
return { index, prepared, error: lifecycleFailureReason(failure) };
|
|
440
582
|
}
|
|
441
583
|
}));
|
|
442
584
|
}
|
|
443
585
|
if (checkBlock) await assertLifecycleBlock(client, block, chainId);
|
|
444
586
|
if (confirmedNumber !== block.number) await assertLifecycleBlock(client, confirmedBlock, chainId);
|
|
445
587
|
if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan changed during asynchronous progress reads");
|
|
588
|
+
assertActive();
|
|
446
589
|
return { canonical, head, confirmationDepth, confirmedBlockNumber: confirmedBlock.number, confirmedBlockHash: confirmedBlock.hash, confirmedAccountNonce, headAccountNonce, pendingAccountNonce, confirmationSafe, token, planHash: planned.planHash, launchId: planned.launchId, chainId, blockNumber: block.number, blockHash: block.hash, markets, receipts };
|
|
447
590
|
}
|