@black-market/sdk 0.6.4 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +381 -118
- 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 +369 -111
- package/src/lifecycle/transactions.ts +34 -0
- package/src/lifecycle/types.ts +35 -3
package/src/lifecycle/markets.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { concatHex, decodeAbiParameters, encodeAbiParameters,
|
|
1
|
+
import { concatHex, decodeAbiParameters, encodeAbiParameters, getAddress, hexToBytes, keccak256, pad, stringToHex, toHex, zeroHash, type Address, type Hex } from "viem";
|
|
2
2
|
import type { MarketConfigV1 } from "./schema.js";
|
|
3
3
|
import { LifecyclePlanningError, type LifecycleProfile, type PoolBoundHookSaltProgress } from "./types.js";
|
|
4
4
|
|
|
@@ -36,32 +36,87 @@ export function decodeV4LifecycleMarketConfig(encoded: Hex): V4LifecycleMarketCo
|
|
|
36
36
|
return config as V4LifecycleMarketConfig;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export type
|
|
39
|
+
export type PoolBoundLifecycleConfigVersion = 5 | 6;
|
|
40
|
+
export type V4PoolBoundLifecycleMarketConfigV5 = Omit<V4LifecycleMarketConfig, "version"> & {
|
|
41
|
+
version: 5; hookSalt: Hex; minimumHookFeePips?: never; feeSensitivityPipsSecondsPerTick?: never;
|
|
42
|
+
};
|
|
43
|
+
export type V4PoolBoundLifecycleMarketConfigV6 = Omit<V4LifecycleMarketConfig, "version"> & {
|
|
44
|
+
version: 6; minimumHookFeePips: number; feeSensitivityPipsSecondsPerTick: number; hookSalt: Hex;
|
|
45
|
+
};
|
|
46
|
+
export type V4PoolBoundLifecycleMarketConfig = V4PoolBoundLifecycleMarketConfigV5 | V4PoolBoundLifecycleMarketConfigV6;
|
|
40
47
|
export const V4_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint16,uint24,int24,uint160,uint24,uint8,uint8,address,bool,bytes32,bytes32,bytes32,address,uint16,(int24,int24,uint128,bytes32,uint256)[])"));
|
|
41
|
-
export const
|
|
48
|
+
export const V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V5 = keccak256(stringToHex("(uint16,uint24,int24,uint160,uint24,uint8,uint8,address,bool,bytes32,bytes32,bytes32,bytes32,address,uint16,(int24,int24,uint128,bytes32,uint256)[])"));
|
|
49
|
+
export const V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6 = keccak256(stringToHex("(uint16,uint24,int24,uint160,uint24,uint24,uint32,uint8,uint8,address,bool,bytes32,bytes32,bytes32,bytes32,address,uint16,(int24,int24,uint128,bytes32,uint256)[])"));
|
|
42
50
|
export const V4_POOL_BOUND_MARKET_ECONOMICS_DOMAIN = keccak256(stringToHex("black-market.pool-bound-market-economics.v1"));
|
|
43
51
|
export const V4_LIFECYCLE_HOOK_PERMISSION_MASK = 0x3fffn;
|
|
44
52
|
export const V4_LIFECYCLE_HOOK_PERMISSIONS = 0x1afcn;
|
|
45
|
-
export const
|
|
53
|
+
export const poolBoundV4LifecycleMarketComponentsV5 = [
|
|
46
54
|
{ name: "version", type: "uint16" }, { name: "lpFeePips", type: "uint24" }, { name: "tickSpacing", type: "int24" },
|
|
47
55
|
{ name: "sqrtPriceX96", type: "uint160" }, { name: "hookFeePips", type: "uint24" }, { name: "feeMode", type: "uint8" },
|
|
48
56
|
{ name: "protocolFeeDenominator", type: "uint8" }, { name: "treasury", type: "address" },
|
|
49
57
|
{ name: "externalLiquidityDisabled", type: "bool" }, { name: "oracleConfigId", type: "bytes32" },
|
|
58
|
+
{ name: "hookSalt", type: "bytes32" }, { name: "profileId", type: "bytes32" }, { name: "termsDigest", type: "bytes32" },
|
|
59
|
+
{ name: "developerBeneficiary", type: "address" }, { name: "developerFeeBps", type: "uint16" },
|
|
60
|
+
{ name: "positions", type: "tuple[]", components: v4LifecyclePositionComponents },
|
|
61
|
+
] as const;
|
|
62
|
+
export const poolBoundV4LifecycleMarketComponentsV6 = [
|
|
63
|
+
{ name: "version", type: "uint16" }, { name: "lpFeePips", type: "uint24" }, { name: "tickSpacing", type: "int24" },
|
|
64
|
+
{ name: "sqrtPriceX96", type: "uint160" }, { name: "hookFeePips", type: "uint24" },
|
|
65
|
+
{ name: "minimumHookFeePips", type: "uint24" }, { name: "feeSensitivityPipsSecondsPerTick", type: "uint32" },
|
|
66
|
+
{ name: "feeMode", type: "uint8" },
|
|
67
|
+
{ name: "protocolFeeDenominator", type: "uint8" }, { name: "treasury", type: "address" },
|
|
68
|
+
{ name: "externalLiquidityDisabled", type: "bool" }, { name: "oracleConfigId", type: "bytes32" },
|
|
50
69
|
{ name: "hookSalt", type: "bytes32" },
|
|
51
70
|
{ name: "profileId", type: "bytes32" }, { name: "termsDigest", type: "bytes32" },
|
|
52
71
|
{ name: "developerBeneficiary", type: "address" }, { name: "developerFeeBps", type: "uint16" },
|
|
53
72
|
{ name: "positions", type: "tuple[]", components: v4LifecyclePositionComponents },
|
|
54
73
|
] as const;
|
|
55
74
|
|
|
75
|
+
export function isPoolBoundV4ConfigVersion(version: number): version is PoolBoundLifecycleConfigVersion {
|
|
76
|
+
return version === 5 || version === 6;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function poolBoundV4LifecycleConfigSchema(version: PoolBoundLifecycleConfigVersion): Hex {
|
|
80
|
+
if (version === 5) return V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V5;
|
|
81
|
+
if (version === 6) return V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA_V6;
|
|
82
|
+
throw new LifecyclePlanningError("UNSUPPORTED_CONFIG_VERSION", "Pool-bound V4 config version must be explicitly 5 or 6");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function assertV1HookFeeShape(config: object): void {
|
|
86
|
+
if ("minimumHookFeePips" in config || "feeSensitivityPipsSecondsPerTick" in config) {
|
|
87
|
+
throw new LifecyclePlanningError("UNSUPPORTED_CONFIG_VERSION", "Config5/V1 cannot encode config6/V2 fee-policy fields");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
56
91
|
export function encodePoolBoundV4LifecycleMarketConfig(config: V4PoolBoundLifecycleMarketConfig): Hex {
|
|
57
|
-
if (config.version
|
|
92
|
+
if (!isPoolBoundV4ConfigVersion(config.version)) throw new LifecyclePlanningError("UNSUPPORTED_CONFIG_VERSION", "Pool-bound V4 config version must be explicitly 5 or 6");
|
|
93
|
+
if (config.version === 6) validatePoolBoundHookFees(config);
|
|
94
|
+
else assertV1HookFeeShape(config);
|
|
58
95
|
if (!Number.isInteger(config.developerFeeBps) || config.developerFeeBps < 0 || config.developerFeeBps > 65535) throw new Error("developerFeeBps must be an explicit uint16");
|
|
59
|
-
return
|
|
96
|
+
return config.version === 5
|
|
97
|
+
? encodeAbiParameters([{ type: "tuple", components: poolBoundV4LifecycleMarketComponentsV5 }], [config])
|
|
98
|
+
: encodeAbiParameters([{ type: "tuple", components: poolBoundV4LifecycleMarketComponentsV6 }], [config]);
|
|
60
99
|
}
|
|
61
|
-
export function decodePoolBoundV4LifecycleMarketConfig(encoded: Hex): V4PoolBoundLifecycleMarketConfig {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
100
|
+
export function decodePoolBoundV4LifecycleMarketConfig(encoded: Hex, expectedVersion?: PoolBoundLifecycleConfigVersion): V4PoolBoundLifecycleMarketConfig {
|
|
101
|
+
// Both dynamic config tuples begin with their ABI offset and explicit uint16 version.
|
|
102
|
+
// The selected schema must re-encode byte-for-byte; relabelled or padded tuples are not another version.
|
|
103
|
+
const [offset, version] = decodeAbiParameters([{ type: "uint256" }, { type: "uint16" }], encoded);
|
|
104
|
+
if (!isPoolBoundV4ConfigVersion(version)) throw new LifecyclePlanningError("UNSUPPORTED_CONFIG_VERSION", "Pool-bound V4 config version must be explicitly 5 or 6");
|
|
105
|
+
if (offset !== 32n || expectedVersion !== undefined && version !== expectedVersion) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Pool-bound wire config does not match its committed version");
|
|
106
|
+
const config = version === 5
|
|
107
|
+
? decodeAbiParameters([{ type: "tuple", components: poolBoundV4LifecycleMarketComponentsV5 }], encoded)[0] as V4PoolBoundLifecycleMarketConfigV5
|
|
108
|
+
: decodeAbiParameters([{ type: "tuple", components: poolBoundV4LifecycleMarketComponentsV6 }], encoded)[0] as V4PoolBoundLifecycleMarketConfigV6;
|
|
109
|
+
if (encodePoolBoundV4LifecycleMarketConfig(config).toLowerCase() !== encoded.toLowerCase()) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Pool-bound config is not canonical for its explicit schema");
|
|
110
|
+
return config;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Immutable per-pool fee policy: pips, and fee-pips * seconds per observed tick. */
|
|
114
|
+
export function validatePoolBoundHookFees(config: Pick<PoolBoundHookParametersV2, "hookFeePips" | "minimumHookFeePips" | "feeSensitivityPipsSecondsPerTick">): void {
|
|
115
|
+
if (!Number.isInteger(config.hookFeePips) || config.hookFeePips < 0 || config.hookFeePips > 1000000 ||
|
|
116
|
+
!Number.isInteger(config.minimumHookFeePips) || config.minimumHookFeePips < 0 || config.minimumHookFeePips > config.hookFeePips ||
|
|
117
|
+
!Number.isInteger(config.feeSensitivityPipsSecondsPerTick) || config.feeSensitivityPipsSecondsPerTick < 0 || config.feeSensitivityPipsSecondsPerTick > 0xffffffff) {
|
|
118
|
+
throw new LifecyclePlanningError("INVALID_HOOK_FEES", "Pool-bound fees require integer 0 <= minimum <= maximum <= 1000000 pips and sensitivity between 0 and 4294967295 fee-pips * seconds/tick");
|
|
119
|
+
}
|
|
65
120
|
}
|
|
66
121
|
|
|
67
122
|
/** Exact pending-source admission terms; author identity is never the live payout address. */
|
|
@@ -69,12 +124,15 @@ export function validateV4LifecycleMarket(options: {
|
|
|
69
124
|
market: MarketConfigV1; config: V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig; profile: LifecycleProfile; token: Address;
|
|
70
125
|
}): void {
|
|
71
126
|
const { market, config, profile, token } = options;
|
|
72
|
-
if (config.version !== 4 && config.version
|
|
127
|
+
if (config.version !== 4 && !isPoolBoundV4ConfigVersion(config.version)) throw new LifecyclePlanningError("UNSUPPORTED_CONFIG_VERSION", "Reviewed V4 config version must be 4, 5 or 6");
|
|
128
|
+
if (config.version === 6) validatePoolBoundHookFees(config);
|
|
129
|
+
else if (config.version === 5) assertV1HookFeeShape(config);
|
|
73
130
|
const { envelope, developerTerms: terms, protocolMaximumDeveloperFeeBps: protocolCeiling } = profile;
|
|
74
131
|
if (envelope === undefined || terms === undefined || protocolCeiling === undefined || !terms.enabled ||
|
|
75
132
|
profile.topology.hookTopology !== (config.version === 4 ? 1 : 2) || market.configVersion !== config.version ||
|
|
76
133
|
profile.topology.configVersion !== config.version || profile.adapter.configVersion !== config.version ||
|
|
77
134
|
envelope.topology !== profile.topology.hookTopology || envelope.configVersion !== config.version || envelope.economicVersion !== 3 ||
|
|
135
|
+
profile.registration.configSchema.toLowerCase() !== (config.version === 4 ? V4_LIFECYCLE_CONFIG_SCHEMA : poolBoundV4LifecycleConfigSchema(config.version)).toLowerCase() ||
|
|
78
136
|
market.adapterId.toLowerCase() !== profile.registration.adapterId.toLowerCase() ||
|
|
79
137
|
market.profileId.toLowerCase() !== profile.id.toLowerCase() || config.profileId.toLowerCase() !== profile.id.toLowerCase() ||
|
|
80
138
|
terms.adapter.toLowerCase() !== profile.adapter.implementation.toLowerCase() ||
|
|
@@ -91,7 +149,7 @@ export function validateV4LifecycleMarket(options: {
|
|
|
91
149
|
const b = envelope.bounds;
|
|
92
150
|
if (config.oracleConfigId.toLowerCase() === zeroHash) throw new LifecyclePlanningError("INVALID_ORACLE_CONFIG", "Market oracle configuration must be nonzero");
|
|
93
151
|
if (config.treasury.toLowerCase() !== envelope.protocolTreasury.toLowerCase() ||
|
|
94
|
-
config.protocolFeeDenominator !== envelope.protocolFeeDenominator || !Number.isInteger(config.hookFeePips) || config.hookFeePips < 0 || config.hookFeePips >= 1000000 ||
|
|
152
|
+
config.protocolFeeDenominator !== envelope.protocolFeeDenominator || !Number.isInteger(config.hookFeePips) || config.hookFeePips < 0 || (config.version === 6 ? config.hookFeePips > 1000000 : config.hookFeePips >= 1000000) ||
|
|
95
153
|
!Number.isInteger(config.lpFeePips) || config.lpFeePips < 0 || config.lpFeePips >= 1000000 ||
|
|
96
154
|
!Number.isInteger(config.feeMode) || config.feeMode < 0 || config.feeMode > 1 || (b.feeModeFlags & (1 << config.feeMode)) === 0 ||
|
|
97
155
|
config.tickSpacing < b.minimumTickSpacing || config.tickSpacing > b.maximumTickSpacing ||
|
|
@@ -102,10 +160,14 @@ export function validateV4LifecycleMarket(options: {
|
|
|
102
160
|
|
|
103
161
|
export type PoolBoundHookParametersV1 = {
|
|
104
162
|
poolManager: Address; registrar: Address; oracleFactory: Address; core: Address; liquidityLocker: Address;
|
|
105
|
-
token: Address; quoteCurrency: Address; lpFeePips: number; tickSpacing: number; sqrtPriceX96: bigint;
|
|
106
|
-
|
|
163
|
+
token: Address; quoteCurrency: Address; lpFeePips: number; tickSpacing: number; sqrtPriceX96: bigint; hookFeePips: number;
|
|
164
|
+
feeMode: number; protocolFeeDenominator: number; treasury: Address;
|
|
107
165
|
externalLiquidityDisabled: boolean; oracleConfigId: Hex; marketCommitment: Hex; expectedPositionCount: number;
|
|
108
166
|
};
|
|
167
|
+
export type PoolBoundHookParametersV2 = PoolBoundHookParametersV1 & {
|
|
168
|
+
minimumHookFeePips: number; feeSensitivityPipsSecondsPerTick: number;
|
|
169
|
+
};
|
|
170
|
+
export type PoolBoundHookParameters = PoolBoundHookParametersV1 | PoolBoundHookParametersV2;
|
|
109
171
|
export const poolBoundHookParametersV1Components = [
|
|
110
172
|
{ name: "poolManager", type: "address" }, { name: "registrar", type: "address" },
|
|
111
173
|
{ name: "oracleFactory", type: "address" }, { name: "core", type: "address" },
|
|
@@ -117,13 +179,51 @@ export const poolBoundHookParametersV1Components = [
|
|
|
117
179
|
{ name: "externalLiquidityDisabled", type: "bool" }, { name: "oracleConfigId", type: "bytes32" },
|
|
118
180
|
{ name: "marketCommitment", type: "bytes32" }, { name: "expectedPositionCount", type: "uint32" },
|
|
119
181
|
] as const;
|
|
120
|
-
export
|
|
121
|
-
|
|
182
|
+
export const poolBoundHookParametersV2Components = [
|
|
183
|
+
{ name: "poolManager", type: "address" }, { name: "registrar", type: "address" },
|
|
184
|
+
{ name: "oracleFactory", type: "address" }, { name: "core", type: "address" },
|
|
185
|
+
{ name: "liquidityLocker", type: "address" }, { name: "token", type: "address" },
|
|
186
|
+
{ name: "quoteCurrency", type: "address" }, { name: "lpFeePips", type: "uint24" },
|
|
187
|
+
{ name: "tickSpacing", type: "int24" }, { name: "sqrtPriceX96", type: "uint160" },
|
|
188
|
+
{ name: "hookFeePips", type: "uint24" },
|
|
189
|
+
{ name: "minimumHookFeePips", type: "uint24" }, { name: "feeSensitivityPipsSecondsPerTick", type: "uint32" },
|
|
190
|
+
{ name: "feeMode", type: "uint8" },
|
|
191
|
+
{ name: "protocolFeeDenominator", type: "uint8" }, { name: "treasury", type: "address" },
|
|
192
|
+
{ name: "externalLiquidityDisabled", type: "bool" }, { name: "oracleConfigId", type: "bytes32" },
|
|
193
|
+
{ name: "marketCommitment", type: "bytes32" }, { name: "expectedPositionCount", type: "uint32" },
|
|
194
|
+
] as const;
|
|
195
|
+
export function encodePoolBoundHookParameters(parameters: PoolBoundHookParameters, configVersion: PoolBoundLifecycleConfigVersion): Hex {
|
|
196
|
+
if (configVersion === 5) {
|
|
197
|
+
assertV1HookFeeShape(parameters);
|
|
198
|
+
return encodeAbiParameters([{ type: "tuple", components: poolBoundHookParametersV1Components }], [parameters]);
|
|
199
|
+
}
|
|
200
|
+
if (configVersion !== 6) throw new LifecyclePlanningError("UNSUPPORTED_CONFIG_VERSION", "Constructor encoding requires explicit config5/V1 or config6/V2 context");
|
|
201
|
+
const v2 = parameters as PoolBoundHookParametersV2;
|
|
202
|
+
validatePoolBoundHookFees(v2);
|
|
203
|
+
return encodeAbiParameters([{ type: "tuple", components: poolBoundHookParametersV2Components }], [v2]);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The explicit market version selects constructor economics, never missing-field defaults. */
|
|
207
|
+
export function buildPoolBoundHookParameters(options: {
|
|
208
|
+
chainId: bigint; core: Address; poolManager: Address; registrar: Address; oracleFactory: Address;
|
|
209
|
+
liquidityLocker: Address; token: Address; market: MarketConfigV1; config: V4PoolBoundLifecycleMarketConfig;
|
|
210
|
+
}): PoolBoundHookParameters {
|
|
211
|
+
const { config, market } = options;
|
|
212
|
+
if (config.version !== market.configVersion) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Constructor config differs from the committed market version");
|
|
213
|
+
return {
|
|
214
|
+
poolManager: options.poolManager, registrar: options.registrar, oracleFactory: options.oracleFactory, core: options.core,
|
|
215
|
+
liquidityLocker: options.liquidityLocker, token: options.token, quoteCurrency: market.quoteAsset,
|
|
216
|
+
lpFeePips: config.lpFeePips, tickSpacing: config.tickSpacing, sqrtPriceX96: config.sqrtPriceX96, hookFeePips: config.hookFeePips,
|
|
217
|
+
...(config.version === 6 ? { minimumHookFeePips: config.minimumHookFeePips, feeSensitivityPipsSecondsPerTick: config.feeSensitivityPipsSecondsPerTick } : {}),
|
|
218
|
+
feeMode: config.feeMode, protocolFeeDenominator: config.protocolFeeDenominator, treasury: config.treasury,
|
|
219
|
+
externalLiquidityDisabled: config.externalLiquidityDisabled, oracleConfigId: config.oracleConfigId,
|
|
220
|
+
marketCommitment: hashPoolBoundV4MarketCommitment(options), expectedPositionCount: config.positions.length,
|
|
221
|
+
};
|
|
122
222
|
}
|
|
123
223
|
export function hashPoolBoundV4MarketCommitment(options: { chainId: bigint; core: Address; registrar: Address; token: Address; market: MarketConfigV1 }): Hex {
|
|
124
224
|
const { chainId, core, registrar, token, market } = options;
|
|
125
|
-
if (market.configVersion
|
|
126
|
-
const config = decodePoolBoundV4LifecycleMarketConfig(market.config);
|
|
225
|
+
if (!isPoolBoundV4ConfigVersion(market.configVersion)) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Pool-bound V4 market requires explicit config version 5 or 6");
|
|
226
|
+
const config = decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion);
|
|
127
227
|
if (config.profileId.toLowerCase() !== market.profileId.toLowerCase()) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Config profile differs from the committed market profile");
|
|
128
228
|
const configHash = keccak256(encodePoolBoundV4LifecycleMarketConfig({ ...config, hookSalt: zeroHash }));
|
|
129
229
|
return keccak256(encodeAbiParameters([
|
|
@@ -134,11 +234,26 @@ export function hashPoolBoundV4MarketCommitment(options: { chainId: bigint; core
|
|
|
134
234
|
export function hasLifecycleV4HookPermissions(hook: Address): boolean {
|
|
135
235
|
return (BigInt(hook) & V4_LIFECYCLE_HOOK_PERMISSION_MASK) === V4_LIFECYCLE_HOOK_PERMISSIONS;
|
|
136
236
|
}
|
|
237
|
+
function poolBoundHookPreimage(deployer: Address, initCodeHash: Hex): Uint8Array {
|
|
238
|
+
const codeHash = hexToBytes(initCodeHash);
|
|
239
|
+
const preimage = new Uint8Array(53 + codeHash.length);
|
|
240
|
+
preimage[0] = 0xff;
|
|
241
|
+
preimage.set(hexToBytes(getAddress(deployer)), 1);
|
|
242
|
+
preimage.set(codeHash, 53);
|
|
243
|
+
return preimage;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function poolBoundHookAddress(digest: Uint8Array): Address {
|
|
247
|
+
return getAddress(toHex(digest.subarray(12)));
|
|
248
|
+
}
|
|
249
|
+
|
|
137
250
|
export function predictPoolBoundHookAddress(options: { deployer: Address; initCodeHash: Hex; salt: Hex }): Address {
|
|
138
|
-
|
|
251
|
+
const preimage = poolBoundHookPreimage(options.deployer, options.initCodeHash);
|
|
252
|
+
preimage.set(pad(hexToBytes(options.salt), { size: 32 }), 21);
|
|
253
|
+
return poolBoundHookAddress(keccak256(preimage, "bytes"));
|
|
139
254
|
}
|
|
140
|
-
export function poolBoundHookInitCodeHash(creationCode: Hex, parameters:
|
|
141
|
-
return keccak256(concatHex([creationCode, encodePoolBoundHookParameters(parameters)]));
|
|
255
|
+
export function poolBoundHookInitCodeHash(creationCode: Hex, parameters: PoolBoundHookParameters, configVersion: PoolBoundLifecycleConfigVersion): Hex {
|
|
256
|
+
return keccak256(concatHex([creationCode, encodePoolBoundHookParameters(parameters, configVersion)]));
|
|
142
257
|
}
|
|
143
258
|
|
|
144
259
|
/** Deterministic CREATE2 search with a macrotask boundary before work and between bounded batches. */
|
|
@@ -147,36 +262,55 @@ export async function minePoolBoundHookSalt(options: {
|
|
|
147
262
|
onProgress?: (progress: PoolBoundHookSaltProgress) => void;
|
|
148
263
|
}): Promise<{ salt: Hex; predictedHook: Address }> {
|
|
149
264
|
const { signal, onProgress } = options;
|
|
150
|
-
|
|
265
|
+
const candidate = options.startSalt ?? 0n;
|
|
151
266
|
if (candidate < 0n || candidate >= 1n << 256n) throw new LifecyclePlanningError("INVALID_HOOK_SALT", "Starting hook salt must fit uint256");
|
|
152
267
|
const checkCancelled = () => {
|
|
153
268
|
if (signal?.aborted) throw Object.assign(new Error("Pool-bound hook salt mining cancelled"), { name: "AbortError" });
|
|
154
269
|
};
|
|
155
270
|
let attempts = 0n;
|
|
156
271
|
checkCancelled();
|
|
157
|
-
|
|
158
|
-
|
|
272
|
+
// The same EIP-1014 owner as the public predictor freezes immutable prefix/
|
|
273
|
+
// suffix once. Only uint256 salt bytes change; viem still owns exact Keccak.
|
|
274
|
+
const preimage = poolBoundHookPreimage(options.deployer, options.initCodeHash);
|
|
275
|
+
const saltBytes = preimage.subarray(21, 53);
|
|
276
|
+
saltBytes.set(hexToBytes(toHex(candidate, { size: 32 })));
|
|
277
|
+
const mask = Number(V4_LIFECYCLE_HOOK_PERMISSION_MASK);
|
|
278
|
+
const permissions = Number(V4_LIFECYCLE_HOOK_PERMISSIONS);
|
|
279
|
+
let firstDigest = onProgress === undefined ? undefined : keccak256(preimage, "bytes");
|
|
280
|
+
if (onProgress !== undefined) onProgress({ attempts, salt: toHex(saltBytes), predictedHook: poolBoundHookAddress(firstDigest!) });
|
|
281
|
+
let advanceSalt = false;
|
|
159
282
|
for (;;) {
|
|
160
283
|
checkCancelled();
|
|
161
284
|
await new Promise<void>((resolve) => globalThis.setTimeout(resolve, 0));
|
|
162
285
|
checkCancelled();
|
|
163
286
|
const deadline = Date.now() + 8;
|
|
164
|
-
let
|
|
287
|
+
let lastDigest: Uint8Array | undefined;
|
|
288
|
+
let batchAttempts = 0;
|
|
165
289
|
for (let batch = 0; batch < 256 && (batch === 0 || Date.now() < deadline); batch += 1) {
|
|
166
290
|
checkCancelled();
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
291
|
+
if (advanceSalt) {
|
|
292
|
+
let byte = saltBytes.length - 1;
|
|
293
|
+
while (byte >= 0 && saltBytes[byte] === 0xff) { saltBytes[byte] = 0; byte -= 1; }
|
|
294
|
+
if (byte < 0) throw new LifecyclePlanningError("HOOK_SALT_EXHAUSTED", "No valid hook address remains in the uint256 salt range");
|
|
295
|
+
saltBytes[byte] = saltBytes[byte]! + 1;
|
|
296
|
+
}
|
|
297
|
+
const digest = firstDigest ?? keccak256(preimage, "bytes");
|
|
298
|
+
firstDigest = undefined;
|
|
299
|
+
batchAttempts += 1;
|
|
300
|
+
// Check all fourteen permission bits without checksumming/materializing
|
|
301
|
+
// every rejected address or allocating a progress object per attempt.
|
|
302
|
+
if ((((digest[30]! << 8) | digest[31]!) & mask) === permissions) {
|
|
303
|
+
attempts += BigInt(batchAttempts);
|
|
304
|
+
const result = { salt: toHex(saltBytes), predictedHook: poolBoundHookAddress(digest) };
|
|
305
|
+
onProgress?.({ attempts, ...result });
|
|
174
306
|
checkCancelled();
|
|
175
|
-
return
|
|
307
|
+
return result;
|
|
176
308
|
}
|
|
177
|
-
|
|
309
|
+
lastDigest = digest;
|
|
310
|
+
advanceSalt = true;
|
|
178
311
|
}
|
|
179
|
-
|
|
312
|
+
attempts += BigInt(batchAttempts);
|
|
313
|
+
if (onProgress !== undefined && lastDigest !== undefined) onProgress({ attempts, salt: toHex(saltBytes), predictedHook: poolBoundHookAddress(lastDigest) });
|
|
180
314
|
}
|
|
181
315
|
}
|
|
182
316
|
|