@black-market/sdk 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +355 -392
- 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 +286 -118
- 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 -1
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +400 -100
- 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 +236 -39
- 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 -321
- package/src/lifecycle/presets.ts +243 -0
- package/src/lifecycle/progress.ts +254 -102
- package/src/lifecycle/rpc.ts +97 -9
- package/src/lifecycle/simulation.ts +391 -94
- 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. */
|
|
@@ -141,74 +222,126 @@ export async function validateV4LifecycleOracle(options: {
|
|
|
141
222
|
|
|
142
223
|
export async function readLifecycleProfiles(options: { client: LifecycleRpcClient; orchestrator: Address; profileIds?: readonly Hex[]; offset?: bigint; limit?: bigint }, pinnedBlock?: LifecycleBlock): Promise<LifecycleProfile[]> {
|
|
143
224
|
return withLifecycleReadClient(options.client, (client) => lifecycleStage(client, "profile.certification", async () => {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
]
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
envelope
|
|
181
|
-
protocolMaximumDeveloperFeeBps
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
225
|
+
const chainIdPromise = lifecycleRpc(client, "eth_chainId").then((value) => rpcQuantity(value, "chain ID"));
|
|
226
|
+
// Block-bound discovery does not depend on chain identity. Join both branches
|
|
227
|
+
// immediately so a failed chain read cannot become detached or a profile refusal.
|
|
228
|
+
const [chainId, { block, profiles }] = await Promise.all([
|
|
229
|
+
chainIdPromise,
|
|
230
|
+
(async () => {
|
|
231
|
+
const block = pinnedBlock ?? await readLifecycleBlock(client);
|
|
232
|
+
const registry = await readLifecycleContract<Address>(client, options.orchestrator, launchLifecycleAbi, "registry", [], block);
|
|
233
|
+
const offset = options.offset ?? 0n; const limit = options.limit ?? 100n;
|
|
234
|
+
if (offset < 0n || limit < 0n || limit > 100n) throw new LifecyclePlanningError("INVALID_PROFILE_PAGE", "Registry profile pages require unsigned offset and limit at most 100");
|
|
235
|
+
let protocolMaximumDeveloperFeeBpsPromise: Promise<number> | undefined;
|
|
236
|
+
const readProtocolMaximumDeveloperFeeBps = () => protocolMaximumDeveloperFeeBpsPromise ??=
|
|
237
|
+
readLifecycleContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
|
|
238
|
+
const readProfile = async (id: Hex): Promise<LifecycleProfile> => {
|
|
239
|
+
const registration = await readLifecycleContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [id], block);
|
|
240
|
+
const schema = registration.configSchema.toLowerCase();
|
|
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";
|
|
242
|
+
const [adapter, certification] = await Promise.all([
|
|
243
|
+
readLifecycleContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block),
|
|
244
|
+
venueKind === "unknown" ? undefined : Promise.all([
|
|
245
|
+
readLifecycleProfileTopology(client, registry, id, block),
|
|
246
|
+
venueKind === "uniswap-v4" ? Promise.all([
|
|
247
|
+
readLifecycleContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block),
|
|
248
|
+
readLifecycleContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
|
|
249
|
+
readProtocolMaximumDeveloperFeeBps(),
|
|
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,
|
|
257
|
+
]).then((values) => ({ values }), (failure: unknown) => ({ failure })),
|
|
258
|
+
]);
|
|
259
|
+
const chainId = await chainIdPromise;
|
|
260
|
+
let topology: ProfileTopologyV1 = { hookTopology: 0, configVersion: adapter.configVersion, hookDeployer: zeroAddress, hookCreationCodeHash: zeroHash };
|
|
261
|
+
let envelope: LaunchEnvelopeV2 | undefined; let developerTerms: LifecycleDeveloperTerms | undefined;
|
|
262
|
+
let protocolMaximumDeveloperFeeBps: number | undefined;
|
|
263
|
+
let admitted = false; let reason: string | undefined;
|
|
264
|
+
try {
|
|
265
|
+
if (certification === undefined) throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${registration.configSchema}`);
|
|
266
|
+
if ("failure" in certification) throw certification.failure;
|
|
267
|
+
const [recordedTopology, reviewed, eligibility] = certification.values;
|
|
268
|
+
topology = recordedTopology;
|
|
269
|
+
if (reviewed !== undefined) {
|
|
270
|
+
envelope = reviewed[0];
|
|
271
|
+
protocolMaximumDeveloperFeeBps = reviewed[2];
|
|
272
|
+
const [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled] = reviewed[1];
|
|
273
|
+
developerTerms = { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled };
|
|
274
|
+
if (implementation.toLowerCase() !== adapter.implementation.toLowerCase() || beneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
|
|
275
|
+
termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
|
|
276
|
+
maximumDeveloperFeeBps > reviewed[2]) throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
|
|
277
|
+
}
|
|
278
|
+
await certifyLifecycleProfile(client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId, eligibility);
|
|
279
|
+
if (developerTerms !== undefined && !developerTerms.enabled) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
|
|
280
|
+
admitted = true;
|
|
281
|
+
} catch (failure) { reason = lifecycleFailureReason(failure); }
|
|
282
|
+
return { id, registration, adapter, topology, envelope, developerTerms, protocolMaximumDeveloperFeeBps: venueKind === "uniswap-v4" ? protocolMaximumDeveloperFeeBps : undefined, admitted, reason, venueKind };
|
|
283
|
+
};
|
|
284
|
+
const [authority, profiles] = await Promise.all([
|
|
285
|
+
readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
|
|
286
|
+
Promise.resolve(options.profileIds ?? readLifecycleContract<readonly Hex[]>(client, registry, lifecycleRegistryAbi, "profileIds", [offset, limit], block)).then((ids) => Promise.all(ids.map(readProfile))),
|
|
287
|
+
]);
|
|
288
|
+
if (authority.toLowerCase() !== options.orchestrator.toLowerCase()) throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to the selected lifecycle orchestrator");
|
|
289
|
+
// A required V4 cap read still rejects the invocation on failure; Abyss has no developer-fee terms.
|
|
290
|
+
if (protocolMaximumDeveloperFeeBpsPromise !== undefined) await protocolMaximumDeveloperFeeBpsPromise;
|
|
291
|
+
return { block, profiles };
|
|
292
|
+
})(),
|
|
197
293
|
]);
|
|
198
|
-
if (authority.toLowerCase() !== options.orchestrator.toLowerCase()) throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to the selected lifecycle orchestrator");
|
|
199
|
-
// A required V4 cap read still rejects the invocation on failure; Abyss has no developer-fee terms.
|
|
200
|
-
if (protocolMaximumDeveloperFeeBpsPromise !== undefined) await protocolMaximumDeveloperFeeBpsPromise;
|
|
201
294
|
if (pinnedBlock === undefined) await assertLifecycleBlock(client, block, chainId);
|
|
202
295
|
return profiles;
|
|
203
296
|
}));
|
|
204
297
|
}
|
|
205
298
|
|
|
206
|
-
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> {
|
|
207
300
|
const { client, plan, marketIndex } = options;
|
|
208
301
|
const market = plan.markets[marketIndex];
|
|
209
|
-
if (market === undefined || market.configVersion
|
|
210
|
-
|
|
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");
|
|
211
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]!;
|
|
212
345
|
const registry = await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "registry", [], block);
|
|
213
346
|
const [authority, registration] = await Promise.all([
|
|
214
347
|
readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
|
|
@@ -226,34 +359,33 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
|
|
|
226
359
|
]);
|
|
227
360
|
const { boundGraph } = await certifyLifecycleProfile(client, plan.orchestrator, registry, market.profileId, registration, adapter, recorded, envelope, block, chainId);
|
|
228
361
|
if (boundGraph === undefined) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Market does not have a certified pool-bound deployer graph");
|
|
229
|
-
|
|
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);
|
|
230
364
|
validateV4LifecycleMarket({ market, config, token, profile: {
|
|
231
365
|
id: market.profileId, registration, adapter, topology: recorded, envelope,
|
|
232
366
|
developerTerms: { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled },
|
|
233
367
|
protocolMaximumDeveloperFeeBps, venueKind: "uniswap-v4", admitted: enabled,
|
|
234
368
|
} });
|
|
235
369
|
await validateV4LifecycleOracle({ client, envelope, oracleConfigId: config.oracleConfigId, block });
|
|
236
|
-
const expected
|
|
237
|
-
|
|
238
|
-
liquidityLocker: boundGraph.locker, token,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
expectedPositionCount: config.positions.length,
|
|
243
|
-
};
|
|
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;
|
|
244
376
|
const [[parameters, parameterSalt], [deployer, initCodeHash, salt, predictedHook], remoteInitCodeHash, remotePrediction] = await Promise.all([
|
|
245
|
-
readLifecycleContract<readonly [
|
|
377
|
+
readLifecycleContract<readonly [PoolBoundHookParameters, Hex]>(client, boundGraph.collectorFactory, collectorAbi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
|
|
246
378
|
readLifecycleContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block),
|
|
247
|
-
readLifecycleContract<Hex>(client, boundGraph.deployer,
|
|
248
|
-
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),
|
|
249
381
|
]);
|
|
250
|
-
const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected);
|
|
382
|
+
const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected, config.version);
|
|
251
383
|
const localPrediction = predictPoolBoundHookAddress({ deployer: boundGraph.deployer, initCodeHash: localInitCodeHash, salt: config.hookSalt });
|
|
252
|
-
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");
|
|
253
385
|
const hookCode = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [predictedHook, toHex(block.number)], block), "bound hook code");
|
|
254
386
|
if (hookCode !== "0x") {
|
|
255
|
-
const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions] = await Promise.all([
|
|
256
|
-
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),
|
|
257
389
|
Promise.all((["poolManager", "registrar", "oracleFactory", "core", "liquidityLocker", "token"] as const).map((getter) => readLifecycleContract<Address>(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block))),
|
|
258
390
|
readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block),
|
|
259
391
|
readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block),
|
|
@@ -262,6 +394,10 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
|
|
|
262
394
|
readLifecycleContract<Hex>(client, predictedHook, fixedFeePoolHookV1Abi, "marketCommitment", [], block),
|
|
263
395
|
readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "openingSqrtPriceX96", [], block),
|
|
264
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,
|
|
265
401
|
]);
|
|
266
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");
|
|
267
403
|
for (const [index, expectedAddress] of [expected.poolManager, expected.registrar, expected.oracleFactory, expected.core, expected.liquidityLocker, expected.token].entries()) {
|
|
@@ -270,20 +406,22 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
|
|
|
270
406
|
if (requiredFlags !== 0x1afcn || allFlags !== 0x3fffn) throw new LifecyclePlanningError("HOOK_DEPLOYMENT_CHANGED", "Existing bound hook callback declarations differ from the reviewed envelope");
|
|
271
407
|
const [currency0, currency1] = BigInt(token) < BigInt(market.quoteAsset) ? [token, market.quoteAsset] : [market.quoteAsset, token];
|
|
272
408
|
const expectedPoolId = keccak256(encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }], [currency0, currency1, config.lpFeePips, config.tickSpacing, predictedHook]));
|
|
273
|
-
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");
|
|
274
411
|
}
|
|
275
|
-
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 });
|
|
276
413
|
}
|
|
277
414
|
|
|
278
|
-
/**
|
|
415
|
+
/** Known identities use frozen local construction data; unlisted identities use
|
|
416
|
+
* explicit live certification. Exact launch execution, not either DTO, authorizes. */
|
|
279
417
|
export async function readPoolBoundHookDeployment(options: { client: LifecycleRpcClient; plan: LaunchPlanV1; marketIndex: number }, pinnedBlock?: LifecycleBlock): Promise<PoolBoundHookDeployment> {
|
|
280
418
|
const commitment = hashLaunchPlan(options.plan);
|
|
281
419
|
const block = pinnedBlock ?? await readLifecycleBlock(options.client);
|
|
282
420
|
return withLifecycleReadClient(options.client, async (client) => {
|
|
283
|
-
const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
|
|
421
|
+
const { deployment } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block, commitment);
|
|
284
422
|
if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while reading hook deployment metadata");
|
|
285
423
|
if (pinnedBlock === undefined) await assertLifecycleBlock(client, block, options.plan.chainId);
|
|
286
|
-
return deployment;
|
|
424
|
+
return { ...deployment };
|
|
287
425
|
});
|
|
288
426
|
}
|
|
289
427
|
|
|
@@ -292,11 +430,12 @@ export async function buildPoolBoundHookDeploymentTransaction(options: { client:
|
|
|
292
430
|
const commitment = hashLaunchPlan(options.plan);
|
|
293
431
|
const block = await readLifecycleBlock(options.client);
|
|
294
432
|
return withLifecycleReadClient(options.client, async (client) => {
|
|
295
|
-
const { deployment, parameters } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block);
|
|
433
|
+
const { deployment, parameters, configVersion } = await readPoolBoundHookDeploymentDetails({ ...options, client }, block, commitment);
|
|
296
434
|
if (!hasLifecycleV4HookPermissions(deployment.predictedHook)) throw new LifecyclePlanningError("INVALID_HOOK_BITS", "Finalize a salt with the exact lifecycle hook permissions before predeployment");
|
|
297
435
|
if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while constructing hook deployment calldata");
|
|
298
436
|
await assertLifecycleBlock(client, block, options.plan.chainId);
|
|
299
|
-
|
|
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 } };
|
|
300
439
|
});
|
|
301
440
|
}
|
|
302
441
|
|
|
@@ -382,28 +521,38 @@ async function readReceipt(reference: LifecycleReceiptReference, options: ReadLa
|
|
|
382
521
|
|
|
383
522
|
/** Reads confirmation-bound canonical state and receipt provenance; local step counters are never used. */
|
|
384
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" });
|
|
385
525
|
const block = pinnedBlock ?? await readLifecycleBlock(options.client);
|
|
386
526
|
return withLifecycleReadClient(options.client, (client) => readLaunchProgressAtBlock(options, client, block, pinnedBlock === undefined));
|
|
387
527
|
}
|
|
388
528
|
|
|
389
529
|
async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, client: LifecycleRpcClient, block: LifecycleBlock, checkBlock: boolean): Promise<CanonicalLaunchProgress> {
|
|
390
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();
|
|
391
535
|
const confirmationDepth = options.confirmations ?? planned.confirmations;
|
|
392
536
|
if (!Number.isSafeInteger(confirmationDepth) || confirmationDepth < 1) throw new LifecyclePlanningError("INVALID_CONFIRMATIONS", "Confirmation depth must be a positive integer");
|
|
393
537
|
const confirmedNumber = block.number >= BigInt(confirmationDepth - 1) ? block.number - BigInt(confirmationDepth - 1) : 0n;
|
|
394
|
-
const confirmedBlock =
|
|
395
|
-
|
|
538
|
+
const [confirmedBlock, chainIdValue] = await Promise.all([
|
|
539
|
+
confirmedNumber === block.number ? block : readLifecycleBlock(client, toHex(confirmedNumber)),
|
|
540
|
+
readLifecycleChainId(client),
|
|
541
|
+
]);
|
|
542
|
+
assertActive();
|
|
543
|
+
const chainId = chainIdValue;
|
|
396
544
|
if (chainId !== planned.plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed launch");
|
|
397
545
|
if (planned.account.toLowerCase() !== planned.plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
|
|
398
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");
|
|
399
547
|
const [head, confirmedProgress, token, confirmedNonce, headNonce, pendingNonce] = await Promise.all([
|
|
400
548
|
readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], block, planned.account),
|
|
401
549
|
confirmedNumber === block.number ? undefined : readLifecycleContract<LaunchProgressV1>(client, planned.plan.orchestrator, launchLifecycleAbi, "readLaunchProgress", [planned.launchId], confirmedBlock, planned.account),
|
|
402
|
-
|
|
550
|
+
readLifecycleTokenAtBlock(client, planned.plan, block),
|
|
403
551
|
lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(confirmedBlock.number)]),
|
|
404
552
|
confirmedNumber === block.number ? undefined : lifecycleRpc(client, "eth_getTransactionCount", [planned.account, toHex(block.number)]),
|
|
405
553
|
lifecycleRpc(client, "eth_getTransactionCount", [planned.account, "pending"]),
|
|
406
554
|
]);
|
|
555
|
+
assertActive();
|
|
407
556
|
const canonical = confirmedProgress ?? head;
|
|
408
557
|
if (token.toLowerCase() !== planned.predictedToken.toLowerCase()) throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed for the committed token configuration");
|
|
409
558
|
for (const progress of [canonical, head]) if (progress.phase !== LifecyclePhase.None) {
|
|
@@ -417,6 +566,7 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
|
|
|
417
566
|
const progressParameters = [{ type: "tuple", components: launchProgressV1Components }] as const;
|
|
418
567
|
const confirmationSafe = confirmedAccountNonce === headAccountNonce && pendingAccountNonce === headAccountNonce && keccak256(encodeAbiParameters(progressParameters, [canonical])) === keccak256(encodeAbiParameters(progressParameters, [head]));
|
|
419
568
|
const receipts = await Promise.all((options.receipts ?? []).map((reference) => readReceipt({ ...reference, confirmations: Math.max(reference.confirmations ?? 1, confirmationDepth) }, { ...options, client }, block)));
|
|
569
|
+
assertActive();
|
|
420
570
|
let markets: LifecycleMarketProgress[] = [];
|
|
421
571
|
if (canonical.preparedMarkets > 0) {
|
|
422
572
|
const directory = await readLifecycleContract<Address>(client, planned.plan.orchestrator, launchLifecycleAbi, "directory", [], confirmedBlock);
|
|
@@ -427,12 +577,14 @@ async function readLaunchProgressAtBlock(options: ReadLaunchProgressOptions, cli
|
|
|
427
577
|
const live = await readLifecycleContract<MarketLiveStateV1>(client, adapter, lifecycleAdapterAbi, "readMarket", [planned.launchId, index], confirmedBlock);
|
|
428
578
|
return { index, prepared, live };
|
|
429
579
|
} catch (failure) {
|
|
430
|
-
|
|
580
|
+
if (failure instanceof Error && failure.name === "AbortError") throw failure;
|
|
581
|
+
return { index, prepared, error: lifecycleFailureReason(failure) };
|
|
431
582
|
}
|
|
432
583
|
}));
|
|
433
584
|
}
|
|
434
585
|
if (checkBlock) await assertLifecycleBlock(client, block, chainId);
|
|
435
586
|
if (confirmedNumber !== block.number) await assertLifecycleBlock(client, confirmedBlock, chainId);
|
|
436
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();
|
|
437
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 };
|
|
438
590
|
}
|