@black-market/sdk 0.3.1 → 0.4.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 +15 -1
- package/dist/launch.d.ts +1 -1
- package/dist/launch.d.ts.map +1 -1
- package/dist/launch.js +1 -1
- package/dist/launch.js.map +1 -1
- package/dist/lifecycle/abi.d.ts +43 -54
- package/dist/lifecycle/abi.d.ts.map +1 -1
- package/dist/lifecycle/abi.js +13 -9
- package/dist/lifecycle/abi.js.map +1 -1
- package/dist/lifecycle/markets.d.ts +1 -1
- package/dist/lifecycle/markets.d.ts.map +1 -1
- package/dist/lifecycle/markets.js +10 -8
- package/dist/lifecycle/markets.js.map +1 -1
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +8 -5
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/progress.d.ts +8 -1
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +29 -21
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/schema.d.ts +0 -28
- package/dist/lifecycle/schema.d.ts.map +1 -1
- package/dist/lifecycle/schema.js +3 -5
- package/dist/lifecycle/schema.js.map +1 -1
- package/docs/launch-lifecycle.md +45 -15
- package/package.json +1 -1
- package/src/launch.ts +1 -1
- package/src/lifecycle/abi.ts +13 -9
- package/src/lifecycle/markets.ts +9 -8
- package/src/lifecycle/planner.ts +7 -4
- package/src/lifecycle/progress.ts +31 -20
- package/src/lifecycle/schema.ts +4 -7
package/src/lifecycle/abi.ts
CHANGED
|
@@ -85,6 +85,10 @@ export const lifecycleRegistryAbi = [
|
|
|
85
85
|
{ name: "envelope", type: "tuple", components: launchEnvelopeV2Components },
|
|
86
86
|
{ name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, { name: "authorization", type: "bytes" },
|
|
87
87
|
], outputs: [] },
|
|
88
|
+
{ type: "function", name: "registerAbyssProfile", stateMutability: "nonpayable", inputs: [
|
|
89
|
+
{ name: "variant", type: "uint8" },
|
|
90
|
+
{ name: "registration", type: "tuple", components: profileRegistrationV1Components },
|
|
91
|
+
], outputs: [] },
|
|
88
92
|
{ type: "function", name: "authorPayout", stateMutability: "view", inputs: [{ type: "address" }], outputs: [{ type: "address" }] },
|
|
89
93
|
{ type: "function", name: "setAuthorPayout", stateMutability: "nonpayable", inputs: [{ name: "authorId", type: "address" }, { name: "payout", type: "address" }], outputs: [] },
|
|
90
94
|
{ type: "function", name: "authorHubCount", stateMutability: "view", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }] },
|
|
@@ -108,7 +112,7 @@ export const lifecycleAdapterAbi = [
|
|
|
108
112
|
{ type: "function", name: "readPosition", stateMutability: "view", inputs: [{ type: "tuple", components: positionIdentityV1Components }], outputs: [{ type: "uint128" }, { type: "address" }] },
|
|
109
113
|
] as const;
|
|
110
114
|
|
|
111
|
-
const
|
|
115
|
+
const v4AdapterMetadataAbi = [
|
|
112
116
|
...lifecycleAdapterAbi,
|
|
113
117
|
{ type: "function", name: "PROFILE_ID", stateMutability: "view", inputs: [], outputs: [{ type: "bytes32" }] },
|
|
114
118
|
{ type: "function", name: "CONFIG_SCHEMA", stateMutability: "view", inputs: [], outputs: [{ type: "bytes32" }] },
|
|
@@ -121,8 +125,8 @@ const reviewedV4AdapterMetadataAbi = [
|
|
|
121
125
|
{ type: "function", name: "collectorFactory", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
122
126
|
{ type: "function", name: "hookDeployer", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
123
127
|
] as const;
|
|
124
|
-
export const
|
|
125
|
-
...
|
|
128
|
+
export const sharedMarketAdapterV1Abi = [
|
|
129
|
+
...v4AdapterMetadataAbi,
|
|
126
130
|
{ type: "constructor", stateMutability: "nonpayable", inputs: [
|
|
127
131
|
{ name: "core_", type: "address" }, { name: "manager_", type: "address" }, { name: "root_", type: "address" },
|
|
128
132
|
{ name: "locker_", type: "address" }, { name: "helper_", type: "address" }, { name: "registry_", type: "address" },
|
|
@@ -136,8 +140,8 @@ export const abyssLifecycleAdapterAbi = [
|
|
|
136
140
|
{ type: "function", name: "profileId", stateMutability: "view", inputs: [{ type: "uint8" }], outputs: [{ type: "bytes32" }] },
|
|
137
141
|
{ type: "function", name: "factory", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
138
142
|
] as const;
|
|
139
|
-
export const
|
|
140
|
-
...
|
|
143
|
+
export const poolMarketAdapterV1Abi = [
|
|
144
|
+
...v4AdapterMetadataAbi,
|
|
141
145
|
{ type: "constructor", stateMutability: "nonpayable", inputs: [
|
|
142
146
|
{ name: "core_", type: "address" }, { name: "manager_", type: "address" }, { name: "oracleFactory_", type: "address" },
|
|
143
147
|
{ name: "locker_", type: "address" }, { name: "deployer_", type: "address" }, { name: "helper_", type: "address" },
|
|
@@ -151,7 +155,7 @@ export const reviewedPoolBoundV4MarketAdapterV1Abi = [
|
|
|
151
155
|
] },
|
|
152
156
|
] as const;
|
|
153
157
|
const boundParametersInput = { name: "parameters", type: "tuple", components: poolBoundHookParametersV1Components } as const;
|
|
154
|
-
export const
|
|
158
|
+
export const poolHookDeployerV1Abi = [
|
|
155
159
|
{ type: "constructor", stateMutability: "nonpayable", inputs: [{ name: "creationCode", type: "bytes" }] },
|
|
156
160
|
{ type: "function", name: "creationCodeHash", stateMutability: "view", inputs: [], outputs: [{ type: "bytes32" }] },
|
|
157
161
|
{ type: "function", name: "codeChunk0", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
@@ -162,7 +166,7 @@ export const reviewedPoolBoundHookDeployerV1Abi = [
|
|
|
162
166
|
{ type: "function", name: "validHookAddress", stateMutability: "pure", inputs: [{ type: "address" }], outputs: [{ type: "bool" }] },
|
|
163
167
|
{ type: "function", name: "deploy", stateMutability: "nonpayable", inputs: [boundParametersInput, { name: "salt", type: "bytes32" }], outputs: [{ name: "hook", type: "address" }] },
|
|
164
168
|
] as const;
|
|
165
|
-
export const
|
|
169
|
+
export const sharedHookDeployerV1Abi = [
|
|
166
170
|
{ type: "constructor", stateMutability: "nonpayable", inputs: [{ name: "creationCode", type: "bytes" }] },
|
|
167
171
|
{ type: "function", name: "creationCodeHash", stateMutability: "view", inputs: [], outputs: [{ type: "bytes32" }] },
|
|
168
172
|
{ type: "function", name: "codeChunk0", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
@@ -219,14 +223,14 @@ export const lifecycleV4LockerAbi = [
|
|
|
219
223
|
{ type: "function", name: "positionCount", stateMutability: "view", inputs: [{ type: "bytes32" }], outputs: [{ type: "uint256" }] },
|
|
220
224
|
{ type: "function", name: "isSealed", stateMutability: "view", inputs: [{ type: "bytes32" }], outputs: [{ type: "bool" }] },
|
|
221
225
|
] as const;
|
|
222
|
-
export const
|
|
226
|
+
export const poolFeeCollectorFactoryV1Abi = [
|
|
223
227
|
{ type: "function", name: "collectorDeployer", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
224
228
|
{ type: "function", name: "poolBoundHookParameters", stateMutability: "view", inputs: [
|
|
225
229
|
{ name: "registrar", type: "address" }, { name: "token", type: "address" }, { name: "market", type: "tuple", components: lifecycleMarketComponents },
|
|
226
230
|
], outputs: [boundParametersInput, { name: "salt", type: "bytes32" }] },
|
|
227
231
|
{ type: "function", name: "decodeAndValidate", stateMutability: "view", inputs: [{ type: "address" }, { type: "address" }, { type: "tuple", components: lifecycleMarketComponents }], outputs: [{ type: "tuple", components: v4LifecycleMarketComponents }] },
|
|
228
232
|
{ type: "function", name: "poolBoundDeploymentMetadata", stateMutability: "view", inputs: [{ type: "address" }, { type: "address" }, { type: "tuple", components: lifecycleMarketComponents }], outputs: [{ type: "address" }, { type: "bytes32" }, { type: "bytes32" }, { type: "address" }] },
|
|
229
|
-
{ type: "function", name: "
|
|
233
|
+
{ type: "function", name: "dependencyDigest", stateMutability: "view", inputs: [{ type: "address" }], outputs: [{ type: "bytes32" }] },
|
|
230
234
|
{ type: "function", name: "create", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "address" }, { type: "tuple", components: lifecycleV4PoolKeyComponents }, { type: "uint256" }], outputs: [{ type: "address" }] },
|
|
231
235
|
] as const;
|
|
232
236
|
export const lifecycleDirectoryAbi = [
|
package/src/lifecycle/markets.ts
CHANGED
|
@@ -39,7 +39,7 @@ export function decodeV4LifecycleMarketConfig(encoded: Hex): V4LifecycleMarketCo
|
|
|
39
39
|
export type V4PoolBoundLifecycleMarketConfig = Omit<V4LifecycleMarketConfig, "version"> & { version: 5; hookSalt: Hex };
|
|
40
40
|
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
41
|
export const V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint16,uint24,int24,uint160,uint24,uint8,uint8,address,bool,bytes32,bytes32,bytes32,bytes32,address,uint16,(int24,int24,uint128,bytes32,uint256)[])"));
|
|
42
|
-
export const V4_POOL_BOUND_MARKET_ECONOMICS_DOMAIN = keccak256(stringToHex("black-market.
|
|
42
|
+
export const V4_POOL_BOUND_MARKET_ECONOMICS_DOMAIN = keccak256(stringToHex("black-market.pool-bound-market-economics.v1"));
|
|
43
43
|
export const V4_LIFECYCLE_HOOK_PERMISSION_MASK = 0x3fffn;
|
|
44
44
|
export const V4_LIFECYCLE_HOOK_PERMISSIONS = 0x1afcn;
|
|
45
45
|
export const poolBoundV4LifecycleMarketComponents = [
|
|
@@ -65,7 +65,7 @@ export function decodePoolBoundV4LifecycleMarketConfig(encoded: Hex): V4PoolBoun
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/** Exact pending-source admission terms; author identity is never the live payout address. */
|
|
68
|
-
export function
|
|
68
|
+
export function validateV4LifecycleMarket(options: {
|
|
69
69
|
market: MarketConfigV1; config: V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig; profile: LifecycleProfile; token: Address;
|
|
70
70
|
}): void {
|
|
71
71
|
const { market, config, profile, token } = options;
|
|
@@ -83,19 +83,20 @@ export function validateReviewedV4LifecycleMarket(options: {
|
|
|
83
83
|
config.developerBeneficiary.toLowerCase() !== terms.beneficiary.toLowerCase() ||
|
|
84
84
|
BigInt(config.developerBeneficiary) === 0n || config.developerBeneficiary.toLowerCase() === token.toLowerCase() ||
|
|
85
85
|
config.developerBeneficiary.toLowerCase() === market.quoteAsset.toLowerCase()) {
|
|
86
|
-
throw new LifecyclePlanningError("
|
|
86
|
+
throw new LifecyclePlanningError("PROFILE_TERMS_MISMATCH", "Market must explicitly bind the admitted profile, frozen terms and stable author identity");
|
|
87
87
|
}
|
|
88
88
|
if (!Number.isInteger(config.developerFeeBps) || config.developerFeeBps < 0 ||
|
|
89
89
|
config.developerFeeBps > envelope.maximumDeveloperFeeBps || config.developerFeeBps > terms.maximumDeveloperFeeBps ||
|
|
90
90
|
config.developerFeeBps > protocolCeiling) throw new LifecyclePlanningError("DEVELOPER_FEE_CEILING", "Explicit developer fee exceeds the reviewed or protocol ceiling");
|
|
91
91
|
const b = envelope.bounds;
|
|
92
|
+
if (config.oracleConfigId.toLowerCase() === zeroHash) throw new LifecyclePlanningError("INVALID_ORACLE_CONFIG", "Market oracle configuration must be nonzero");
|
|
92
93
|
if (config.treasury.toLowerCase() !== envelope.protocolTreasury.toLowerCase() ||
|
|
93
|
-
config.protocolFeeDenominator !== envelope.protocolFeeDenominator || config.hookFeePips
|
|
94
|
-
|
|
94
|
+
config.protocolFeeDenominator !== envelope.protocolFeeDenominator || !Number.isInteger(config.hookFeePips) || config.hookFeePips < 0 || config.hookFeePips >= 1000000 ||
|
|
95
|
+
!Number.isInteger(config.lpFeePips) || config.lpFeePips < 0 || config.lpFeePips >= 1000000 ||
|
|
96
|
+
!Number.isInteger(config.feeMode) || config.feeMode < 0 || config.feeMode > 1 || (b.feeModeFlags & (1 << config.feeMode)) === 0 ||
|
|
95
97
|
config.tickSpacing < b.minimumTickSpacing || config.tickSpacing > b.maximumTickSpacing ||
|
|
96
|
-
config.positions.length === 0 || config.positions.length > b.maximumPositions
|
|
97
|
-
|
|
98
|
-
throw new LifecyclePlanningError("REVIEWED_BOUNDS_MISMATCH", "Market economics exceed or differ from the exact admitted envelope bounds");
|
|
98
|
+
config.positions.length === 0 || config.positions.length > b.maximumPositions) {
|
|
99
|
+
throw new LifecyclePlanningError("PROFILE_BOUNDS_MISMATCH", "Market economics exceed or differ from the exact admitted envelope bounds");
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
package/src/lifecycle/planner.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { encodeAbiParameters, encodeFunctionData, keccak256, toHex, zeroAddress, type Address, type Hex } from "viem";
|
|
2
2
|
import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleErc20Abi, lifecycleFundingEscrowAbi, lifecycleRegistryAbi } from "./abi.js";
|
|
3
3
|
import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecycleMode, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LifecycleVenue, LIFECYCLE_ERC404_CAPABILITY, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, LIFECYCLE_MAX_ERC404_SUPPLY, LIFECYCLE_MULTI_POSITION_CAPABILITY, LIFECYCLE_REQUIRED_CAPABILITIES, marketIdentityV1Components, parseLaunchPlan, serializeLaunchPlan, type LaunchPlanV1, type MarketIdentityV1 } from "./schema.js";
|
|
4
|
-
import { ABYSS_LIFECYCLE_CONFIG_SCHEMA, readLaunchProgress, readLifecycleProfiles, readPoolBoundHookDeployment, validateLifecycleMarketIdentity } from "./progress.js";
|
|
5
|
-
import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, hasLifecycleV4HookPermissions, minePoolBoundHookSalt, predictPoolBoundHookAddress,
|
|
4
|
+
import { ABYSS_LIFECYCLE_CONFIG_SCHEMA, readLaunchProgress, readLifecycleProfiles, readPoolBoundHookDeployment, validateLifecycleMarketIdentity, validateV4LifecycleOracle } from "./progress.js";
|
|
5
|
+
import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, hasLifecycleV4HookPermissions, minePoolBoundHookSalt, predictPoolBoundHookAddress, validateV4LifecycleMarket, V4_LIFECYCLE_CONFIG_SCHEMA, V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA, type V4LifecycleMarketConfig, type V4PoolBoundLifecycleMarketConfig } from "./markets.js";
|
|
6
6
|
import { assertLifecycleBlock, lifecycleRpc, readLifecycleBlock, readLifecycleContract, resolveLifecycleLimits, rpcHex, rpcQuantity } from "./rpc.js";
|
|
7
7
|
import { simulateLaunchTransactions } from "./simulation.js";
|
|
8
8
|
import { LifecyclePlanningError, type BuildNextTransactionOptions, type CanonicalLaunchProgress, type LifecycleBlock, type LifecycleFundingPrerequisite, type LifecyclePoolBoundHookDeployment, type LifecycleProfile, type LifecycleRpcClient, type LifecycleSimulation, type LifecycleTransaction, type PlannedLaunch, type PlanLaunchOptions, type PoolBoundLifecyclePreparationProgress, type SimulateLaunchPlanOptions } from "./types.js";
|
|
@@ -40,7 +40,7 @@ function validatePlanShape(plan: LaunchPlanV1): void {
|
|
|
40
40
|
if (market.configVersion === 4) config = decodeV4LifecycleMarketConfig(market.config);
|
|
41
41
|
else if (market.configVersion === 5) config = decodePoolBoundV4LifecycleMarketConfig(market.config);
|
|
42
42
|
if (config !== undefined) {
|
|
43
|
-
if (config.profileId.toLowerCase() !== market.profileId.toLowerCase()) throw new LifecyclePlanningError("
|
|
43
|
+
if (config.profileId.toLowerCase() !== market.profileId.toLowerCase()) throw new LifecyclePlanningError("PROFILE_TERMS_MISMATCH", "Inner config profile differs from the committed market profile");
|
|
44
44
|
const quote = market.quoteAsset.toLowerCase();
|
|
45
45
|
if (v4Quotes.has(quote)) throw new LifecyclePlanningError("DUPLICATE_V4_QUOTE", "Only one V4 market per quote asset is allowed across shared and pool-bound offerings; put multiple positions in that market");
|
|
46
46
|
v4Quotes.add(quote);
|
|
@@ -137,7 +137,10 @@ async function validatePendingInputs(client: LifecycleRpcClient, planned: Planne
|
|
|
137
137
|
const schema = profile.registration.configSchema.toLowerCase();
|
|
138
138
|
const config = schema === V4_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() ? decodeV4LifecycleMarketConfig(market.config) : schema === V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() ? decodePoolBoundV4LifecycleMarketConfig(market.config) : schema === ABYSS_LIFECYCLE_CONFIG_SCHEMA.toLowerCase() ? decodeAbyssLifecycleMarketConfig(market.config) : undefined;
|
|
139
139
|
if (config === undefined) throw new LifecyclePlanningError("UNSUPPORTED_SCHEMA", `Unsupported lifecycle config schema ${profile.registration.configSchema}`);
|
|
140
|
-
if ("developerFeeBps" in config)
|
|
140
|
+
if ("developerFeeBps" in config) {
|
|
141
|
+
validateV4LifecycleMarket({ market, config, profile, token: planned.predictedToken });
|
|
142
|
+
if (config.version === 4) await validateV4LifecycleOracle({ client, envelope: profile.envelope!, oracleConfigId: config.oracleConfigId, block });
|
|
143
|
+
}
|
|
141
144
|
if (config.positions.length === 0 || config.positions.length > 32 || positionCount + config.positions.length > 32) throw new LifecyclePlanningError("INVALID_POSITION_COUNT", "A launch commits at most 32 positive positions across every market/venue");
|
|
142
145
|
positionCount += config.positions.length;
|
|
143
146
|
const rewards = progress.canonical.rewards === zeroAddress && plan.token.rewardMode === LifecycleRewardMode.Dividends ? planned.predictedToken : progress.canonical.rewards;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { concatHex, decodeFunctionData, encodeAbiParameters, encodeFunctionData, keccak256, stringToHex, toHex, zeroAddress, zeroHash, type Address, type Hash, type Hex } from "viem";
|
|
2
|
-
import { abyssLifecycleAdapterAbi, launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleOracleFactoryAbi, lifecycleRegistryAbi,
|
|
3
|
-
import { decodePoolBoundV4LifecycleMarketConfig, encodePoolBoundHookParameters, hasLifecycleV4HookPermissions, hashPoolBoundV4MarketCommitment, poolBoundHookInitCodeHash, predictPoolBoundHookAddress,
|
|
2
|
+
import { abyssLifecycleAdapterAbi, launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleOracleFactoryAbi, lifecycleRegistryAbi, poolFeeCollectorFactoryV1Abi, lifecycleV4HookAbi, lifecycleV4LockerAbi, poolHookDeployerV1Abi, sharedHookDeployerV1Abi, poolBoundLaunchFeeHookV1Abi, poolMarketAdapterV1Abi, sharedMarketAdapterV1Abi } from "./abi.js";
|
|
3
|
+
import { decodePoolBoundV4LifecycleMarketConfig, encodePoolBoundHookParameters, hasLifecycleV4HookPermissions, hashPoolBoundV4MarketCommitment, poolBoundHookInitCodeHash, predictPoolBoundHookAddress, validateV4LifecycleMarket, V4_LIFECYCLE_CONFIG_SCHEMA, V4_POOL_BOUND_LIFECYCLE_CONFIG_SCHEMA, type PoolBoundHookParametersV1 } from "./markets.js";
|
|
4
4
|
import { hashLaunchIdentity, hashLaunchPlan, hashLaunchBounds, hashLaunchDependencies, hashLifecycleProfile, launchProgressV1Components, LifecycleMode, LifecyclePhase, LifecycleVenue, LIFECYCLE_REQUIRED_CAPABILITIES, LIFECYCLE_ERC404_CAPABILITY, LIFECYCLE_MULTI_POSITION_CAPABILITY, type AdapterRegistrationV1, type LaunchPlanV1, type LaunchProgressV1, type MarketIdentityV1, type MarketLiveStateV1, type PreparedMarketV1, type ProfileRegistrationV1, type ProfileTopologyV1, type LaunchEnvelopeV2, type LifecycleDeveloperTerms } from "./schema.js";
|
|
5
5
|
import { assertLifecycleBlock, lifecycleRpc, readLifecycleBlock, readLifecycleContract, readLifecycleProfileTopology, rpcHex, rpcObject, rpcQuantity } 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";
|
|
@@ -52,14 +52,14 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
|
|
|
52
52
|
hashLaunchBounds(envelope.bounds).toLowerCase() !== envelope.configBoundsDigest.toLowerCase() ||
|
|
53
53
|
envelope.capabilities !== registration.capabilities || envelope.capabilities !== adapter.capabilities ||
|
|
54
54
|
(envelope.capabilities & ~(LIFECYCLE_REQUIRED_CAPABILITIES | LIFECYCLE_ERC404_CAPABILITY | LIFECYCLE_MULTI_POSITION_CAPABILITY)) !== 0n) {
|
|
55
|
-
throw new LifecyclePlanningError("
|
|
55
|
+
throw new LifecyclePlanningError("PROFILE_ENVELOPE", "Frozen reviewed profile identity, bounds, capabilities or economic version differs from registry admission");
|
|
56
56
|
}
|
|
57
57
|
const b = envelope.bounds;
|
|
58
|
-
if (b.
|
|
58
|
+
if (b.minimumTickSpacing < 1 ||
|
|
59
59
|
b.maximumTickSpacing > 32767 || b.minimumTickSpacing > b.maximumTickSpacing || b.maximumPositions < 1 || b.maximumPositions > 32 ||
|
|
60
60
|
b.maximumOracleCardinality < 2 || b.maximumOracleCardinality > 4096 || b.feeModeFlags === 0 || (b.feeModeFlags & ~3) !== 0 ||
|
|
61
|
-
envelope.protocolTreasury === zeroAddress || (envelope.protocolFeeDenominator !== 0 && (envelope.protocolFeeDenominator < 4 || envelope.protocolFeeDenominator > 10))) throw new LifecyclePlanningError("
|
|
62
|
-
const abi = bound ?
|
|
61
|
+
envelope.protocolTreasury === zeroAddress || (envelope.protocolFeeDenominator !== 0 && (envelope.protocolFeeDenominator < 4 || envelope.protocolFeeDenominator > 10))) throw new LifecyclePlanningError("PROFILE_BOUNDS", "Registry envelope has unsupported executable bounds");
|
|
62
|
+
const abi = bound ? poolMarketAdapterV1Abi : sharedMarketAdapterV1Abi;
|
|
63
63
|
for (const [getter, expected] of [["PROFILE_ID", id], ["CONFIG_SCHEMA", registration.configSchema], ["implementationRegistry", registry]] as const) {
|
|
64
64
|
const actual = await readLifecycleContract<Hex>(client, adapter.implementation, abi, getter, [], block);
|
|
65
65
|
if (actual.toLowerCase() !== expected.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed adapter metadata differs from registry certification");
|
|
@@ -77,12 +77,12 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
|
|
|
77
77
|
[g.hookDeployer, g.hookDeployerCodeHash]] as const) {
|
|
78
78
|
if (keccak256(await readDependencyCode(client, address, block)).toLowerCase() !== expectedHash.toLowerCase()) throw new LifecyclePlanningError("PROFILE_CODE_HASH", "Live runtime differs from the frozen reviewed graph hash");
|
|
79
79
|
}
|
|
80
|
-
const collectorDeployer = await readLifecycleContract<Address>(client, g.collectorFactory,
|
|
80
|
+
const collectorDeployer = await readLifecycleContract<Address>(client, g.collectorFactory, poolFeeCollectorFactoryV1Abi, "collectorDeployer", [], block);
|
|
81
81
|
const launcher = await readLifecycleContract<Address>(client, g.locker, lifecycleV4LockerAbi, "launcher", [], block);
|
|
82
82
|
const lockerManager = await readLifecycleContract<Address>(client, g.locker, lifecycleV4LockerAbi, "poolManager", [], block);
|
|
83
83
|
if (collectorDeployer.toLowerCase() !== g.collectorDeployer.toLowerCase() || launcher.toLowerCase() !== adapter.implementation.toLowerCase() ||
|
|
84
84
|
lockerManager.toLowerCase() !== g.manager.toLowerCase() || g.locker.toLowerCase() === g.manager.toLowerCase() || g.locker.toLowerCase() === g.hookRoot.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Reviewed collector or locker authority differs from the frozen graph");
|
|
85
|
-
const deployerAbi = shared ?
|
|
85
|
+
const deployerAbi = shared ? sharedHookDeployerV1Abi : poolHookDeployerV1Abi;
|
|
86
86
|
const creationCodeHash = await readLifecycleContract<Hex>(client, g.hookDeployer, deployerAbi, "creationCodeHash", [], block);
|
|
87
87
|
const chunk0 = await readLifecycleContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk0", [], block);
|
|
88
88
|
const chunk1 = await readLifecycleContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk1", [], block);
|
|
@@ -101,7 +101,7 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
|
|
|
101
101
|
const recordedHash = await readLifecycleContract<Hex>(client, g.hookDeployer, deployerAbi, "deployedCodeHash", [g.hookRoot], block);
|
|
102
102
|
const initCodeHash = keccak256(concatHex([creationCode, encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "address" }], [g.manager, adapter.implementation, g.oracleFactory])]));
|
|
103
103
|
const prediction = predictPoolBoundHookAddress({ deployer: g.hookDeployer, initCodeHash, salt: g.sharedHookSalt });
|
|
104
|
-
const remotePrediction = await readLifecycleContract<Address>(client, g.hookDeployer,
|
|
104
|
+
const remotePrediction = await readLifecycleContract<Address>(client, g.hookDeployer, sharedHookDeployerV1Abi, "predict", [g.manager, adapter.implementation, g.oracleFactory, g.sharedHookSalt], block);
|
|
105
105
|
if (recordedHash === zeroHash || recordedHash.toLowerCase() !== g.hookRuntimeCodeHash.toLowerCase() || keccak256(rootCode).toLowerCase() !== recordedHash.toLowerCase() ||
|
|
106
106
|
!hasLifecycleV4HookPermissions(g.hookRoot) || prediction.toLowerCase() !== g.hookRoot.toLowerCase() || remotePrediction.toLowerCase() !== prediction.toLowerCase()) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared root lacks exact typed-deployer runtime and constructor/salt provenance");
|
|
107
107
|
for (const [getter, expected] of [["registrar", adapter.implementation], ["poolManager", g.manager], ["oracleFactory", g.oracleFactory]] as const) {
|
|
@@ -110,12 +110,22 @@ async function certifyLifecycleProfile(client: LifecycleRpcClient, orchestrator:
|
|
|
110
110
|
if (await readLifecycleContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "REQUIRED_HOOK_FLAGS", [], block) !== 0x1afcn ||
|
|
111
111
|
await readLifecycleContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "ALL_HOOK_MASK", [], block) !== 0x3fffn) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Shared hook callbacks differ from admission");
|
|
112
112
|
} else if (g.hookRoot !== zeroAddress || g.hookRuntimeCodeHash !== zeroHash || g.sharedHookSalt !== zeroHash) throw new LifecyclePlanningError("PROFILE_DEPENDENCY", "Bound topology cannot reuse a shared root");
|
|
113
|
-
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("
|
|
114
|
-
const [move, cardinality] = await readLifecycleContract<readonly [number, number]>(client, g.oracleFactory, lifecycleOracleFactoryAbi, "oracleConfigs", [b.oracleConfigId], block);
|
|
115
|
-
if (move === 0 || move > 887272 || cardinality < 2 || cardinality > b.maximumOracleCardinality) throw new LifecyclePlanningError("REVIEWED_BOUNDS", "Reviewed oracle configuration exceeds the admitted bounds");
|
|
113
|
+
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");
|
|
116
114
|
return { topology, boundGraph: bound ? { manager: g.manager, oracleFactory: g.oracleFactory, locker: g.locker, collectorFactory: g.collectorFactory, deployer: g.hookDeployer, creationCode } : undefined };
|
|
117
115
|
}
|
|
118
116
|
|
|
117
|
+
/** Validate the market-selected registered oracle against the certified factory at the pinned block. */
|
|
118
|
+
export async function validateV4LifecycleOracle(options: {
|
|
119
|
+
client: LifecycleRpcClient; envelope: LaunchEnvelopeV2; oracleConfigId: Hex; block: LifecycleBlock;
|
|
120
|
+
}): Promise<void> {
|
|
121
|
+
const { client, envelope, oracleConfigId, block } = options;
|
|
122
|
+
if (oracleConfigId.toLowerCase() === zeroHash) throw new LifecyclePlanningError("INVALID_ORACLE_CONFIG", "Market oracle configuration must be nonzero");
|
|
123
|
+
const [move, cardinality] = await readLifecycleContract<readonly [number, number]>(client, envelope.graph.oracleFactory, lifecycleOracleFactoryAbi, "oracleConfigs", [oracleConfigId], block);
|
|
124
|
+
if (move <= 0 || move > 887272 || cardinality < 2 || cardinality > envelope.bounds.maximumOracleCardinality) {
|
|
125
|
+
throw new LifecyclePlanningError("INVALID_ORACLE_CONFIG", "Selected market oracle is unregistered or exceeds the admitted cardinality bounds");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
119
129
|
export async function readLifecycleProfiles(options: { client: LifecycleRpcClient; orchestrator: Address; profileIds?: readonly Hex[]; offset?: bigint; limit?: bigint }, pinnedBlock?: LifecycleBlock): Promise<LifecycleProfile[]> {
|
|
120
130
|
const block = pinnedBlock ?? await readLifecycleBlock(options.client);
|
|
121
131
|
const chainId = rpcQuantity(await lifecycleRpc(options.client, "eth_chainId"), "chain ID");
|
|
@@ -143,7 +153,7 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
|
|
|
143
153
|
developerTerms = { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled };
|
|
144
154
|
if (implementation.toLowerCase() !== adapter.implementation.toLowerCase() || beneficiary.toLowerCase() !== envelope.beneficiary.toLowerCase() ||
|
|
145
155
|
termsDigest.toLowerCase() !== envelope.termsDigest.toLowerCase() || maximumDeveloperFeeBps !== envelope.maximumDeveloperFeeBps ||
|
|
146
|
-
maximumDeveloperFeeBps > protocolMaximumDeveloperFeeBps) throw new LifecyclePlanningError("
|
|
156
|
+
maximumDeveloperFeeBps > protocolMaximumDeveloperFeeBps) throw new LifecyclePlanningError("PROFILE_TERMS", "Registry developer terms differ from the frozen reviewed envelope");
|
|
147
157
|
}
|
|
148
158
|
await certifyLifecycleProfile(options.client, options.orchestrator, registry, id, registration, adapter, topology, envelope, block, chainId);
|
|
149
159
|
const eligible = await readLifecycleContract<Address>(options.client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, topology.configVersion, LIFECYCLE_REQUIRED_CAPABILITIES], block);
|
|
@@ -175,11 +185,12 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
|
|
|
175
185
|
if (boundGraph === undefined) throw new LifecyclePlanningError("UNCERTIFIED_TOPOLOGY", "Market does not have a certified pool-bound deployer graph");
|
|
176
186
|
const token = await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "predictToken", [plan], block);
|
|
177
187
|
const config = decodePoolBoundV4LifecycleMarketConfig(market.config);
|
|
178
|
-
|
|
188
|
+
validateV4LifecycleMarket({ market, config, token, profile: {
|
|
179
189
|
id: market.profileId, registration, adapter, topology: recorded, envelope,
|
|
180
190
|
developerTerms: { adapter: implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled },
|
|
181
191
|
protocolMaximumDeveloperFeeBps, venueKind: "uniswap-v4", admitted: enabled,
|
|
182
192
|
} });
|
|
193
|
+
await validateV4LifecycleOracle({ client, envelope, oracleConfigId: config.oracleConfigId, block });
|
|
183
194
|
const expected: PoolBoundHookParametersV1 = {
|
|
184
195
|
poolManager: boundGraph.manager, registrar: adapter.implementation, oracleFactory: boundGraph.oracleFactory, core: plan.orchestrator,
|
|
185
196
|
liquidityLocker: boundGraph.locker, token, quoteCurrency: market.quoteAsset, lpFeePips: config.lpFeePips, tickSpacing: config.tickSpacing,
|
|
@@ -188,16 +199,16 @@ async function readPoolBoundHookDeploymentDetails(options: { client: LifecycleRp
|
|
|
188
199
|
marketCommitment: hashPoolBoundV4MarketCommitment({ chainId, core: plan.orchestrator, registrar: adapter.implementation, token, market }),
|
|
189
200
|
expectedPositionCount: config.positions.length,
|
|
190
201
|
};
|
|
191
|
-
const [parameters, parameterSalt] = await readLifecycleContract<readonly [PoolBoundHookParametersV1, Hex]>(client, boundGraph.collectorFactory,
|
|
192
|
-
const [deployer, initCodeHash, salt, predictedHook] = await readLifecycleContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation,
|
|
193
|
-
const remoteInitCodeHash = await readLifecycleContract<Hex>(client, boundGraph.deployer,
|
|
194
|
-
const remotePrediction = await readLifecycleContract<Address>(client, boundGraph.deployer,
|
|
202
|
+
const [parameters, parameterSalt] = await readLifecycleContract<readonly [PoolBoundHookParametersV1, Hex]>(client, boundGraph.collectorFactory, poolFeeCollectorFactoryV1Abi, "poolBoundHookParameters", [adapter.implementation, token, market], block);
|
|
203
|
+
const [deployer, initCodeHash, salt, predictedHook] = await readLifecycleContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block);
|
|
204
|
+
const remoteInitCodeHash = await readLifecycleContract<Hex>(client, boundGraph.deployer, poolHookDeployerV1Abi, "initCodeHash", [expected], block);
|
|
205
|
+
const remotePrediction = await readLifecycleContract<Address>(client, boundGraph.deployer, poolHookDeployerV1Abi, "predict", [expected, config.hookSalt], block);
|
|
195
206
|
const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected);
|
|
196
207
|
const localPrediction = predictPoolBoundHookAddress({ deployer: boundGraph.deployer, initCodeHash: localInitCodeHash, salt: config.hookSalt });
|
|
197
208
|
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");
|
|
198
209
|
const hookCode = rpcHex(await lifecycleRpc(client, "eth_getCode", [predictedHook, toHex(block.number)]), "bound hook code");
|
|
199
210
|
if (hookCode !== "0x") {
|
|
200
|
-
const deployedCodeHash = await readLifecycleContract<Hex>(client, deployer,
|
|
211
|
+
const deployedCodeHash = await readLifecycleContract<Hex>(client, deployer, poolHookDeployerV1Abi, "deployedCodeHash", [predictedHook], block);
|
|
201
212
|
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");
|
|
202
213
|
for (const [getter, expectedAddress] of [
|
|
203
214
|
["poolManager", expected.poolManager], ["registrar", expected.registrar], ["oracleFactory", expected.oracleFactory],
|
|
@@ -238,7 +249,7 @@ export async function buildPoolBoundHookDeploymentTransaction(options: { client:
|
|
|
238
249
|
if (!hasLifecycleV4HookPermissions(deployment.predictedHook)) throw new LifecyclePlanningError("INVALID_HOOK_BITS", "Finalize a salt with the exact lifecycle hook permissions before predeployment");
|
|
239
250
|
if (hashLaunchPlan(options.plan) !== commitment) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed while constructing hook deployment calldata");
|
|
240
251
|
await assertLifecycleBlock(options.client, block);
|
|
241
|
-
return { to: deployment.deployer, data: encodeFunctionData({ abi:
|
|
252
|
+
return { to: deployment.deployer, data: encodeFunctionData({ abi: poolHookDeployerV1Abi, functionName: "deploy", args: [parameters, deployment.salt] }), value: 0n, deployment };
|
|
242
253
|
}
|
|
243
254
|
|
|
244
255
|
export function validateLifecycleMarketIdentity(identity: MarketIdentityV1, plan: LaunchPlanV1, token: Address, marketIndex: number): void {
|
package/src/lifecycle/schema.ts
CHANGED
|
@@ -71,9 +71,8 @@ export type MarketLiveStateV1 = {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
export type LaunchBoundsV2 = {
|
|
74
|
-
|
|
74
|
+
minimumTickSpacing: number; maximumTickSpacing: number;
|
|
75
75
|
maximumPositions: number; maximumOracleCardinality: number; feeModeFlags: number;
|
|
76
|
-
externalLiquidityDisabled: boolean; oracleConfigId: Hex;
|
|
77
76
|
};
|
|
78
77
|
export type LaunchGraphV2 = {
|
|
79
78
|
manager: Address; hookRoot: Address; oracleFactory: Address; locker: Address; collectorFactory: Address;
|
|
@@ -96,11 +95,9 @@ export type SourceTermsV3 = {
|
|
|
96
95
|
maximumDeveloperFeeBps: number; developerFeeBps: number;
|
|
97
96
|
};
|
|
98
97
|
export const launchBoundsV2Components = [
|
|
99
|
-
{ name: "maximumHookFeePips", type: "uint24" }, { name: "maximumLpFeePips", type: "uint24" },
|
|
100
98
|
{ name: "minimumTickSpacing", type: "int24" }, { name: "maximumTickSpacing", type: "int24" },
|
|
101
99
|
{ name: "maximumPositions", type: "uint16" }, { name: "maximumOracleCardinality", type: "uint16" },
|
|
102
|
-
{ name: "feeModeFlags", type: "uint8" },
|
|
103
|
-
{ name: "oracleConfigId", type: "bytes32" },
|
|
100
|
+
{ name: "feeModeFlags", type: "uint8" },
|
|
104
101
|
] as const;
|
|
105
102
|
export const launchGraphV2Components = [
|
|
106
103
|
{ name: "manager", type: "address" }, { name: "hookRoot", type: "address" },
|
|
@@ -154,7 +151,7 @@ export function hashLifecycleProfile(envelope: LaunchEnvelopeV2): Hex {
|
|
|
154
151
|
return keccak256(encodeAbiParameters([
|
|
155
152
|
{ type: "bytes32" }, { type: "bytes32" }, { type: "bytes32" }, { type: "bytes32" }, { type: "bytes32" },
|
|
156
153
|
{ type: "uint8" }, { type: "uint32" }, { type: "uint32" }, { type: "address" }, { type: "uint16" }, { type: "uint64" },
|
|
157
|
-
], [keccak256(stringToHex("black-market.
|
|
154
|
+
], [keccak256(stringToHex("black-market.launch-profile.v2")), envelope.artifactDigest,
|
|
158
155
|
envelope.reviewManifestDigest, envelope.configBoundsDigest, envelope.termsDigest, envelope.topology,
|
|
159
156
|
envelope.configVersion, envelope.economicVersion, envelope.beneficiary, envelope.maximumDeveloperFeeBps, envelope.capabilities]));
|
|
160
157
|
}
|
|
@@ -170,7 +167,7 @@ export function hashLaunchDependencies(options: {
|
|
|
170
167
|
{ type: "address" }, { type: "bytes32" }, { type: "address" }, { type: "bytes32" },
|
|
171
168
|
{ type: "address" }, { type: "bytes32" }, { type: "address" }, { type: "bytes32" }, { type: "bytes32" },
|
|
172
169
|
{ type: "address" }, { type: "bytes32" }, { type: "address" }, { type: "bytes32" },
|
|
173
|
-
], [keccak256(stringToHex("black-market.
|
|
170
|
+
], [keccak256(stringToHex("black-market.v4-dependencies.v2")), chainId, core, g.coreCodeHash,
|
|
174
171
|
registry, registrar, g.manager, g.managerCodeHash, g.hookRoot, g.hookRuntimeCodeHash,
|
|
175
172
|
g.oracleFactory, g.oracleFactoryCodeHash, g.locker, g.lockerCodeHash, g.collectorFactory, g.collectorFactoryCodeHash,
|
|
176
173
|
g.collectorDeployer, g.collectorDeployerCodeHash, g.hookDeployer, g.hookDeployerCodeHash,
|