@black-market/sdk 0.7.1 → 0.8.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 +58 -22
- package/dist/launch.d.ts +31 -5
- package/dist/launch.d.ts.map +1 -1
- package/dist/launch.js +71 -11
- package/dist/launch.js.map +1 -1
- package/dist/lifecycle/abi.d.ts +18 -0
- package/dist/lifecycle/abi.d.ts.map +1 -1
- package/dist/lifecycle/abi.js +4 -0
- package/dist/lifecycle/abi.js.map +1 -1
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +2 -0
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/planner.d.ts +1 -1
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +58 -10
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/progress.js +35 -35
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.d.ts +10 -4
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +137 -16
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/types.d.ts +20 -1
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/docs/launch-lifecycle.md +396 -64
- package/examples/.env.example +22 -2
- package/examples/README.md +315 -87
- package/examples/{launch-erc20-abyss.mjs → launch-erc20-abyss.ts} +1 -1
- package/examples/{launch-erc20-burn-mixed.mjs → launch-erc20-burn-mixed.ts} +1 -1
- package/examples/launch-erc20-depth-v4.ts +7 -0
- package/examples/{launch-erc20-dividends-abyss.mjs → launch-erc20-dividends-abyss.ts} +1 -1
- package/examples/launch-erc20-dividends-multiquote-mixed.ts +8 -0
- package/examples/launch-erc20-multi-quote.ts +8 -0
- package/examples/{launch-erc20-staking-v4.mjs → launch-erc20-staking-v4.ts} +1 -1
- package/examples/{launch-erc20-v4.mjs → launch-erc20-v4.ts} +1 -1
- package/examples/{launch-erc404-abyss.mjs → launch-erc404-abyss.ts} +1 -1
- package/examples/{launch-erc404-dividends-mixed.mjs → launch-erc404-dividends-mixed.ts} +1 -1
- package/examples/{launch-erc404-v4.mjs → launch-erc404-v4.ts} +1 -1
- package/examples/launch-example-kyber.ts +204 -0
- package/examples/launch-example-support.ts +876 -0
- package/examples/launch-example.ts +1163 -0
- package/examples/{launch-lifecycle-smoke.mjs → launch-lifecycle-smoke.ts} +1 -1
- package/examples/{launch.mjs → launch.ts} +1 -1
- package/package.json +2 -2
- package/src/launch.ts +81 -14
- package/src/lifecycle/abi.ts +4 -0
- package/src/lifecycle/index.ts +2 -0
- package/src/lifecycle/planner.ts +61 -12
- package/src/lifecycle/progress.ts +35 -35
- package/src/lifecycle/rpc.ts +140 -17
- package/src/lifecycle/types.ts +19 -1
- package/examples/launch-example-support.mjs +0 -393
- package/examples/launch-example.mjs +0 -633
- /package/examples/{generate-lifecycle-commitment-fixture.mjs → generate-lifecycle-commitment-fixture.ts} +0 -0
- /package/examples/{quickstart.mjs → quickstart.ts} +0 -0
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
simulateLaunchPlan, isPoolBoundV4ConfigVersion,
|
|
13
13
|
} from "@black-market/sdk/lifecycle";
|
|
14
14
|
const [manifestFile, plansFile] = process.argv.slice(2);
|
|
15
|
-
if (!manifestFile || !plansFile) throw new Error("Usage: node examples/launch-lifecycle-smoke.
|
|
15
|
+
if (!manifestFile || !plansFile) throw new Error("Usage: node examples/launch-lifecycle-smoke.ts manifest.json sdk-plans.json");
|
|
16
16
|
const manifest = JSON.parse(await readFile(manifestFile, "utf8"));
|
|
17
17
|
const exported = JSON.parse(await readFile(plansFile, "utf8"));
|
|
18
18
|
const rpcUrl = process.env.LAUNCH_LIFECYCLE_RPC_URL;
|
|
@@ -9,7 +9,7 @@ const args = process.argv.slice(2);
|
|
|
9
9
|
const option = (name) => { const index = args.indexOf(name); return index === -1 ? undefined : args[index + 1]; };
|
|
10
10
|
const planFile = option("--plan"), rpcUrl = option("--rpc"), mode = option("--mode");
|
|
11
11
|
if (!planFile || !rpcUrl || !["atomic", "staged"].includes(mode)) {
|
|
12
|
-
throw new Error("Usage: node examples/launch.
|
|
12
|
+
throw new Error("Usage: node examples/launch.ts --plan plan.json --rpc RPC_URL --mode atomic|staged [--limits current-limits.json] [--receipts receipts.json] [--confirmations N] [--fork-rpc LOOPBACK_RPC_URL] [--mine]");
|
|
13
13
|
}
|
|
14
14
|
const client = createPublicClient({ transport: http(rpcUrl) });
|
|
15
15
|
const plan = parseLaunchPlan(await readFile(planFile, "utf8"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@black-market/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "TypeScript SDK for Abyss token launches, swaps, and lending.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsc -p tsconfig.json",
|
|
41
41
|
"dev": "tsc -p tsconfig.json --watch",
|
|
42
|
-
"examples": "pnpm build && node examples/quickstart.
|
|
42
|
+
"examples": "pnpm build && node examples/quickstart.ts",
|
|
43
43
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
44
44
|
"prepublishOnly": "pnpm build",
|
|
45
45
|
"test": "pnpm test:unit",
|
package/src/launch.ts
CHANGED
|
@@ -43,9 +43,9 @@ export type LaunchPositionRangesInput = {
|
|
|
43
43
|
/** Source-defined Black Market inventory weights, widths and gaps. */
|
|
44
44
|
distributionPreset: "early-scarcity" | "staircase" | "smooth-ramp";
|
|
45
45
|
positionCount?: number;
|
|
46
|
-
/** Defaults to source-gaps; adjacent removes
|
|
47
|
-
|
|
48
|
-
/** Defaults to
|
|
46
|
+
/** Defaults to source-gaps for Staircase/Early Scarcity; adjacent removes their gaps. */
|
|
47
|
+
positionGaps?: "source-gaps" | "adjacent";
|
|
48
|
+
/** Defaults to true; extends only the final band's far endpoint. */
|
|
49
49
|
extendFinalRangeToBoundary?: boolean;
|
|
50
50
|
};
|
|
51
51
|
|
|
@@ -78,6 +78,21 @@ export type LaunchPositionRanges = {
|
|
|
78
78
|
maxLiquidityPerTick: bigint;
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
+
export type LaunchTopologyPosition = {
|
|
82
|
+
tickLower: number; tickUpper: number;
|
|
83
|
+
} & ({ maxTokenAmount: bigint } | { tokenAmountMaximum: bigint });
|
|
84
|
+
|
|
85
|
+
export type LaunchTopologyCoverageInput = {
|
|
86
|
+
positions: readonly LaunchTopologyPosition[];
|
|
87
|
+
/** Canonical tick derived from the market's opening sqrtPriceX96. */
|
|
88
|
+
openingTick: number;
|
|
89
|
+
tickSpacing: number;
|
|
90
|
+
/** Existing launch convention: true means the launched token is token0. */
|
|
91
|
+
launchedTokenIsQuote: boolean;
|
|
92
|
+
/** Defaults to false; opening containment and the permanent tail remain required. */
|
|
93
|
+
allowInteriorGaps?: boolean;
|
|
94
|
+
};
|
|
95
|
+
|
|
81
96
|
export type LaunchBuySqrtPriceLimitInput = {
|
|
82
97
|
launchSqrtPriceX96: bigint;
|
|
83
98
|
launchedTokenIsQuote: boolean;
|
|
@@ -196,7 +211,7 @@ export function getSqrtRatioAtTick(tick: number): bigint {
|
|
|
196
211
|
return (ratio >> 32n) + (ratio % Q32 === 0n ? 0n : 1n);
|
|
197
212
|
}
|
|
198
213
|
|
|
199
|
-
function getTickAtSqrtRatio(sqrtPriceX96: bigint): number {
|
|
214
|
+
export function getTickAtSqrtRatio(sqrtPriceX96: bigint): number {
|
|
200
215
|
assertCanonicalSqrtPrice(sqrtPriceX96, "sqrtPriceX96");
|
|
201
216
|
|
|
202
217
|
let low = MIN_TICK;
|
|
@@ -242,6 +257,56 @@ function launchBand(
|
|
|
242
257
|
return { tickLower, tickUpper };
|
|
243
258
|
}
|
|
244
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Requires opening buy-side liquidity, continuous forward coverage and a funded
|
|
262
|
+
* permanent tail at the usable protocol boundary. Overlaps and adjacency are valid.
|
|
263
|
+
* Token1 containment includes the upper endpoint crossed by the first downward buy.
|
|
264
|
+
* Input positions are never reordered or mutated.
|
|
265
|
+
*/
|
|
266
|
+
export function assertLaunchTopologyCoverage(input: LaunchTopologyCoverageInput): void {
|
|
267
|
+
const { positions, openingTick, tickSpacing, launchedTokenIsQuote } = input;
|
|
268
|
+
if (!Number.isInteger(tickSpacing) || tickSpacing < 1 || tickSpacing > 32767) throw new Error("tickSpacing must be 1..32767");
|
|
269
|
+
if (typeof launchedTokenIsQuote !== "boolean") throw new Error("launchedTokenIsQuote must be a boolean");
|
|
270
|
+
const allowInteriorGaps = input.allowInteriorGaps ?? false;
|
|
271
|
+
if (typeof allowInteriorGaps !== "boolean") throw new Error("allowInteriorGaps must be a boolean");
|
|
272
|
+
getSqrtRatioAtTick(openingTick);
|
|
273
|
+
const band = launchBand(openingTick, tickSpacing, launchedTokenIsQuote);
|
|
274
|
+
const boundaryTick = launchedTokenIsQuote ? band.tickUpper : band.tickLower;
|
|
275
|
+
let containsOpening = false;
|
|
276
|
+
let hasPermanentTail = false;
|
|
277
|
+
for (const position of positions) {
|
|
278
|
+
const { tickLower, tickUpper } = position;
|
|
279
|
+
if (!Number.isInteger(tickLower) || !Number.isInteger(tickUpper) ||
|
|
280
|
+
tickLower % tickSpacing !== 0 || tickUpper % tickSpacing !== 0) {
|
|
281
|
+
throw new Error("TOPOLOGY_OFF_GRID_TICK: Every position endpoint must be spacing-aligned");
|
|
282
|
+
}
|
|
283
|
+
getSqrtRatioAtTick(tickLower);
|
|
284
|
+
getSqrtRatioAtTick(tickUpper);
|
|
285
|
+
if (tickLower >= tickUpper) throw new Error("TOPOLOGY_OPENING_GAP: Every position must have a nonempty tick range");
|
|
286
|
+
if (launchedTokenIsQuote ? tickLower <= openingTick && openingTick < tickUpper : tickLower < openingTick && openingTick <= tickUpper) {
|
|
287
|
+
containsOpening = true;
|
|
288
|
+
}
|
|
289
|
+
const tokenMaximum = "maxTokenAmount" in position ? position.maxTokenAmount : position.tokenAmountMaximum;
|
|
290
|
+
if ((launchedTokenIsQuote ? tickUpper : tickLower) === boundaryTick && tokenMaximum > 0n) hasPermanentTail = true;
|
|
291
|
+
}
|
|
292
|
+
if (!containsOpening) throw new Error("TOPOLOGY_OPENING_GAP: No opening buy-side band contains the launch tick");
|
|
293
|
+
const sorted = [...positions].sort(launchedTokenIsQuote
|
|
294
|
+
? (left, right) => left.tickLower - right.tickLower
|
|
295
|
+
: (left, right) => right.tickUpper - left.tickUpper);
|
|
296
|
+
let coveredTo = openingTick;
|
|
297
|
+
for (const position of sorted) {
|
|
298
|
+
const near = launchedTokenIsQuote ? position.tickLower : position.tickUpper;
|
|
299
|
+
const far = launchedTokenIsQuote ? position.tickUpper : position.tickLower;
|
|
300
|
+
if (launchedTokenIsQuote ? far <= coveredTo : far >= coveredTo) continue;
|
|
301
|
+
if (!allowInteriorGaps && (launchedTokenIsQuote ? near > coveredTo : near < coveredTo)) {
|
|
302
|
+
throw new Error(`TOPOLOGY_INTERIOR_GAP: Uncovered interval between ticks ${coveredTo} and ${near}`);
|
|
303
|
+
}
|
|
304
|
+
coveredTo = far;
|
|
305
|
+
if (coveredTo === boundaryTick) break;
|
|
306
|
+
}
|
|
307
|
+
if (!hasPermanentTail) throw new Error(`MISSING_PERMANENT_TAIL: A positive token allocation must reach tick ${boundaryTick}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
245
310
|
function liquidityForAmount0(amount0: bigint, sqrtLowerX96: bigint, sqrtUpperX96: bigint): bigint {
|
|
246
311
|
return (((amount0 * sqrtLowerX96) / Q96) * sqrtUpperX96) / (sqrtUpperX96 - sqrtLowerX96);
|
|
247
312
|
}
|
|
@@ -333,8 +398,8 @@ function apportionLaunchInventory(total: number, scores: readonly number[]): num
|
|
|
333
398
|
/**
|
|
334
399
|
* Source-defined one-sided Black Market presets, ordered toward higher launch-token
|
|
335
400
|
* prices. The first position starts at launch; later bands are contiguous or leave
|
|
336
|
-
* the preset's intentional gaps.
|
|
337
|
-
*
|
|
401
|
+
* the preset's intentional gaps. By default the final band reaches the usable
|
|
402
|
+
* protocol boundary; no equal-tick extrapolation is applied to earlier bands.
|
|
338
403
|
* Supply allocation across pools belongs to the caller: pass each pool's share.
|
|
339
404
|
* Budget caps floor all but the final allocation, which receives the remainder.
|
|
340
405
|
* Exact rational funding floors guarantee canonical rounded-up mint debits fit
|
|
@@ -356,14 +421,16 @@ export function deriveLaunchPositionRanges(input: LaunchPositionRangesInput): La
|
|
|
356
421
|
}
|
|
357
422
|
const positionCount = input.positionCount ?? 5;
|
|
358
423
|
if (!Number.isInteger(positionCount) || positionCount < 2 || positionCount > 16) throw new Error("positionCount must be 2..16");
|
|
359
|
-
const extendFinalRangeToBoundary = input.extendFinalRangeToBoundary ??
|
|
424
|
+
const extendFinalRangeToBoundary = input.extendFinalRangeToBoundary ?? true;
|
|
360
425
|
if (typeof extendFinalRangeToBoundary !== "boolean") throw new Error("extendFinalRangeToBoundary must be a boolean");
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
426
|
+
const positionGaps = input.positionGaps === undefined
|
|
427
|
+
? distributionPreset === "smooth-ramp" ? "adjacent" : "source-gaps"
|
|
428
|
+
: input.positionGaps;
|
|
429
|
+
if (positionGaps !== "source-gaps" && positionGaps !== "adjacent") {
|
|
430
|
+
throw new Error("positionGaps must be source-gaps or adjacent");
|
|
364
431
|
}
|
|
365
|
-
if (
|
|
366
|
-
throw new Error("
|
|
432
|
+
if (distributionPreset === "smooth-ramp" && positionGaps === "source-gaps") {
|
|
433
|
+
throw new Error("smooth-ramp positions are always adjacent");
|
|
367
434
|
}
|
|
368
435
|
const launchTick = getTickAtSqrtRatio(input.launchSqrtPriceX96);
|
|
369
436
|
if (launchTick % tickSpacing !== 0 || getSqrtRatioAtTick(launchTick) !== input.launchSqrtPriceX96) {
|
|
@@ -403,10 +470,10 @@ export function deriveLaunchPositionRanges(input: LaunchPositionRangesInput): La
|
|
|
403
470
|
: (input.tokenBudget * BigInt(weight)) / 10_000n;
|
|
404
471
|
if (allocatedTokenAmount <= 0n) throw new Error("Every position must have a positive allocated token cap");
|
|
405
472
|
allocated += allocatedTokenAmount;
|
|
406
|
-
if (index > 0) {
|
|
473
|
+
if (index > 0 && positionGaps === "source-gaps") {
|
|
407
474
|
if (distributionPreset === "early-scarcity") {
|
|
408
475
|
distance += 2 + Math.floor((4 * index) / (positionCount - 1));
|
|
409
|
-
} else if (distributionPreset === "staircase" &&
|
|
476
|
+
} else if (distributionPreset === "staircase" &&
|
|
410
477
|
Math.floor((index * 4) / positionCount) !== Math.floor(((index - 1) * 4) / positionCount)) {
|
|
411
478
|
distance += 3;
|
|
412
479
|
}
|
package/src/lifecycle/abi.ts
CHANGED
|
@@ -305,6 +305,8 @@ export const lifecycleV4LockerAbi = [
|
|
|
305
305
|
{ type: "function", name: "launcher", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
306
306
|
{ type: "function", name: "positionCount", stateMutability: "view", inputs: [{ type: "bytes32" }], outputs: [{ type: "uint256" }] },
|
|
307
307
|
{ type: "function", name: "isSealed", stateMutability: "view", inputs: [{ type: "bytes32" }], outputs: [{ type: "bool" }] },
|
|
308
|
+
// Present on the deployed V2 locker; read for permanent-custody verification.
|
|
309
|
+
{ type: "function", name: "feeRecipient", stateMutability: "view", inputs: [{ type: "bytes32" }], outputs: [{ type: "address" }] },
|
|
308
310
|
] as const;
|
|
309
311
|
const poolFeeCollectorFactoryCommonAbi = [
|
|
310
312
|
{ type: "function", name: "collectorDeployer", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
@@ -359,6 +361,8 @@ export const lifecycleErc404Abi = [
|
|
|
359
361
|
{ type: "function", name: "unit", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
|
|
360
362
|
{ type: "function", name: "baseURI", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
|
|
361
363
|
{ type: "function", name: "mirrorERC721", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
364
|
+
// Present on LaunchERC404V1; read for ERC404 supply/custody verification.
|
|
365
|
+
{ type: "function", name: "maxNFTSupply", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
|
|
362
366
|
] as const;
|
|
363
367
|
export const lifecycleErc404MirrorAbi = [
|
|
364
368
|
{ type: "function", name: "baseERC20", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
package/src/lifecycle/index.ts
CHANGED
|
@@ -8,4 +8,6 @@ export * from "./fork.js";
|
|
|
8
8
|
export * from "./authors.js";
|
|
9
9
|
export * from "./presets.js";
|
|
10
10
|
export * from "./transactions.js";
|
|
11
|
+
export { createLifecycleSessionCache } from "./rpc.js";
|
|
11
12
|
export { planLaunch, simulateLaunchPlan, buildNextTransaction, prepareAndPlanLifecycleLaunch } from "./planner.js";
|
|
13
|
+
export { assertLaunchTopologyCoverage, getTickAtSqrtRatio, type LaunchTopologyCoverageInput, type LaunchTopologyPosition } from "../launch.js";
|
package/src/lifecycle/planner.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { encodeFunctionData, keccak256, toHex, zeroAddress, type Address, type Hex } from "viem";
|
|
2
|
+
import { ABYSS_FEE_TIERS } from "../abyss.js";
|
|
3
|
+
import { assertLaunchTopologyCoverage, getTickAtSqrtRatio } from "../launch.js";
|
|
2
4
|
import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleErc20Abi } from "./abi.js";
|
|
3
5
|
import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, LIFECYCLE_MAX_ERC404_SUPPLY, parseLaunchPlan, serializeLaunchPlan, type LaunchPlanV1 } from "./schema.js";
|
|
4
6
|
import { readLaunchProgress, readLifecycleProfiles, readLifecycleTokenAtBlock, readPoolBoundHookDeployment } from "./progress.js";
|
|
5
|
-
import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion, minePoolBoundHookSalt, type V4LifecycleMarketConfig, type V4PoolBoundLifecycleMarketConfig } from "./markets.js";
|
|
6
|
-
import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleChainId, readLifecycleContract, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
7
|
+
import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion, minePoolBoundHookSalt, type AbyssLifecycleMarketConfig, type V4LifecycleMarketConfig, type V4PoolBoundLifecycleMarketConfig } from "./markets.js";
|
|
8
|
+
import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleChainId, readLifecycleContract, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
7
9
|
import { prepareLaunchSimulation, simulateLaunchTransactions, type NextTransactionEnvelope } from "./simulation.js";
|
|
8
10
|
import { LifecyclePlanningError, type BuildNextTransactionOptions, type CanonicalLaunchProgress, type LifecycleBlock, type LifecycleConstructionProfile, type LifecycleFundingPrerequisite, type LifecyclePoolBoundHookDeployment, type LifecycleProfile, type LifecycleRpcClient, type LifecycleSimulation, type LifecycleTransaction, type PlannedLaunch, type PlanLaunchOptions, type PrepareAndPlanLifecycleLaunchOptions, type SimulateLaunchPlanOptions } from "./types.js";
|
|
9
11
|
import { deriveKnownPoolBoundHookDeployment, protectOrderedBuyMinimums, validateBuySlippageBps } from "./calibration.js";
|
|
@@ -11,7 +13,12 @@ import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets
|
|
|
11
13
|
import { buildLaunchTransactions } from "./transactions.js";
|
|
12
14
|
import { assertLifecycleMarketAllocation, assertLifecycleSupplyAllocation } from "./allocations.js";
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
type ValidatedPlanShape = {
|
|
17
|
+
commitment: Hex;
|
|
18
|
+
marketConfigs: (AbyssLifecycleMarketConfig | V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig)[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function validatePlanShape(plan: LaunchPlanV1): ValidatedPlanShape {
|
|
15
22
|
// Encoding checks all exact integer widths, addresses, byte strings and tuple fields.
|
|
16
23
|
const commitment = hashLaunchPlan(plan);
|
|
17
24
|
if (plan.markets.length === 0 || plan.markets.length > 16 || plan.buys.length > 64 || plan.funding.length > 8 || plan.feeAssets.length === 0 || plan.feeAssets.length > 8 || plan.token.supply <= 0n || plan.token.inventoryRecipient === zeroAddress || plan.token.inventoryRecipient.toLowerCase() === plan.orchestrator.toLowerCase() || plan.executorFeeBps > 1000) throw new LifecyclePlanningError("INVALID_PLAN", "Plan exceeds lifecycle bounds or lacks positive token economics");
|
|
@@ -36,6 +43,7 @@ function validatePlanShape(plan: LaunchPlanV1): Hex {
|
|
|
36
43
|
}
|
|
37
44
|
const v4Quotes = new Set<string>();
|
|
38
45
|
let positionCount = 0;
|
|
46
|
+
const marketConfigs: ValidatedPlanShape["marketConfigs"] = [];
|
|
39
47
|
for (const market of plan.markets) {
|
|
40
48
|
if (market.tokenBudget <= 0n || market.config === "0x" || (market.config.length - 2) / 2 > 16384) throw new LifecyclePlanningError("INVALID_MARKET", "Each market needs a positive token budget and bounded versioned config");
|
|
41
49
|
let config: V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig | undefined;
|
|
@@ -49,8 +57,13 @@ function validatePlanShape(plan: LaunchPlanV1): Hex {
|
|
|
49
57
|
v4Quotes.add(quote);
|
|
50
58
|
if (config.lpFeePips >= 1000000 || (config.version === 6 ? config.hookFeePips > 1000000 : config.hookFeePips >= 1000000) || config.feeMode > 1 || config.tickSpacing < 1 || config.tickSpacing > 32767 || config.sqrtPriceX96 < 4295128739n || config.sqrtPriceX96 >= 1461446703485210103287273052203988822378723970342n || (config.protocolFeeDenominator !== 0 && (config.protocolFeeDenominator < 4 || config.protocolFeeDenominator > 10 || config.treasury === zeroAddress))) throw new LifecyclePlanningError("INVALID_MARKET", "V4 market violates lifecycle fee, price, treasury or position bounds");
|
|
51
59
|
positionCount += assertLifecycleMarketAllocation(market, config.positions);
|
|
60
|
+
marketConfigs.push(config);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const abyss = decodeAbyssLifecycleMarketConfig(market.config);
|
|
64
|
+
positionCount += assertLifecycleMarketAllocation(market, abyss.positions);
|
|
65
|
+
marketConfigs.push(abyss);
|
|
52
66
|
}
|
|
53
|
-
else positionCount += assertLifecycleMarketAllocation(market, decodeAbyssLifecycleMarketConfig(market.config).positions);
|
|
54
67
|
if (positionCount > 32) throw new LifecyclePlanningError("INVALID_POSITION_COUNT", "A launch commits at most 32 total positions across every venue");
|
|
55
68
|
}
|
|
56
69
|
assertLifecycleSupplyAllocation(plan);
|
|
@@ -59,7 +72,7 @@ function validatePlanShape(plan: LaunchPlanV1): Hex {
|
|
|
59
72
|
const funding = plan.funding.find((item) => item.asset.toLowerCase() === market.quoteAsset.toLowerCase());
|
|
60
73
|
if (required > (funding?.amount ?? 0n)) throw new LifecyclePlanningError("INVALID_FUNDING", "Quote funding does not cover all ordered buys for its asset");
|
|
61
74
|
}
|
|
62
|
-
return commitment;
|
|
75
|
+
return { commitment, marketConfigs };
|
|
63
76
|
}
|
|
64
77
|
|
|
65
78
|
async function readFunding(client: LifecycleRpcClient, plan: LaunchPlanV1, block: LifecycleBlock): Promise<{ prerequisites: LifecycleFundingPrerequisite[]; approvals: LifecycleTransaction[] }> {
|
|
@@ -180,7 +193,7 @@ function assertReviewedTransactionUnchanged(options: BuildNextTransactionOptions
|
|
|
180
193
|
async function readTokenFactoryBinding(client: LifecycleRpcClient, orchestrator: Address, block: LifecycleBlock): Promise<Pick<PlannedLaunch, "tokenFactory" | "tokenFactoryCodeHash">> {
|
|
181
194
|
const tokenFactory = await readLifecycleContract<Address>(client, orchestrator, launchLifecycleAbi, "tokenFactory", [], block);
|
|
182
195
|
const [codeValue, authority] = await Promise.all([
|
|
183
|
-
|
|
196
|
+
lifecycleStaticRpc(client, "eth_getCode", [tokenFactory, toHex(block.number)], block),
|
|
184
197
|
readLifecycleContract<Address>(client, tokenFactory, lifecycleAdapterAbi, "core", [], block),
|
|
185
198
|
]);
|
|
186
199
|
const code = rpcHex(codeValue, "token factory code");
|
|
@@ -202,7 +215,7 @@ function assertPlanningActive(signal?: AbortSignal): void {
|
|
|
202
215
|
if (signal?.aborted) throw Object.assign(new Error("Lifecycle launch planning cancelled"), { name: "AbortError" });
|
|
203
216
|
}
|
|
204
217
|
|
|
205
|
-
function validatePlanningIntent(options: PlanLaunchOptions):
|
|
218
|
+
function validatePlanningIntent(options: PlanLaunchOptions): ValidatedPlanShape {
|
|
206
219
|
const { plan, account, mode } = options;
|
|
207
220
|
assertPlanningActive(options.signal);
|
|
208
221
|
if (mode !== "atomic" && mode !== "staged") throw new LifecyclePlanningError("EXPLICIT_MODE_REQUIRED", "Choose atomic or staged explicitly; staged is never silently substituted");
|
|
@@ -211,15 +224,45 @@ function validatePlanningIntent(options: PlanLaunchOptions): Hex {
|
|
|
211
224
|
return validatePlanShape(plan);
|
|
212
225
|
}
|
|
213
226
|
|
|
227
|
+
function validatePlanTopology(options: PlanLaunchOptions, marketConfigs: ValidatedPlanShape["marketConfigs"], token: Address): void {
|
|
228
|
+
for (const [marketIndex, config] of marketConfigs.entries()) {
|
|
229
|
+
try {
|
|
230
|
+
let tickSpacing: number;
|
|
231
|
+
let openingSqrtPriceX96: bigint;
|
|
232
|
+
if ("version" in config) {
|
|
233
|
+
tickSpacing = config.tickSpacing;
|
|
234
|
+
openingSqrtPriceX96 = config.sqrtPriceX96;
|
|
235
|
+
} else {
|
|
236
|
+
const tier = ABYSS_FEE_TIERS.find(({ feePips }) => feePips === config.fee);
|
|
237
|
+
if (tier === undefined) throw new LifecyclePlanningError("INVALID_MARKET", "Abyss market fee must select a canonical tick-spacing tier");
|
|
238
|
+
tickSpacing = tier.tickSpacing;
|
|
239
|
+
openingSqrtPriceX96 = config.openingSqrtPriceX96;
|
|
240
|
+
}
|
|
241
|
+
assertLaunchTopologyCoverage({ positions: config.positions, openingTick: getTickAtSqrtRatio(openingSqrtPriceX96), tickSpacing,
|
|
242
|
+
launchedTokenIsQuote: BigInt(token) < BigInt(options.plan.markets[marketIndex]!.quoteAsset),
|
|
243
|
+
allowInteriorGaps: options.allowTopologyGaps === true });
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (!(error instanceof Error)) throw error;
|
|
246
|
+
const code = error.message.slice(0, error.message.indexOf(":"));
|
|
247
|
+
if (code === "MISSING_PERMANENT_TAIL" && options.allowMissingPermanentTail === true) continue;
|
|
248
|
+
if (code === "TOPOLOGY_OPENING_GAP" || code === "TOPOLOGY_INTERIOR_GAP" ||
|
|
249
|
+
code === "MISSING_PERMANENT_TAIL" || code === "TOPOLOGY_OFF_GRID_TICK") {
|
|
250
|
+
throw new LifecyclePlanningError(code, `Market ${marketIndex}: ${error.message}`);
|
|
251
|
+
}
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
214
257
|
function assertFinalPlan(planned: PlannedLaunch, signal?: AbortSignal): void {
|
|
215
258
|
assertPlanningActive(signal);
|
|
216
259
|
if (hashLaunchPlan(planned.plan).toLowerCase() !== planned.planHash.toLowerCase() || hashLaunchIdentity(planned.plan).toLowerCase() !== planned.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed during asynchronous planning; previous reads and simulation are invalid");
|
|
217
260
|
}
|
|
218
261
|
|
|
219
262
|
/** One owner constructs and mines, optionally measures actual ordered buy outputs,
|
|
220
|
-
* then proves only the final protected commitment. No
|
|
263
|
+
* then proves only the final protected commitment. No cached execution certificates. */
|
|
221
264
|
export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifecycleLaunchOptions): Promise<PlannedLaunch> {
|
|
222
|
-
const commitment = validatePlanningIntent(options);
|
|
265
|
+
const { commitment } = validatePlanningIntent(options);
|
|
223
266
|
if (options.buySlippageBps !== undefined) validateBuySlippageBps(options.buySlippageBps);
|
|
224
267
|
const needsMining = options.plan.markets.some((market) => isPoolBoundV4ConfigVersion(market.configVersion));
|
|
225
268
|
const needsProtection = options.buySlippageBps !== undefined && options.plan.buys.length !== 0;
|
|
@@ -293,7 +336,7 @@ export async function planLaunch(options: PlanLaunchOptions): Promise<PlannedLau
|
|
|
293
336
|
|
|
294
337
|
async function planLaunchWithReads(options: PlanLaunchOptions, client: LifecycleRpcClient, pinnedBlock?: LifecycleBlock, ownedProgress?: CanonicalLaunchProgress, diagnostic = false, reviewed?: ReviewedNextTransaction): Promise<PlannedLaunch> {
|
|
295
338
|
const { plan, account, mode } = options;
|
|
296
|
-
const planHash = validatePlanningIntent(options);
|
|
339
|
+
const { commitment: planHash, marketConfigs } = validatePlanningIntent(options);
|
|
297
340
|
const launchId = hashLaunchIdentity(plan);
|
|
298
341
|
const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
|
|
299
342
|
pinnedBlock ?? readLifecycleBlock(client), readLifecycleChainId(client),
|
|
@@ -322,7 +365,8 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
322
365
|
]));
|
|
323
366
|
assertPlanningActive(options.signal);
|
|
324
367
|
const placeholder: LifecycleSimulation = { backend: "unavailable", confidence: "provisional", admitted: false, executionProof: "unavailable", protocolFit: "unknown", transportPreflight: "not-requested", blockNumber: block.number, blockHash: block.hash, account, chainId: plan.chainId, limits, steps: [] };
|
|
325
|
-
let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, simulation: placeholder, preparationBatchSize: plan.markets.length, limits: options.limits
|
|
368
|
+
let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, simulation: placeholder, preparationBatchSize: plan.markets.length, limits: options.limits,
|
|
369
|
+
allowTopologyGaps: options.allowTopologyGaps === true, allowMissingPermanentTail: options.allowMissingPermanentTail === true };
|
|
326
370
|
const receiptsPending = progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed");
|
|
327
371
|
if (!progress.confirmationSafe || receiptsPending) {
|
|
328
372
|
const hookDeployments: LifecyclePoolBoundHookDeployment[] = [];
|
|
@@ -340,6 +384,9 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
340
384
|
await assertLifecycleBlock(client, block, plan.chainId);
|
|
341
385
|
return { ...planned, simulation: { ...placeholder, confidence: "stateful", executionProof: "proved", protocolFit: "proved", admitted: true, reason: "Canonical launch is terminal; no transaction remains" } };
|
|
342
386
|
}
|
|
387
|
+
// Topology is a creation policy, not a retroactive obstacle to recovery of
|
|
388
|
+
// immutable onchain commitments. Waiting and terminal launches returned above.
|
|
389
|
+
if (progress.canonical.phase === LifecyclePhase.None && progress.head.phase === LifecyclePhase.None) validatePlanTopology(options, marketConfigs, identity.predictedToken);
|
|
343
390
|
const [funding, metadata] = await lifecycleStage(client, "plan.inputs", () => Promise.all([
|
|
344
391
|
progress.canonical.phase === LifecyclePhase.None ? readFunding(client, plan, block) : { prerequisites: [], approvals: [] },
|
|
345
392
|
readPlanMetadata(client, plan, identity.predictedToken, block),
|
|
@@ -459,7 +506,8 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
|
|
|
459
506
|
if (next === undefined) throw new LifecyclePlanningError("MISSING_GAS_PROOF", "Cancellation omitted its exact simulation admission", simulation);
|
|
460
507
|
return preflightNextTransaction(options, next, simulation, reviewed);
|
|
461
508
|
}
|
|
462
|
-
const refreshed = await lifecycleStage(client, "plan", () => planLaunchWithReads({ client, plan: options.planned.plan, account: options.planned.account, mode: options.planned.mode, limits: options.limits === undefined ? options.planned.limits : options.limits, fork: options.fork, receipts: options.receipts, confirmations: options.confirmations ?? options.planned.confirmations, signal: options.signal
|
|
509
|
+
const refreshed = await lifecycleStage(client, "plan", () => planLaunchWithReads({ client, plan: options.planned.plan, account: options.planned.account, mode: options.planned.mode, limits: options.limits === undefined ? options.planned.limits : options.limits, fork: options.fork, receipts: options.receipts, confirmations: options.confirmations ?? options.planned.confirmations, signal: options.signal,
|
|
510
|
+
allowTopologyGaps: options.planned.allowTopologyGaps, allowMissingPermanentTail: options.planned.allowMissingPermanentTail }, client, block, progress, false, reviewed));
|
|
463
511
|
assertFinalPlan(refreshed, options.signal);
|
|
464
512
|
if (!refreshed.simulation.admitted) throw new LifecyclePlanningError("PLAN_NOT_ADMITTED", refreshed.simulation.reason ?? "Remaining exact launch sequence is not admitted", refreshed.simulation);
|
|
465
513
|
assertPlannedDeploymentBindings(options.planned, refreshed);
|
|
@@ -478,6 +526,7 @@ export async function simulateLaunchPlan(options: SimulateLaunchPlanOptions): Pr
|
|
|
478
526
|
client, plan: planned.plan, account: planned.account, mode: planned.mode,
|
|
479
527
|
limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork,
|
|
480
528
|
receipts: options.receipts, confirmations: options.confirmations ?? planned.confirmations, signal: options.signal,
|
|
529
|
+
allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
|
|
481
530
|
});
|
|
482
531
|
assertPlannedDeploymentBindings(planned, current);
|
|
483
532
|
return current.simulation;
|
|
@@ -2,7 +2,7 @@ import { concatHex, decodeFunctionData, encodeAbiParameters, encodeFunctionData,
|
|
|
2
2
|
import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleOracleFactoryAbi, lifecycleRegistryAbi, poolFeeCollectorFactoryV1Abi, poolFeeCollectorFactoryConfigV6Abi, lifecycleV4HookAbi, lifecycleV4LockerAbi, poolHookDeployerV1Abi, poolHookDeployerConfigV6Abi, sharedHookDeployerV1Abi, fixedFeePoolHookV1Abi, fixedFeePoolHookConfigV6Abi, poolMarketAdapterV1Abi, sharedMarketAdapterV1Abi } from "./abi.js";
|
|
3
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
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";
|
|
5
|
+
import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleRpc, lifecycleStage, readLifecycleBlock, readLifecycleChainId, readLifecycleContract, readLifecycleStaticContract, readLifecycleProfileTopology, rpcHex, rpcObject, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
6
6
|
import { LifecyclePlanningError, type CanonicalLaunchProgress, type LifecycleBlock, type LifecycleMarketProgress, type LifecycleProfile, type LifecycleReceiptReference, type LifecycleReceiptStatus, type LifecycleRpcClient, type PoolBoundHookDeployment, type ReadLaunchProgressOptions } from "./types.js";
|
|
7
7
|
import { deriveKnownPoolBoundHookDeployment } from "./calibration.js";
|
|
8
8
|
import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
|
|
@@ -76,7 +76,7 @@ function lifecycleProfileCertificateKey(orchestrator: Address, registry: Address
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
async function readDependencyCode(client: LifecycleRpcClient, address: Address, block: LifecycleBlock): Promise<Hex> {
|
|
79
|
-
const code = rpcHex(await
|
|
79
|
+
const code = rpcHex(await lifecycleStaticRpc(client, "eth_getCode", [address, toHex(block.number)], block), "lifecycle dependency code");
|
|
80
80
|
if (address === zeroAddress || code === "0x") throw new LifecyclePlanningError("PROFILE_DEPENDENCY", `Lifecycle dependency ${address} has no deployed code`);
|
|
81
81
|
return code;
|
|
82
82
|
}
|
|
@@ -118,7 +118,7 @@ async function certifyLifecycleProfileFresh(client: LifecycleRpcClient, orchestr
|
|
|
118
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());
|
|
119
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");
|
|
120
120
|
if (eligibility !== undefined && "failure" in eligibility) throw eligibility.failure;
|
|
121
|
-
const eligible = eligibility?.eligible ?? await
|
|
121
|
+
const eligible = eligibility?.eligible ?? await readLifecycleStaticContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, version, LIFECYCLE_REQUIRED_CAPABILITIES], block);
|
|
122
122
|
if (eligible.toLowerCase() !== adapter.implementation.toLowerCase()) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
|
|
123
123
|
return Object.freeze({ topology: Object.freeze({ ...topology }) });
|
|
124
124
|
}
|
|
@@ -147,28 +147,28 @@ async function certifyLifecycleProfileFresh(client: LifecycleRpcClient, orchestr
|
|
|
147
147
|
// Frozen graph addresses permit independent evidence reads. Getter results
|
|
148
148
|
// still have to match that graph; none of these observations is caller authority.
|
|
149
149
|
const [eligible, metadata, actualVersion, dependencies, dependencyCodes, collectorDeployer, launcher, lockerManager, creationCodeHash, chunk0, chunk1, chunkCodes, sharedRoot] = await Promise.all([
|
|
150
|
-
|
|
151
|
-
Promise.all((["PROFILE_ID", "CONFIG_SCHEMA", "implementationRegistry"] as const).map((getter) =>
|
|
152
|
-
|
|
153
|
-
Promise.all((["poolManager", "hookRoot", "oracleFactory", "locker", "collectorFactory", "hookDeployer"] as const).map((getter) =>
|
|
150
|
+
readLifecycleStaticContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, version, LIFECYCLE_REQUIRED_CAPABILITIES], block),
|
|
151
|
+
Promise.all((["PROFILE_ID", "CONFIG_SCHEMA", "implementationRegistry"] as const).map((getter) => readLifecycleStaticContract<Hex>(client, adapter.implementation, abi, getter, [], block))),
|
|
152
|
+
readLifecycleStaticContract<number>(client, adapter.implementation, abi, "CONFIG_VERSION", [], block),
|
|
153
|
+
Promise.all((["poolManager", "hookRoot", "oracleFactory", "locker", "collectorFactory", "hookDeployer"] as const).map((getter) => readLifecycleStaticContract<Address>(client, adapter.implementation, abi, getter, [], block))),
|
|
154
154
|
Promise.all(codeBindings.map(([address]) => readDependencyCode(client, address, block))),
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
155
|
+
readLifecycleStaticContract<Address>(client, g.collectorFactory, poolFeeCollectorFactoryV1Abi, "collectorDeployer", [], block),
|
|
156
|
+
readLifecycleStaticContract<Address>(client, g.locker, lifecycleV4LockerAbi, "launcher", [], block),
|
|
157
|
+
readLifecycleStaticContract<Address>(client, g.locker, lifecycleV4LockerAbi, "poolManager", [], block),
|
|
158
|
+
readLifecycleStaticContract<Hex>(client, g.hookDeployer, deployerAbi, "creationCodeHash", [], block),
|
|
159
|
+
readLifecycleStaticContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk0", [], block),
|
|
160
|
+
readLifecycleStaticContract<Address>(client, g.hookDeployer, deployerAbi, "codeChunk1", [], block),
|
|
161
161
|
Promise.all([
|
|
162
162
|
readDependencyCode(client, g.codeChunk0, block),
|
|
163
163
|
g.codeChunk1 === zeroAddress ? Promise.resolve<Hex>("0x") : readDependencyCode(client, g.codeChunk1, block),
|
|
164
164
|
]),
|
|
165
165
|
shared ? Promise.all([
|
|
166
166
|
readDependencyCode(client, g.hookRoot, block),
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
Promise.all((["registrar", "poolManager", "oracleFactory"] as const).map((getter) =>
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
readLifecycleStaticContract<Hex>(client, g.hookDeployer, deployerAbi, "deployedCodeHash", [g.hookRoot], block),
|
|
168
|
+
readLifecycleStaticContract<Address>(client, g.hookDeployer, sharedHookDeployerV1Abi, "predict", [g.manager, adapter.implementation, g.oracleFactory, g.sharedHookSalt], block),
|
|
169
|
+
Promise.all((["registrar", "poolManager", "oracleFactory"] as const).map((getter) => readLifecycleStaticContract<Address>(client, g.hookRoot, lifecycleV4HookAbi, getter, [], block))),
|
|
170
|
+
readLifecycleStaticContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "REQUIRED_HOOK_FLAGS", [], block),
|
|
171
|
+
readLifecycleStaticContract<bigint>(client, g.hookRoot, lifecycleV4HookAbi, "ALL_HOOK_MASK", [], block),
|
|
172
172
|
]) : undefined,
|
|
173
173
|
]);
|
|
174
174
|
if (eligible.toLowerCase() !== adapter.implementation.toLowerCase()) throw new LifecyclePlanningError("INELIGIBLE_PROFILE", "Registry refuses this profile for pending source admission");
|
|
@@ -214,7 +214,7 @@ export async function validateV4LifecycleOracle(options: {
|
|
|
214
214
|
}): Promise<void> {
|
|
215
215
|
const { client, envelope, oracleConfigId, block } = options;
|
|
216
216
|
if (oracleConfigId.toLowerCase() === zeroHash) throw new LifecyclePlanningError("INVALID_ORACLE_CONFIG", "Market oracle configuration must be nonzero");
|
|
217
|
-
const [move, cardinality] = await
|
|
217
|
+
const [move, cardinality] = await readLifecycleStaticContract<readonly [number, number]>(client, envelope.graph.oracleFactory, lifecycleOracleFactoryAbi, "oracleConfigs", [oracleConfigId], block);
|
|
218
218
|
if (move <= 0 || move > 887272 || cardinality < 2 || cardinality > envelope.bounds.maximumOracleCardinality) {
|
|
219
219
|
throw new LifecyclePlanningError("INVALID_ORACLE_CONFIG", "Selected market oracle is unregistered or exceeds the admitted cardinality bounds");
|
|
220
220
|
}
|
|
@@ -234,25 +234,25 @@ export async function readLifecycleProfiles(options: { client: LifecycleRpcClien
|
|
|
234
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
235
|
let protocolMaximumDeveloperFeeBpsPromise: Promise<number> | undefined;
|
|
236
236
|
const readProtocolMaximumDeveloperFeeBps = () => protocolMaximumDeveloperFeeBpsPromise ??=
|
|
237
|
-
|
|
237
|
+
readLifecycleStaticContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block);
|
|
238
238
|
const readProfile = async (id: Hex): Promise<LifecycleProfile> => {
|
|
239
|
-
const registration = await
|
|
239
|
+
const registration = await readLifecycleStaticContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [id], block);
|
|
240
240
|
const schema = registration.configSchema.toLowerCase();
|
|
241
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
242
|
const [adapter, certification] = await Promise.all([
|
|
243
|
-
|
|
243
|
+
readLifecycleStaticContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block),
|
|
244
244
|
venueKind === "unknown" ? undefined : Promise.all([
|
|
245
245
|
readLifecycleProfileTopology(client, registry, id, block),
|
|
246
246
|
venueKind === "uniswap-v4" ? Promise.all([
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
readLifecycleStaticContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [id], block),
|
|
248
|
+
readLifecycleStaticContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [id], block),
|
|
249
249
|
readProtocolMaximumDeveloperFeeBps(),
|
|
250
250
|
]) : undefined,
|
|
251
251
|
// The exact Abyss arguments follow from registration, independently
|
|
252
252
|
// of adapter/topology responses. Handle refusal immediately, but
|
|
253
253
|
// keep its original place after canonical/schema/topology checks.
|
|
254
254
|
venueKind === "abyss" ? chainIdPromise.then(() =>
|
|
255
|
-
|
|
255
|
+
readLifecycleStaticContract<Address>(client, registry, lifecycleRegistryAbi, "requireEligible", [registration.adapterId, id, 1, LIFECYCLE_REQUIRED_CAPABILITIES], block)
|
|
256
256
|
).then((eligible) => ({ eligible }), (failure: unknown) => ({ failure })) : undefined,
|
|
257
257
|
]).then((values) => ({ values }), (failure: unknown) => ({ failure })),
|
|
258
258
|
]);
|
|
@@ -345,16 +345,16 @@ async function readPoolBoundHookDeploymentDetailsFresh(options: { client: Lifecy
|
|
|
345
345
|
const registry = await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "registry", [], block);
|
|
346
346
|
const [authority, registration] = await Promise.all([
|
|
347
347
|
readLifecycleContract<Address>(client, registry, lifecycleRegistryAbi, "core", [], block),
|
|
348
|
-
|
|
348
|
+
readLifecycleStaticContract<ProfileRegistrationV1>(client, registry, lifecycleRegistryAbi, "profile", [market.profileId], block),
|
|
349
349
|
]);
|
|
350
350
|
if (authority.toLowerCase() !== plan.orchestrator.toLowerCase()) throw new LifecyclePlanningError("REGISTRY_BINDING", "Registry is not bound to this lifecycle core");
|
|
351
|
-
const adapter = await
|
|
351
|
+
const adapter = await readLifecycleStaticContract<AdapterRegistrationV1>(client, registry, lifecycleRegistryAbi, "adapter", [registration.adapterId], block);
|
|
352
352
|
if (registration.adapterId.toLowerCase() !== market.adapterId.toLowerCase()) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Market adapter differs from the profile's immutable registration");
|
|
353
353
|
const [recorded, envelope, [implementation, beneficiary, maximumDeveloperFeeBps, termsDigest, enabled], protocolMaximumDeveloperFeeBps, token] = await Promise.all([
|
|
354
354
|
readLifecycleProfileTopology(client, registry, market.profileId, block),
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
readLifecycleStaticContract<LaunchEnvelopeV2>(client, registry, lifecycleRegistryAbi, "profileEnvelope", [market.profileId], block),
|
|
356
|
+
readLifecycleStaticContract<readonly [Address, Address, number, Hex, boolean]>(client, registry, lifecycleRegistryAbi, "developerTerms", [market.profileId], block),
|
|
357
|
+
readLifecycleStaticContract<number>(client, registry, lifecycleRegistryAbi, "protocolMaximumDeveloperFeeBps", [], block),
|
|
358
358
|
readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "predictToken", [plan], block),
|
|
359
359
|
]);
|
|
360
360
|
const { boundGraph } = await certifyLifecycleProfile(client, plan.orchestrator, registry, market.profileId, registration, adapter, recorded, envelope, block, chainId);
|
|
@@ -374,10 +374,10 @@ async function readPoolBoundHookDeploymentDetailsFresh(options: { client: Lifecy
|
|
|
374
374
|
const deployerAbi = config.version === 5 ? poolHookDeployerV1Abi : poolHookDeployerConfigV6Abi;
|
|
375
375
|
const collectorAbi = config.version === 5 ? poolFeeCollectorFactoryV1Abi : poolFeeCollectorFactoryConfigV6Abi;
|
|
376
376
|
const [[parameters, parameterSalt], [deployer, initCodeHash, salt, predictedHook], remoteInitCodeHash, remotePrediction] = await Promise.all([
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
377
|
+
readLifecycleStaticContract<readonly [PoolBoundHookParameters, Hex]>(client, boundGraph.collectorFactory, collectorAbi, "poolBoundHookParameters", [adapter.implementation, token, market], block),
|
|
378
|
+
readLifecycleStaticContract<readonly [Address, Hex, Hex, Address]>(client, adapter.implementation, poolMarketAdapterV1Abi, "hookDeploymentMetadata", [token, market], block),
|
|
379
|
+
readLifecycleStaticContract<Hex>(client, boundGraph.deployer, deployerAbi, "initCodeHash", [expected], block),
|
|
380
|
+
readLifecycleStaticContract<Address>(client, boundGraph.deployer, deployerAbi, "predict", [expected, config.hookSalt], block),
|
|
381
381
|
]);
|
|
382
382
|
const localInitCodeHash = poolBoundHookInitCodeHash(boundGraph.creationCode, expected, config.version);
|
|
383
383
|
const localPrediction = predictPoolBoundHookAddress({ deployer: boundGraph.deployer, initCodeHash: localInitCodeHash, salt: config.hookSalt });
|
|
@@ -385,7 +385,7 @@ async function readPoolBoundHookDeploymentDetailsFresh(options: { client: Lifecy
|
|
|
385
385
|
const hookCode = rpcHex(await lifecyclePinnedRpc(client, "eth_getCode", [predictedHook, toHex(block.number)], block), "bound hook code");
|
|
386
386
|
if (hookCode !== "0x") {
|
|
387
387
|
const [deployedCodeHash, dependencies, requiredFlags, allFlags, actualPoolId, deploymentConfigHash, marketCommitment, opening, positions, feePolicy] = await Promise.all([
|
|
388
|
-
|
|
388
|
+
readLifecycleStaticContract<Hex>(client, deployer, deployerAbi, "deployedCodeHash", [predictedHook], block),
|
|
389
389
|
Promise.all((["poolManager", "registrar", "oracleFactory", "core", "liquidityLocker", "token"] as const).map((getter) => readLifecycleContract<Address>(client, predictedHook, fixedFeePoolHookV1Abi, getter, [], block))),
|
|
390
390
|
readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "REQUIRED_HOOK_FLAGS", [], block),
|
|
391
391
|
readLifecycleContract<bigint>(client, predictedHook, fixedFeePoolHookV1Abi, "ALL_HOOK_MASK", [], block),
|