@black-market/sdk 0.13.0 → 0.14.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 +4 -4
- package/dist/lifecycle/index.d.ts +3 -1
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +3 -1
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/lens.d.ts +409 -0
- package/dist/lifecycle/lens.d.ts.map +1 -0
- package/dist/lifecycle/lens.js +30 -0
- package/dist/lifecycle/lens.js.map +1 -0
- package/dist/lifecycle/mining.d.ts +47 -0
- package/dist/lifecycle/mining.d.ts.map +1 -0
- package/dist/lifecycle/mining.js +89 -0
- package/dist/lifecycle/mining.js.map +1 -0
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +70 -62
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +5 -0
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +5 -0
- package/dist/lifecycle/presets.js.map +1 -1
- package/dist/lifecycle/progress.d.ts +48 -0
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +31 -20
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.d.ts +6 -2
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +47 -44
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/simulation.d.ts +5 -3
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +130 -109
- package/dist/lifecycle/simulation.js.map +1 -1
- package/dist/lifecycle/snapshot.d.ts +8 -0
- package/dist/lifecycle/snapshot.d.ts.map +1 -0
- package/dist/lifecycle/snapshot.js +79 -0
- package/dist/lifecycle/snapshot.js.map +1 -0
- package/dist/lifecycle/types.d.ts +15 -5
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/docs/launch-lifecycle.md +42 -20
- package/docs/reference.md +43 -6
- package/examples/README.md +2 -2
- package/package.json +1 -1
- package/src/lifecycle/index.ts +5 -1
- package/src/lifecycle/lens.ts +42 -0
- package/src/lifecycle/mining.ts +80 -0
- package/src/lifecycle/planner.ts +72 -58
- package/src/lifecycle/presets.ts +10 -0
- package/src/lifecycle/progress.ts +26 -16
- package/src/lifecycle/rpc.ts +45 -43
- package/src/lifecycle/simulation.ts +126 -100
- package/src/lifecycle/snapshot.ts +72 -0
- package/src/lifecycle/types.ts +15 -5
package/docs/reference.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Use the [launch guide](launch-lifecycle.md) for the end-to-end flow and the
|
|
4
4
|
[runner reference](../examples/README.md) for environment and recovery artifacts.
|
|
5
|
-
This page covers SDK **0.
|
|
5
|
+
This page covers SDK **0.14.1** addresses, limits, wire formats, fees, and diagnostics.
|
|
6
6
|
|
|
7
7
|
## Deployed infrastructure
|
|
8
8
|
|
|
@@ -19,6 +19,7 @@ start historical log queries there, in chunks of at most **10M blocks**.
|
|
|
19
19
|
| `UFG_LAUNCH_PROXY_ADDRESS` | `0xf6673d859DE03eB74A0b01696Edb97f941dFD0CA` |
|
|
20
20
|
| `UFG_TREASURY_FACTORY_ADDRESS` | `0x26Aa5D52134c8E6c4C01E9fb53A7568c0C64901F` |
|
|
21
21
|
| `UFG_UPSTREAM_FACTORY_ADDRESS` | `0xA170b5A58C111334A7237F2Ea2c428ec5022f68E` |
|
|
22
|
+
| Known deployment `launchLens` | `0xcb4bb3caca71fe52eace8170cf83e901418646f8` |
|
|
22
23
|
|
|
23
24
|
`getKnownLifecycleDeployment`, `getKnownLifecycleProfile`, and
|
|
24
25
|
`listKnownLifecycleProfiles` are pure selectors keyed by chain/core. Unknown
|
|
@@ -165,9 +166,28 @@ Failed getters cannot fall back to the large header gas limit or a hard-coded 32
|
|
|
165
166
|
|
|
166
167
|
`transactionGasCeiling`, when set, is the minimum complete-envelope chain/RPC/account
|
|
167
168
|
restriction. It is separate from compute. NodeInterface (`0xc8`)
|
|
168
|
-
`gasEstimateL1Component` estimates poster allocation
|
|
169
|
-
calldata/value at the same block.
|
|
170
|
-
|
|
169
|
+
`gasEstimateL1Component` estimates poster allocation once for the next final
|
|
170
|
+
transaction, using its exact sender/target/calldata/value at the same block.
|
|
171
|
+
Compute-only calibration and fresh poster allocation size the immediate envelope
|
|
172
|
+
before native validation. `step.computeGasRequired` retains a separable compute
|
|
173
|
+
observation, never the old poster charge or provisional budget. A fresh poster
|
|
174
|
+
quote is combined once when that command becomes next. Both unvalidated sizing
|
|
175
|
+
and the final exact pass use affordable pinned envelopes; validation:false does
|
|
176
|
+
not disable Nitro's upfront funds check. The final pass uses exactly returned gas.
|
|
177
|
+
Sizing starts with per-call ceilings because unused gas is refunded between calls.
|
|
178
|
+
Only a genuine upfront-funds refusal permits one second unvalidated sizing attempt
|
|
179
|
+
with the budget shared across the sequence. Other failures do not retry; the final
|
|
180
|
+
exact validated pass is never replayed.
|
|
181
|
+
|
|
182
|
+
A tight payer balance can cover smaller exact staged envelopes yet not the
|
|
183
|
+
discovery margin. This may return `failureCategory: "affordability"` even though
|
|
184
|
+
the underlying launch could fit. The refused RPC exposes no successful-prefix
|
|
185
|
+
results; the SDK does not invent them or add more metering attempts.
|
|
186
|
+
|
|
187
|
+
Suffix steps are marked `provisional: true` and have no transaction estimate:
|
|
188
|
+
their conservative complete budgets check sequence feasibility, bounded across
|
|
189
|
+
the pinned affordable remaining balance. `buildNextTransaction` never reuses
|
|
190
|
+
those budgets as estimates; it freshly quotes and proves only the next command.
|
|
171
191
|
|
|
172
192
|
On Nitro, `estimate.posterGas` / `posterFee` are buffered allocations **inside**
|
|
173
193
|
`gasLimit`; `dataFeeIncludedInGas` is true, `dataFee` is zero, and `totalFee` equals
|
|
@@ -187,14 +207,31 @@ fixed transaction fees or proof that every permitted topology fits.
|
|
|
187
207
|
| `LifecycleRpcClient` option | Behavior |
|
|
188
208
|
| --- | --- |
|
|
189
209
|
| `supportsReadBatching: true` | Opt into at most eight concurrent SDK reads per invocation; set only when your transport batches concurrent requests. |
|
|
190
|
-
| `sessionCache: createLifecycleSessionCache()` | Caller-owned reuse of SDK-selected static reads at one
|
|
210
|
+
| `sessionCache: createLifecycleSessionCache()` | Caller-owned reuse of SDK-selected static reads at one pin; successful deployment/backend observations also survive across blocks. Without it, those observations belong to the supplied client object. |
|
|
191
211
|
| `onDiagnostic(event)` | Local opt-in stage/RPC timing listener; no automatic console output or telemetry. |
|
|
192
212
|
|
|
193
213
|
Call `sessionCache.invalidate()` on wallet/chain switches and after each confirmed
|
|
194
|
-
transaction
|
|
214
|
+
transaction to discard pinned static values. Session deployment/backend facts are
|
|
215
|
+
keyed by chain and account where applicable and live until the session owner is
|
|
216
|
+
discarded. `sessionCache.block` is readonly `{ number, hash }` or undefined.
|
|
217
|
+
Planning pins retain the actual RPC L2 block number and header hash. EVM
|
|
218
|
+
`BLOCKNUMBER`/`BLOCKHASH`, including Multicall3's metadata getters, expose L1 values
|
|
219
|
+
on Nitro and are not L2 pin sources.
|
|
195
220
|
Balances, allowances, current progress, receipts, nonces, policy callbacks, and
|
|
196
221
|
stateful execution proof stay fresh. Session caching does not extend admission.
|
|
197
222
|
|
|
223
|
+
One RPC L2 header supplies number, hash, timestamp, gas limit and base fee.
|
|
224
|
+
The initial Multicall3 call uses that explicit L2 block and includes native balance,
|
|
225
|
+
funding/progress and Nitro getters. Independent getters are aggregated with no
|
|
226
|
+
minimum-count threshold. Multicall3 runtime code is attested once per session.
|
|
227
|
+
The 4663 preset enables `launchLensAbi.snapshot` without caller configuration:
|
|
228
|
+
one call replaces dependent token/hook/market/funding reads inside that aggregate.
|
|
229
|
+
Its `launchLensCodeHash` pins the reviewed deployed runtime as frozen release
|
|
230
|
+
metadata, like `tokenFactoryCodeHash`, without another guard-only RPC.
|
|
231
|
+
The deployed preset `hookSaltMiner` replaces local mining during combined
|
|
232
|
+
preparation; every returned winner is verified locally.
|
|
233
|
+
Lens failures surface directly; planning does not retry through legacy reads.
|
|
234
|
+
|
|
198
235
|
Diagnostic events carry `stage`, `phase`, optional `durationMs`, `queueMs`,
|
|
199
236
|
`requestId`, allowlisted RPC `method`, and cache `hits` / `misses` / `reuses`.
|
|
200
237
|
Queue time is separate from request time; request IDs are local sequences.
|
package/examples/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Run the examples
|
|
2
2
|
|
|
3
|
-
These commands use the checkout's built `@black-market/sdk` **0.
|
|
3
|
+
These commands use the checkout's built `@black-market/sdk` **0.14.1** and viem.
|
|
4
4
|
Use **Node 24+** for direct TypeScript execution. Start with the one-market
|
|
5
5
|
`launch-erc20-abyss.ts`; larger cases can exceed the RPC's simulation budget.
|
|
6
6
|
For valuation, token ordering, ranges, and buy protection, read the
|
|
@@ -20,7 +20,7 @@ chmod 600 .env
|
|
|
20
20
|
Launch wrappers load `.env` from the working directory with `process.loadEnvFile`.
|
|
21
21
|
Existing environment values take precedence. Values are literal; shell interpolation
|
|
22
22
|
is not applied. Use the matching SDK version when copying wrappers into a consumer project:
|
|
23
|
-
`pnpm add @black-market/sdk@0.
|
|
23
|
+
`pnpm add @black-market/sdk@0.14.1 viem`.
|
|
24
24
|
Copy `launch-example.ts`, `launch-example-support.ts`, and `launch-example-kyber.ts` with the wrapper.
|
|
25
25
|
|
|
26
26
|
### Configure the environment
|
package/package.json
CHANGED
package/src/lifecycle/index.ts
CHANGED
|
@@ -5,11 +5,15 @@ export * from "./abi.js";
|
|
|
5
5
|
export * from "./markets.js";
|
|
6
6
|
export * from "./hmm.js";
|
|
7
7
|
export * from "./ufg.js";
|
|
8
|
-
export
|
|
8
|
+
export { ABYSS_LIFECYCLE_CONFIG_SCHEMA, readLifecycleTokenAtBlock, readLifecycleHeadProgress, predictLifecycleToken,
|
|
9
|
+
validateV4LifecycleOracle, readLifecycleProfiles, readPoolBoundHookDeployment,
|
|
10
|
+
buildPoolBoundHookDeploymentTransaction, validateLifecycleMarketIdentity, readLaunchProgress } from "./progress.js";
|
|
9
11
|
export * from "./fork.js";
|
|
10
12
|
export * from "./authors.js";
|
|
11
13
|
export * from "./presets.js";
|
|
12
14
|
export * from "./transactions.js";
|
|
13
15
|
export { createLifecycleSessionCache } from "./rpc.js";
|
|
16
|
+
export { launchLensAbi } from "./lens.js";
|
|
17
|
+
export { hookSaltMinerAbi } from "./mining.js";
|
|
14
18
|
export { planLaunch, simulateLaunchPlan, buildNextTransaction, prepareAndPlanLifecycleLaunch } from "./planner.js";
|
|
15
19
|
export { assertLaunchTopologyCoverage, getTickAtSqrtRatio, type LaunchTopologyCoverageInput, type LaunchTopologyPosition } from "../launch.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Address, type Hex } from "viem";
|
|
2
|
+
import { launchPlanV1Components, launchProgressV1Components, preparedMarketV1Components, marketLiveStateV1Components, type LaunchProgressV1, type PreparedMarketV1, type MarketLiveStateV1 } from "./schema.js";
|
|
3
|
+
import type { LifecycleBlock, LifecycleRpcClient } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/** ILaunchLens.sol, versioned planning snapshot. Its address is owned by the deployment preset. */
|
|
6
|
+
export const launchLensAbi = [{ type: "function", name: "snapshot", stateMutability: "view",
|
|
7
|
+
inputs: [{ name: "plan", type: "tuple", components: launchPlanV1Components }, { name: "owner", type: "address" },
|
|
8
|
+
{ name: "fundingEscrow", type: "address" }, { name: "tokens", type: "address[]" }, { name: "maxAttempts", type: "uint256" }],
|
|
9
|
+
outputs: [{ name: "result", type: "tuple", components: [
|
|
10
|
+
{ name: "chainId", type: "uint256" }, { name: "blockNumber", type: "uint256" },
|
|
11
|
+
{ name: "parentBlockHash", type: "bytes32" }, { name: "timestamp", type: "uint256" }, { name: "nativeBalance", type: "uint256" },
|
|
12
|
+
{ name: "tokenBalances", type: "tuple[]", components: [{ name: "token", type: "address" }, { name: "balance", type: "uint256" }, { name: "allowance", type: "uint256" }] },
|
|
13
|
+
{ name: "predictedToken", type: "address" }, { name: "launchId", type: "bytes32" },
|
|
14
|
+
{ name: "progress", type: "tuple", components: launchProgressV1Components },
|
|
15
|
+
{ name: "preparedMarkets", type: "tuple[]", components: [
|
|
16
|
+
{ name: "index", type: "uint32" }, { name: "adapter", type: "address" }, { name: "prepared", type: "tuple", components: preparedMarketV1Components },
|
|
17
|
+
{ name: "liveStateSuccess", type: "bool" }, { name: "live", type: "tuple", components: marketLiveStateV1Components }, { name: "liveStateError", type: "bytes" },
|
|
18
|
+
] },
|
|
19
|
+
{ name: "hookDeployments", type: "tuple[]", components: [
|
|
20
|
+
{ name: "marketIndex", type: "uint32" }, { name: "adapter", type: "address" }, { name: "deployer", type: "address" },
|
|
21
|
+
{ name: "constructorParameters", type: "bytes" }, { name: "initCodeHash", type: "bytes32" }, { name: "salt", type: "bytes32" }, { name: "hook", type: "address" }, { name: "found", type: "bool" },
|
|
22
|
+
] },
|
|
23
|
+
] }],
|
|
24
|
+
}] as const;
|
|
25
|
+
|
|
26
|
+
export type LifecycleLensSnapshot = {
|
|
27
|
+
chainId: bigint; blockNumber: bigint; parentBlockHash: Hex; timestamp: bigint; nativeBalance: bigint;
|
|
28
|
+
tokenBalances: readonly { token: Address; balance: bigint; allowance: bigint }[];
|
|
29
|
+
predictedToken: Address; launchId: Hex; progress: LaunchProgressV1;
|
|
30
|
+
preparedMarkets: readonly { index: number; adapter: Address; prepared: PreparedMarketV1; liveStateSuccess: boolean; live: MarketLiveStateV1; liveStateError: Hex }[];
|
|
31
|
+
hookDeployments: readonly { marketIndex: number; adapter: Address; deployer: Address; constructorParameters: Hex; initCodeHash: Hex; salt: Hex; hook: Address; found: boolean }[];
|
|
32
|
+
};
|
|
33
|
+
const snapshots = new WeakMap<LifecycleRpcClient, { blockHash: Hex; snapshot: LifecycleLensSnapshot }>();
|
|
34
|
+
|
|
35
|
+
export function observeLifecycleLens(client: LifecycleRpcClient, block: LifecycleBlock, snapshot: LifecycleLensSnapshot): void {
|
|
36
|
+
snapshots.set(client, { blockHash: block.hash.toLowerCase() as Hex, snapshot });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function lifecycleLensSnapshot(client: LifecycleRpcClient, block: LifecycleBlock): LifecycleLensSnapshot | undefined {
|
|
40
|
+
const observation = snapshots.get(client);
|
|
41
|
+
return observation?.snapshot.blockNumber === block.number && observation.blockHash === block.hash.toLowerCase() ? observation.snapshot : undefined;
|
|
42
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { parseAbi, type Address, type Hex } from "viem";
|
|
2
|
+
import { hasLifecycleV4HookPermissions, minePoolBoundHookSalt, predictPoolBoundHookAddress } from "./markets.js";
|
|
3
|
+
import { readLifecycleContract } from "./rpc.js";
|
|
4
|
+
import { LifecyclePlanningError, type LifecycleBlock, type LifecyclePoolBoundHookDeployment, type LifecycleRpcClient, type PoolBoundLifecyclePreparationProgress } from "./types.js";
|
|
5
|
+
import { lifecycleLensSnapshot } from "./lens.js";
|
|
6
|
+
|
|
7
|
+
export const hookSaltMinerAbi = parseAbi(["function findMany((address deployer, bytes32 initCodeHash, uint256 startSalt, uint160 flags)[] searches, uint256 maxAttempts) pure returns (bytes32[] salts, address[] hooks, bool[] found)"]);
|
|
8
|
+
|
|
9
|
+
/** Read-only bounded EVM mining; every returned winner is checked with local CREATE2. */
|
|
10
|
+
export async function mineLifecycleHooks(options: {
|
|
11
|
+
client: LifecycleRpcClient; block: LifecycleBlock; miner?: Address;
|
|
12
|
+
deployments: readonly LifecyclePoolBoundHookDeployment[]; signal?: AbortSignal;
|
|
13
|
+
onProgress?: (progress: PoolBoundLifecyclePreparationProgress) => void;
|
|
14
|
+
}): Promise<LifecyclePoolBoundHookDeployment[]> {
|
|
15
|
+
const { deployments, signal, onProgress, miner, client, block } = options;
|
|
16
|
+
const checkActive = () => { if (signal?.aborted) throw Object.assign(new Error("Lifecycle hook mining cancelled"), { name: "AbortError" }); };
|
|
17
|
+
checkActive();
|
|
18
|
+
if (miner === undefined) return Promise.all(deployments.map(async (deployment) => ({ ...deployment,
|
|
19
|
+
...await minePoolBoundHookSalt({ ...deployment, startSalt: BigInt(deployment.salt), signal,
|
|
20
|
+
onProgress: onProgress === undefined ? undefined : (progress) => onProgress({ marketIndex: deployment.marketIndex, ...progress }) }),
|
|
21
|
+
})));
|
|
22
|
+
const results = deployments.map((deployment) => ({ ...deployment }));
|
|
23
|
+
const lens = lifecycleLensSnapshot(client, block);
|
|
24
|
+
let pending: number[] = [];
|
|
25
|
+
for (const [index, deployment] of deployments.entries()) {
|
|
26
|
+
const observed = lens?.hookDeployments.find((hook) => hook.marketIndex === deployment.marketIndex);
|
|
27
|
+
if (observed === undefined) { pending.push(index); continue; }
|
|
28
|
+
const predictedHook = predictPoolBoundHookAddress({ ...deployment, salt: observed.salt });
|
|
29
|
+
if (observed.deployer.toLowerCase() !== deployment.deployer.toLowerCase() || observed.initCodeHash.toLowerCase() !== deployment.initCodeHash.toLowerCase() ||
|
|
30
|
+
BigInt(observed.salt) < BigInt(deployment.salt) || observed.found && (observed.hook.toLowerCase() !== predictedHook.toLowerCase() || !hasLifecycleV4HookPermissions(predictedHook))) {
|
|
31
|
+
throw new LifecyclePlanningError("INVALID_MINER_RESULT", "LaunchLens hook result differs from the locally derived constructor or CREATE2 address");
|
|
32
|
+
}
|
|
33
|
+
results[index] = { ...deployment, salt: observed.salt, predictedHook };
|
|
34
|
+
if (!observed.found) pending.push(index);
|
|
35
|
+
else onProgress?.({ marketIndex: deployment.marketIndex, salt: observed.salt, predictedHook, attempts: BigInt(observed.salt) - BigInt(deployment.salt) + 1n });
|
|
36
|
+
}
|
|
37
|
+
while (pending.length > 0) {
|
|
38
|
+
checkActive();
|
|
39
|
+
let maxAttempts = BigInt(Math.floor(150_000 / pending.length));
|
|
40
|
+
for (const index of pending) {
|
|
41
|
+
const available = (1n << 256n) - 1n - BigInt(results[index]!.salt);
|
|
42
|
+
if (available < maxAttempts) maxAttempts = available;
|
|
43
|
+
}
|
|
44
|
+
if (maxAttempts === 0n) {
|
|
45
|
+
pending = pending.filter((index) => {
|
|
46
|
+
const deployment = results[index]!;
|
|
47
|
+
if (BigInt(deployment.salt) !== (1n << 256n) - 1n) return true;
|
|
48
|
+
const predictedHook = predictPoolBoundHookAddress(deployment);
|
|
49
|
+
if (!hasLifecycleV4HookPermissions(predictedHook)) throw new LifecyclePlanningError("HOOK_SALT_EXHAUSTED", "No valid hook address remains in the uint256 salt range");
|
|
50
|
+
results[index] = { ...deployment, predictedHook };
|
|
51
|
+
onProgress?.({ marketIndex: deployment.marketIndex, salt: deployment.salt, predictedHook, attempts: BigInt(deployment.salt) - BigInt(deployments[index]!.salt) + 1n });
|
|
52
|
+
return false;
|
|
53
|
+
});
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const searches = pending.map((index) => ({ deployer: results[index]!.deployer, initCodeHash: results[index]!.initCodeHash, startSalt: BigInt(results[index]!.salt), flags: 0x1afcn }));
|
|
57
|
+
const [salts, hooks, found] = await readLifecycleContract<readonly [readonly Hex[], readonly Address[], readonly boolean[]]>(client, miner, hookSaltMinerAbi, "findMany", [searches, maxAttempts], block);
|
|
58
|
+
checkActive();
|
|
59
|
+
if (salts.length !== pending.length || hooks.length !== pending.length || found.length !== pending.length) throw new LifecyclePlanningError("INVALID_MINER_RESULT", "Hook miner omitted a search result");
|
|
60
|
+
const unfinished: number[] = [];
|
|
61
|
+
for (const [ordinal, index] of pending.entries()) {
|
|
62
|
+
const deployment = results[index]!;
|
|
63
|
+
const salt = salts[ordinal]!;
|
|
64
|
+
const end = searches[ordinal]!.startSalt + maxAttempts;
|
|
65
|
+
const predictedHook = predictPoolBoundHookAddress({ ...deployment, salt });
|
|
66
|
+
if (found[ordinal]) {
|
|
67
|
+
if (BigInt(salt) < searches[ordinal]!.startSalt || BigInt(salt) >= end || hooks[ordinal]!.toLowerCase() !== predictedHook.toLowerCase() || !hasLifecycleV4HookPermissions(predictedHook)) throw new LifecyclePlanningError("INVALID_MINER_RESULT", "Hook miner returned an invalid CREATE2 salt or permission mask");
|
|
68
|
+
} else {
|
|
69
|
+
if (BigInt(salt) !== end) throw new LifecyclePlanningError("INVALID_MINER_RESULT", "Hook miner did not advance to the next unsearched salt");
|
|
70
|
+
unfinished.push(index);
|
|
71
|
+
}
|
|
72
|
+
results[index] = { ...deployment, salt, predictedHook };
|
|
73
|
+
onProgress?.({ marketIndex: deployment.marketIndex, salt, predictedHook, attempts: BigInt(salt) - BigInt(deployments[index]!.salt) + (found[ordinal] ? 1n : 0n) });
|
|
74
|
+
checkActive();
|
|
75
|
+
}
|
|
76
|
+
pending = unfinished;
|
|
77
|
+
}
|
|
78
|
+
checkActive();
|
|
79
|
+
return results;
|
|
80
|
+
}
|
package/src/lifecycle/planner.ts
CHANGED
|
@@ -3,10 +3,10 @@ import { ABYSS_FEE_TIERS } from "../abyss.js";
|
|
|
3
3
|
import { assertLaunchTopologyCoverage, getTickAtSqrtRatio } from "../launch.js";
|
|
4
4
|
import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleErc20Abi } from "./abi.js";
|
|
5
5
|
import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, LIFECYCLE_MAX_ERC404_SUPPLY, parseLaunchPlan, serializeLaunchPlan, type LaunchPlanV1 } from "./schema.js";
|
|
6
|
-
import { readLaunchProgress, readLifecycleHeadProgress, readLifecycleProfiles, readLifecycleTokenAtBlock, readPoolBoundHookDeployment } from "./progress.js";
|
|
7
|
-
import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion,
|
|
8
|
-
import {
|
|
9
|
-
import { prepareLaunchSimulation, simulateLaunchTransactions, type NextTransactionEnvelope } from "./simulation.js";
|
|
6
|
+
import { readLaunchProgress, readLifecycleExecutionProgress, readLifecycleHeadProgress, readLifecycleProfiles, readLifecycleTokenAtBlock, readPoolBoundHookDeployment } from "./progress.js";
|
|
7
|
+
import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion, type AbyssLifecycleMarketConfig, type V4LifecycleMarketConfig, type V4PoolBoundLifecycleMarketConfig } from "./markets.js";
|
|
8
|
+
import { assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleRpc, lifecycleStage, readLifecycleChainId, readLifecycleContract, readLifecycleGasPrice, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
|
|
9
|
+
import { prepareLaunchSimulation, simulateLaunchTransactions, type GasSizingHints, type NextTransactionEnvelope } from "./simulation.js";
|
|
10
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";
|
|
11
11
|
import { deriveKnownPoolBoundHookDeployment, protectOrderedBuyMinimums, validateBuySlippageBps } from "./calibration.js";
|
|
12
12
|
import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
|
|
@@ -14,6 +14,8 @@ import { buildLaunchTransactions, packPreparationBatches } from "./transactions.
|
|
|
14
14
|
import { assertLifecycleMarketAllocation, assertLifecycleSupplyAllocation } from "./allocations.js";
|
|
15
15
|
import { assertUfgLaunchAccount, hashUfgLaunchConfig, UFG_LAUNCH_PROXY_ADDRESS } from "./ufg-config.js";
|
|
16
16
|
import { buildUfgLaunchTransactions, buildUfgCancelTransaction, validateUfgLaunchPlan } from "./ufg.js";
|
|
17
|
+
import { readLifecyclePlanningBlock } from "./snapshot.js";
|
|
18
|
+
import { mineLifecycleHooks } from "./mining.js";
|
|
17
19
|
|
|
18
20
|
type ValidatedPlanShape = {
|
|
19
21
|
commitment: Hex;
|
|
@@ -293,29 +295,26 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
|
|
|
293
295
|
fixed.gas !== heldEnvelope!.gas || fixed.gasPrice !== heldEnvelope!.gasPrice) throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Fixed final envelope changed during preparation");
|
|
294
296
|
};
|
|
295
297
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
296
|
-
const
|
|
298
|
+
const block = await readLifecyclePlanningBlock(client, draft, options.account, options.ufg !== undefined, true);
|
|
299
|
+
const chainId = await readLifecycleChainId(client);
|
|
297
300
|
if (chainId !== draft.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the economic plan being finalized");
|
|
298
301
|
const metadata = needsMining ? await Promise.all(draft.markets.map((market, marketIndex) => isPoolBoundV4ConfigVersion(market.configVersion)
|
|
299
302
|
? readPoolBoundHookDeployment({ client, plan: draft, marketIndex }, block) : undefined)) : [];
|
|
300
303
|
assertDraft();
|
|
301
304
|
const markets = [...draft.markets];
|
|
302
|
-
const minedDeployments
|
|
303
|
-
|
|
304
|
-
|
|
305
|
+
const minedDeployments = await mineLifecycleHooks({ client, block,
|
|
306
|
+
miner: getKnownLifecycleDeployment({ chainId, orchestrator: draft.orchestrator })?.hookSaltMiner,
|
|
307
|
+
deployments: metadata.flatMap((deployment, marketIndex) => deployment === undefined ? [] : [{ marketIndex, ...deployment }]),
|
|
308
|
+
signal: options.signal, onProgress: options.onProgress });
|
|
309
|
+
for (const mined of minedDeployments) {
|
|
305
310
|
assertDraft();
|
|
306
|
-
const
|
|
307
|
-
deployer: deployment.deployer, initCodeHash: deployment.initCodeHash, startSalt: BigInt(deployment.salt), signal: options.signal,
|
|
308
|
-
onProgress: options.onProgress === undefined ? undefined : (progress) => options.onProgress?.({ marketIndex, ...progress }),
|
|
309
|
-
});
|
|
311
|
+
const marketIndex = mined.marketIndex;
|
|
310
312
|
const market = markets[marketIndex]!;
|
|
311
313
|
if (!isPoolBoundV4ConfigVersion(market.configVersion)) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Mined market lost its explicit pool-bound version");
|
|
312
314
|
markets[marketIndex] = { ...market, config: encodePoolBoundV4LifecycleMarketConfig({ ...decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion), hookSalt: mined.salt }) };
|
|
313
|
-
minedDeployments.push({ marketIndex, deployer: deployment.deployer, initCodeHash: deployment.initCodeHash, ...mined });
|
|
314
315
|
}
|
|
315
316
|
assertDraft();
|
|
316
|
-
|
|
317
|
-
// start at a fresh post-mining snapshot and share it through final protection.
|
|
318
|
-
const executionBlock = needsMining ? await readLifecycleBlock(client) : block;
|
|
317
|
+
const executionBlock = block;
|
|
319
318
|
assertDraft();
|
|
320
319
|
const price = heldEnvelope === undefined ? undefined : heldEnvelope.gasPrice ?? rpcQuantity(await readLifecycleGasPrice(client), "gas price");
|
|
321
320
|
const baseFeeEnvelope = (executionBlock.baseFeePerGas ?? 0n) * 2n;
|
|
@@ -325,19 +324,21 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
|
|
|
325
324
|
});
|
|
326
325
|
let plan: LaunchPlanV1 = { ...draft, markets };
|
|
327
326
|
let ownedProgress: CanonicalLaunchProgress | undefined;
|
|
327
|
+
let sizing: GasSizingHints | undefined;
|
|
328
328
|
if (needsProtection) {
|
|
329
329
|
// A diagnostic minimum is never returned as an admitted or executable plan.
|
|
330
330
|
// Price guards, funding minima, recipients and all ordered budgets stay exact.
|
|
331
331
|
const diagnosticPlan = { ...plan, buys: plan.buys.map((buy) => ({ ...buy, minTokenOut: 1n })) };
|
|
332
|
-
const diagnostic = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan: diagnosticPlan }, client, executionBlock, undefined, true,
|
|
332
|
+
const diagnostic = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan: diagnosticPlan }, client, executionBlock, undefined, true, finalEnvelope));
|
|
333
333
|
assertDraft();
|
|
334
334
|
if (diagnostic.progress.canonical.phase !== LifecyclePhase.None || diagnostic.progress.head.phase !== LifecyclePhase.None) throw new LifecyclePlanningError("LAUNCH_ALREADY_STARTED", "Buy protection cannot replace an already committed launch plan");
|
|
335
335
|
plan = protectOrderedBuyMinimums(diagnostic, options.buySlippageBps!);
|
|
336
336
|
// This is the real SDK-owned None observation, not an assumed new-launch
|
|
337
337
|
// state. No onchain commitment existed at this exact snapshot to replace.
|
|
338
338
|
ownedProgress = { ...diagnostic.progress, planHash: hashLaunchPlan(plan) };
|
|
339
|
+
sizing = new Map(diagnostic.simulation.steps.map((step) => [step.transactionId, step.gasRequired ?? step.gasUsed!]));
|
|
339
340
|
}
|
|
340
|
-
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, executionBlock, ownedProgress, false,
|
|
341
|
+
const planned = await lifecycleStage(client, "plan", () => planLaunchWithReads({ ...options, plan }, client, executionBlock, ownedProgress, false, finalEnvelope, sizing));
|
|
341
342
|
assertDraft();
|
|
342
343
|
assertFinalPlan(planned, options.signal);
|
|
343
344
|
if (planned.progress.canonical.phase !== LifecyclePhase.Active && planned.progress.canonical.phase !== LifecyclePhase.Cancelled) {
|
|
@@ -365,12 +366,12 @@ export async function planLaunch(options: PlanLaunchOptions): Promise<PlannedLau
|
|
|
365
366
|
return result;
|
|
366
367
|
}
|
|
367
368
|
|
|
368
|
-
async function planLaunchWithReads(options: PlanLaunchOptions, client: LifecycleRpcClient, pinnedBlock?: LifecycleBlock, ownedProgress?: CanonicalLaunchProgress, diagnostic = false,
|
|
369
|
+
async function planLaunchWithReads(options: PlanLaunchOptions, client: LifecycleRpcClient, pinnedBlock?: LifecycleBlock, ownedProgress?: CanonicalLaunchProgress, diagnostic = false, fixedFinalEnvelope?: NextTransactionEnvelope, sizing?: GasSizingHints): Promise<PlannedLaunch> {
|
|
369
370
|
const { plan, account, mode, ufg } = options;
|
|
370
371
|
const { commitment: planHash, marketConfigs, ufgConfigHash } = validatePlanningIntent(options);
|
|
371
372
|
const launchId = hashLaunchIdentity(plan);
|
|
372
373
|
const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
|
|
373
|
-
pinnedBlock ??
|
|
374
|
+
pinnedBlock ?? readLifecyclePlanningBlock(client, plan, account, ufg !== undefined), readLifecycleChainId(client),
|
|
374
375
|
]));
|
|
375
376
|
if (chainId !== plan.chainId) throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain differs from the committed plan");
|
|
376
377
|
// Only preparation/build-next can supply freshly observed progress from this
|
|
@@ -413,11 +414,11 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
413
414
|
hookDeployments.push({ marketIndex, ...(known?.deployment ?? await readPoolBoundHookDeployment({ client, plan, marketIndex }, block)) });
|
|
414
415
|
}
|
|
415
416
|
const waiting = { ...placeholder, confidence: "stateful" as const, reason: !progress.confirmationSafe ? "Canonical confirmed progress/account nonce has not caught up with head or pending transactions; wait for the selected confirmation depth" : "Wait for canonical receipt confirmations or resolve replacement before requesting another transaction" };
|
|
416
|
-
|
|
417
|
+
|
|
417
418
|
return { ...planned, hookDeployments, simulation: waiting };
|
|
418
419
|
}
|
|
419
420
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) {
|
|
420
|
-
|
|
421
|
+
|
|
421
422
|
return { ...planned, simulation: { ...placeholder, confidence: "stateful", executionProof: "proved", protocolFit: "proved", admitted: true, reason: "Canonical launch is terminal; no transaction remains" } };
|
|
422
423
|
}
|
|
423
424
|
// Topology is a creation policy, not a retroactive obstacle to recovery of
|
|
@@ -432,43 +433,30 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
432
433
|
const prepared = progress.canonical.preparedMarkets;
|
|
433
434
|
const computeCap = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
|
|
434
435
|
let batches: number[] = [];
|
|
435
|
-
if (mode === "staged") batches =
|
|
436
|
-
: [...(prepared > 0 ? [prepared] : []), ...(prepared < plan.markets.length ? [plan.markets.length - prepared] : [])];
|
|
437
|
-
if (reviewed !== undefined && reviewed.kind === "prepare") {
|
|
438
|
-
// Canonical progress fixes the start; the exact held calldata fixes its span.
|
|
439
|
-
// Never rediscover a batch or enlarge a reviewed transaction.
|
|
440
|
-
const match = /^prepare:(\d+):(\d+)$/.exec(reviewed.id);
|
|
441
|
-
const start = Number(match?.[1]), count = Number(match?.[2]);
|
|
442
|
-
if (start !== prepared || !Number.isSafeInteger(count) || count <= 0 || start + count > plan.markets.length) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation differs from canonical unfinished progress");
|
|
443
|
-
batches = [...(prepared > 0 ? [prepared] : []), count, ...(start + count < plan.markets.length ? [plan.markets.length - start - count] : [])];
|
|
444
|
-
}
|
|
436
|
+
if (mode === "staged") batches = packPreparationBatches(plan, prepared, computeCap);
|
|
445
437
|
let transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg);
|
|
446
438
|
if (fixedFinalEnvelope !== undefined && (transactions.length !== 1 || transactions[0]!.kind !== "atomic")) {
|
|
447
439
|
throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Fixed final envelope requires one immediate atomic launch with no preceding approvals");
|
|
448
440
|
}
|
|
449
|
-
if (reviewed !== undefined) {
|
|
450
|
-
if (!matchesReviewedNextTransaction(transactions[0], reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
|
|
451
|
-
transactions = transactions.slice(0, 1);
|
|
452
|
-
}
|
|
453
441
|
if (metadata.reasons.length > 0) {
|
|
454
|
-
|
|
442
|
+
|
|
455
443
|
return { ...planned, simulation: { ...placeholder, confidence: "stateful", reason: metadata.reasons.join("; "), failureCategory: "semantic" }, transactions, totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
|
|
456
444
|
}
|
|
457
|
-
const envelope = diagnostic ? undefined :
|
|
458
|
-
const preparedSimulation = prepareLaunchSimulation({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, block, true, envelope, fixedFinalEnvelope?.gasPrice);
|
|
445
|
+
const envelope = diagnostic ? undefined : fixedFinalEnvelope;
|
|
446
|
+
const preparedSimulation = prepareLaunchSimulation({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, block, true, envelope, fixedFinalEnvelope?.gasPrice, sizing);
|
|
459
447
|
try {
|
|
460
448
|
const execute = diagnostic ? preparedSimulation.measure : (candidate: readonly LifecycleTransaction[]) => preparedSimulation(candidate, envelope);
|
|
461
449
|
let simulation = await execute(transactions);
|
|
462
450
|
assertFinalPlan(planned, options.signal);
|
|
463
451
|
if (mode === "staged") {
|
|
464
|
-
// Only
|
|
465
|
-
//
|
|
466
|
-
while (
|
|
452
|
+
// Only pressure at the native ceiling can invalidate packing. A smaller
|
|
453
|
+
// affordability/RPC/account envelope is not evidence for another wallet write.
|
|
454
|
+
while (!simulation.admitted && simulation.failedTransactionId?.startsWith("prepare:")) {
|
|
467
455
|
const failedIndex = transactions.findIndex((transaction) => transaction.id === simulation.failedTransactionId);
|
|
468
456
|
const failedTransaction = transactions[failedIndex];
|
|
469
457
|
const step = simulation.steps.find((candidate) => candidate.transactionId === simulation.failedTransactionId);
|
|
470
458
|
const count = failedTransaction?.marketCount ?? 0;
|
|
471
|
-
if (count <= 1 || step?.
|
|
459
|
+
if (count <= 1 || step?.gasUsed === undefined || step.gasLimit === undefined || step.gasLimit < limits.executionGasCeiling || step.gasUsed * 100n < limits.executionGasCeiling * 95n) break;
|
|
472
460
|
let first = 0;
|
|
473
461
|
const batchIndex = batches.findIndex((batch) => { const matches = first === failedTransaction?.marketStart; first += batch; return matches; });
|
|
474
462
|
const left = Math.ceil(count / 2);
|
|
@@ -481,9 +469,6 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
481
469
|
} else if (!diagnostic && !simulation.admitted && !(simulation.capacityConstraint === "compute" && simulation.confidence === "provisional")) {
|
|
482
470
|
simulation = { ...simulation, reason: `${simulation.reason ?? "Exact atomic plan is not admitted"}; choose staged explicitly only if empty preparation can be partitioned and complete activation fits` };
|
|
483
471
|
}
|
|
484
|
-
if (reviewed !== undefined && !matchesReviewedNextTransaction(transactions[0], reviewed)) {
|
|
485
|
-
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
|
|
486
|
-
}
|
|
487
472
|
return { ...planned, simulation, transactions: attachEstimates(transactions, simulation), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
|
|
488
473
|
} finally { preparedSimulation.close(); }
|
|
489
474
|
}
|
|
@@ -506,7 +491,6 @@ async function preflightNextTransaction(options: BuildNextTransactionOptions, ne
|
|
|
506
491
|
...(next.nonce === undefined ? {} : { nonce: toHex(next.nonce) }),
|
|
507
492
|
}, "latest"]), "submission gas estimate");
|
|
508
493
|
if (estimated <= 0n || estimated > next.gas) throw new Error("Submission RPC gas estimate exceeds the exact reviewed envelope or is zero");
|
|
509
|
-
if (rpcQuantity(await lifecycleRpc(submissionClient, "eth_chainId"), "submission chain ID") !== simulation.chainId) throw new Error("Active submission RPC chain changed during preflight");
|
|
510
494
|
assertPlanningActive(options.signal);
|
|
511
495
|
} catch (failure) {
|
|
512
496
|
if (failure instanceof Error && failure.name === "AbortError") throw failure;
|
|
@@ -528,7 +512,7 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
|
|
|
528
512
|
const reviewed = supplied === undefined ? undefined : Object.freeze({ id: supplied.id, kind: supplied.kind, chainId: supplied.chainId,
|
|
529
513
|
from: supplied.from, to: supplied.to, data: supplied.data, value: supplied.value, gas: supplied.gas, gasPrice: supplied.gasPrice, nonce: supplied.nonce });
|
|
530
514
|
const result = await withLifecycleReadClient(options.client, async (client) => {
|
|
531
|
-
const block = await
|
|
515
|
+
const block = await readLifecyclePlanningBlock(client, options.planned.plan, options.planned.account, options.planned.ufg !== undefined);
|
|
532
516
|
const limitSource = options.limits === undefined ? options.planned.limits : options.limits;
|
|
533
517
|
if (getKnownLifecycleDeployment({ chainId: options.planned.chainId, orchestrator: options.planned.plan.orchestrator }) !== undefined && typeof limitSource !== "function") {
|
|
534
518
|
await Promise.all([
|
|
@@ -537,12 +521,12 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
|
|
|
537
521
|
resolveLifecycleLimits({ client, account: options.planned.account, orchestrator: options.planned.plan.orchestrator, block, chainId: options.planned.chainId }, limitSource),
|
|
538
522
|
]);
|
|
539
523
|
}
|
|
540
|
-
const progress = await
|
|
524
|
+
const progress = await readLifecycleExecutionProgress({ ...options, client }, block);
|
|
541
525
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) {
|
|
542
526
|
// Only the confirmed canonical terminal phase is terminal; a head-only
|
|
543
527
|
// Active observation is not, and the confirmation/nonce/receipt gates below
|
|
544
528
|
// remain in force for every nonterminal case.
|
|
545
|
-
|
|
529
|
+
|
|
546
530
|
return undefined;
|
|
547
531
|
}
|
|
548
532
|
if (!progress.confirmationSafe) throw new LifecyclePlanningError("UNCONFIRMED_STATE", "Wait for confirmed canonical progress and account nonce, including untracked approvals/replacements, before constructing the next transaction");
|
|
@@ -562,14 +546,44 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
|
|
|
562
546
|
if (next === undefined) throw new LifecyclePlanningError("MISSING_GAS_PROOF", "Cancellation omitted its exact simulation admission", simulation);
|
|
563
547
|
return preflightNextTransaction(options, next, simulation, reviewed);
|
|
564
548
|
}
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
549
|
+
const { planned } = options;
|
|
550
|
+
assertFinalPlan(planned, options.signal);
|
|
551
|
+
const funding = progress.canonical.phase === LifecyclePhase.None
|
|
552
|
+
? await readFunding(client, planned.plan, block, planned.account, planned.ufg !== undefined)
|
|
553
|
+
: { prerequisites: [], approvals: [] };
|
|
554
|
+
const prepared = progress.canonical.preparedMarkets;
|
|
555
|
+
let batches: number[] = [];
|
|
556
|
+
if (planned.mode === "staged") {
|
|
557
|
+
let suffixStart = prepared;
|
|
558
|
+
const prefix = prepared > 0 ? [prepared] : [];
|
|
559
|
+
if (reviewed?.kind === "prepare") {
|
|
560
|
+
const span = /^prepare:(\d+):(\d+)$/.exec(reviewed.id);
|
|
561
|
+
const first = Number(span?.[1]), count = Number(span?.[2]);
|
|
562
|
+
if (first !== prepared || !Number.isSafeInteger(count) || count <= 0 || first + count > planned.plan.markets.length) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation does not start at the canonical unfinished market");
|
|
563
|
+
prefix.push(count);
|
|
564
|
+
suffixStart += count;
|
|
565
|
+
}
|
|
566
|
+
const suffix = planned.transactions
|
|
567
|
+
.filter((transaction) => transaction.kind === "prepare" && transaction.marketStart! + transaction.marketCount! > suffixStart)
|
|
568
|
+
.map((transaction) => transaction.marketStart! + transaction.marketCount! - Math.max(transaction.marketStart!, suffixStart));
|
|
569
|
+
if (suffix.length === 0 && suffixStart < planned.plan.markets.length) {
|
|
570
|
+
const { limits } = await resolveLifecycleLimits({ client, account: planned.account, orchestrator: planned.plan.orchestrator, block, chainId: planned.chainId }, limitSource);
|
|
571
|
+
const cap = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
|
|
572
|
+
suffix.push(...packPreparationBatches(planned.plan, suffixStart, cap).slice(suffixStart > 0 ? 1 : 0));
|
|
573
|
+
}
|
|
574
|
+
batches = [...prefix, ...suffix];
|
|
575
|
+
}
|
|
576
|
+
const candidate = orderedTransactions(planned.plan, planned.mode, progress, funding.approvals, batches, planned.ufg)[0];
|
|
577
|
+
if (candidate === undefined) return undefined;
|
|
578
|
+
if (reviewed !== undefined && !matchesReviewedNextTransaction(candidate, reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
|
|
579
|
+
const observed = planned.simulation.executionProof === "proved" ? planned.simulation.steps.find((step) => step.transactionId === candidate.id && step.success) : undefined;
|
|
580
|
+
const compute = observed?.computeGasRequired;
|
|
581
|
+
const sizing: GasSizingHints | undefined = reviewed !== undefined || compute === undefined ? undefined : new Map([[candidate.id, compute]]);
|
|
582
|
+
const simulation = await simulateLaunchTransactions({ client, planned, limits: limitSource, fork: options.fork, signal: options.signal }, [candidate], block, reviewed, sizing);
|
|
583
|
+
assertFinalPlan(planned, options.signal);
|
|
584
|
+
if (!simulation.admitted) throw new LifecyclePlanningError("PLAN_NOT_ADMITTED", simulation.reason ?? "Next exact launch transaction is not admitted", simulation);
|
|
585
|
+
const next = attachEstimates([candidate], simulation)[0]!;
|
|
586
|
+
return preflightNextTransaction(options, next, simulation, reviewed);
|
|
573
587
|
});
|
|
574
588
|
assertFinalPlan(options.planned, options.signal);
|
|
575
589
|
if (result !== undefined) assertReviewedTransactionUnchanged(options, reviewed, result);
|
package/src/lifecycle/presets.ts
CHANGED
|
@@ -61,6 +61,11 @@ export type LifecycleDeploymentPreset = {
|
|
|
61
61
|
readonly tokenFactory: Address;
|
|
62
62
|
readonly tokenFactoryCodeHash: Hex;
|
|
63
63
|
readonly fundingEscrow: Address;
|
|
64
|
+
/** Optional SDK-owned read-only acceleration contracts. */
|
|
65
|
+
readonly hookSaltMiner?: Address;
|
|
66
|
+
readonly launchLens?: Address;
|
|
67
|
+
/** Frozen reviewed runtime identity, like tokenFactoryCodeHash; not live admission. */
|
|
68
|
+
readonly launchLensCodeHash?: Hex;
|
|
64
69
|
readonly profiles: readonly LifecycleProfilePreset[];
|
|
65
70
|
readonly provenance: {
|
|
66
71
|
readonly sourceCommit: string;
|
|
@@ -325,6 +330,11 @@ const knownDeployment: LifecycleDeploymentPreset = Object.freeze({
|
|
|
325
330
|
tokenFactory: "0xd3cE64E49224a9a96075633f63761FE6BE22FE30",
|
|
326
331
|
tokenFactoryCodeHash: "0x6bbbdfe93ec615b7f1e70bac5e69aa890eba5e5f248f25e7022e866b4064d541",
|
|
327
332
|
fundingEscrow: "0x7bc77946CeF52A178583FCe1EEB7751983cEC703",
|
|
333
|
+
hookSaltMiner: "0x2f06176ba739b609b6a7334e2e6647558ba78ad5",
|
|
334
|
+
// LaunchLens/871f9385, L2 deployment block 100095353.
|
|
335
|
+
// tx 0xafdf35fa50d2c3fe08f1ab231a4e5d5e4dd5629c57f14dfe0a3aff5b0b8404ff
|
|
336
|
+
launchLens: "0xcb4bb3caca71fe52eace8170cf83e901418646f8",
|
|
337
|
+
launchLensCodeHash: "0xc19e86bfed47afbb6ff719293edd8fb8bb3399d5d017b3f533d90cc72e58f1d3",
|
|
328
338
|
profiles: Object.freeze([
|
|
329
339
|
abyssPreset(KnownLifecycleProfile.Abyss0, "0x49b2ed65e2021247951193dd2c671b40867b6e064af65a2d98a70f57556412fc"),
|
|
330
340
|
abyssPreset(KnownLifecycleProfile.Abyss1, "0x2f6f494f8810473b3c0ac6b03b1a1353d067ffa5c86d3cba16b04c6b61f73c05"),
|