@black-market/sdk 0.10.0 → 0.12.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 +21 -2
- package/dist/lifecycle/abi.d.ts +1 -0
- package/dist/lifecycle/abi.d.ts.map +1 -1
- package/dist/lifecycle/abi.js +1 -0
- package/dist/lifecycle/abi.js.map +1 -1
- package/dist/lifecycle/calibration.d.ts.map +1 -1
- package/dist/lifecycle/calibration.js +2 -0
- package/dist/lifecycle/calibration.js.map +1 -1
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.d.ts +71 -0
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.d.ts.map +1 -0
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.js +73 -0
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.js.map +1 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.d.ts +71 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.d.ts.map +1 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.js +73 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.js.map +1 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +1 -0
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/markets.d.ts +3 -1
- package/dist/lifecycle/markets.d.ts.map +1 -1
- package/dist/lifecycle/markets.js +20 -3
- package/dist/lifecycle/markets.js.map +1 -1
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +83 -54
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +9 -2
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +26 -5
- package/dist/lifecycle/presets.js.map +1 -1
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +26 -5
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.js +3 -2
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/simulation.d.ts +1 -1
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +119 -175
- package/dist/lifecycle/simulation.js.map +1 -1
- package/dist/lifecycle/transactions.d.ts +6 -0
- package/dist/lifecycle/transactions.d.ts.map +1 -1
- package/dist/lifecycle/transactions.js +39 -0
- package/dist/lifecycle/transactions.js.map +1 -1
- package/dist/lifecycle/types.d.ts +17 -4
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/lifecycle/ufg-abi.d.ts +2386 -0
- package/dist/lifecycle/ufg-abi.d.ts.map +1 -0
- package/dist/lifecycle/ufg-abi.js +171 -0
- package/dist/lifecycle/ufg-abi.js.map +1 -0
- package/dist/lifecycle/ufg-config.d.ts +38 -0
- package/dist/lifecycle/ufg-config.d.ts.map +1 -0
- package/dist/lifecycle/ufg-config.js +75 -0
- package/dist/lifecycle/ufg-config.js.map +1 -0
- package/dist/lifecycle/ufg.d.ts +21 -0
- package/dist/lifecycle/ufg.d.ts.map +1 -0
- package/dist/lifecycle/ufg.js +98 -0
- package/dist/lifecycle/ufg.js.map +1 -0
- package/docs/launch-lifecycle.md +215 -32
- package/examples/.env.example +4 -0
- package/examples/README.md +35 -2
- package/examples/launch-example-support.ts +1 -0
- package/examples/launch-example.ts +1 -0
- package/examples/launch.ts +1 -0
- package/examples/ufg-launch-config.ts +31 -0
- package/package.json +1 -1
- package/src/lifecycle/abi.ts +1 -0
- package/src/lifecycle/calibration.ts +1 -0
- package/src/lifecycle/deployments/ufg-dynamic-pool-20261021.ts +74 -0
- package/src/lifecycle/deployments/ufg-fixed-pool-20261021.ts +74 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/markets.ts +16 -3
- package/src/lifecycle/planner.ts +75 -51
- package/src/lifecycle/presets.ts +27 -6
- package/src/lifecycle/progress.ts +22 -5
- package/src/lifecycle/rpc.ts +3 -2
- package/src/lifecycle/simulation.ts +114 -166
- package/src/lifecycle/transactions.ts +38 -1
- package/src/lifecycle/types.ts +15 -5
- package/src/lifecycle/ufg-abi.ts +173 -0
- package/src/lifecycle/ufg-config.ts +77 -0
- package/src/lifecycle/ufg.ts +86 -0
|
@@ -43,6 +43,7 @@ export function validateBuySlippageBps(bps: number): void {
|
|
|
43
43
|
export function protectOrderedBuyMinimums(planned: PlannedLaunch, bps: number): LaunchPlanV1 {
|
|
44
44
|
validateBuySlippageBps(bps);
|
|
45
45
|
const { simulation, plan } = planned;
|
|
46
|
+
if (simulation.deferredTransactionIds.length !== 0) throw new LifecyclePlanningError("LAUNCH_DIAGNOSTIC_FAILED", "Buy slippage calibration requires actual activation outputs beyond the proven RPC prefix; use a higher aggregate simulation cap/provider or preserve already committed minima without buySlippageBps", simulation);
|
|
46
47
|
if (simulation.confidence !== "stateful" || simulation.backend === "unavailable" || simulation.steps.length !== planned.transactions.length || simulation.steps.some((step, index) => !step.success || step.transactionId !== planned.transactions[index]?.id)) throw new LifecyclePlanningError("LAUNCH_DIAGNOSTIC_FAILED", simulation.reason ?? "The complete diagnostic launch sequence did not execute successfully", simulation);
|
|
47
48
|
const activation = planned.transactions.find((transaction) => transaction.kind === "atomic" || transaction.kind === "activate");
|
|
48
49
|
const step = simulation.steps.find((candidate) => candidate.transactionId === activation?.id);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { LaunchEnvelopeV2, AdapterRegistrationV1, ProfileRegistrationV1, ProfileTopologyV1 } from "../schema.js";
|
|
2
|
+
|
|
3
|
+
// Generated by scripts/generate-ufg-presets.mjs from ufg-registry-readback.json.
|
|
4
|
+
// Robinhood block 94551140, 0x0778956b5a7bad65f8182b969ac4efc6a7182a0a922fbcb94dc87bfea26e9cc4. Creation code is byte-identical to the existing dynamic zero-royalty hook.
|
|
5
|
+
export const ufgDynamicEnvelope = Object.freeze({
|
|
6
|
+
artifactDigest: "0xd448a4117b78714cc4f96824356c6a90ec666449fde5c57e2b7bbe072f5e4a7a",
|
|
7
|
+
reviewManifestDigest: "0x0a3a0dfcae4fa51b2cccb2213e44800b7e3b74e681a66bc090f2e403a15b64b3",
|
|
8
|
+
configBoundsDigest: "0x72e0b2d542556d472a47f9c38b2c91079c72793af847dee77933f0eca203129d",
|
|
9
|
+
termsDigest: "0xbe3b09dc6d49fd548e76fed654b852039e8899e591ebacafc5e67f0692d0c699",
|
|
10
|
+
topology: 2,
|
|
11
|
+
configVersion: 6,
|
|
12
|
+
economicVersion: 3,
|
|
13
|
+
capabilities: 123n,
|
|
14
|
+
flags: 0n,
|
|
15
|
+
callbackFlags: 6908,
|
|
16
|
+
callbackMask: 16383,
|
|
17
|
+
protocolTreasury: "0x961981916AB6575C3af3eeCecbf6A3b7Fad7C9e1",
|
|
18
|
+
protocolFeeDenominator: 6,
|
|
19
|
+
beneficiary: "0x7cb44b8693b3C350A2BcD4b681aF85365aa27783",
|
|
20
|
+
maximumDeveloperFeeBps: 0,
|
|
21
|
+
bounds: Object.freeze({
|
|
22
|
+
minimumTickSpacing: 1,
|
|
23
|
+
maximumTickSpacing: 32767,
|
|
24
|
+
maximumPositions: 32,
|
|
25
|
+
maximumOracleCardinality: 4096,
|
|
26
|
+
feeModeFlags: 3,
|
|
27
|
+
}),
|
|
28
|
+
graph: Object.freeze({
|
|
29
|
+
manager: "0x8366a39CC670B4001A1121B8F6A443A643e40951",
|
|
30
|
+
hookRoot: "0x0000000000000000000000000000000000000000",
|
|
31
|
+
oracleFactory: "0xe7feF2BC860B25bbdEB6F6AB96d88bAAa77ddad7",
|
|
32
|
+
locker: "0xDa3bdeD722Bc45126752Ad6f28FAf27917D1a2fa",
|
|
33
|
+
collectorFactory: "0x46a8493b8f84E3Da412856Dd256263107e3873F2",
|
|
34
|
+
collectorDeployer: "0x50d6C10435dD928e426B8094FB944582c4FCa6f7",
|
|
35
|
+
hookDeployer: "0x435E0cb9b4C244edD5Bed40F9e39acdfcCd8d0f3",
|
|
36
|
+
coreCodeHash: "0x9fb8f46c3b980b9db25dfdb487a69a308a86be145860269b4036a4e74480787b",
|
|
37
|
+
managerCodeHash: "0xbd3881180b547f5fe817545743cfb4343e96b1bc6640dcd70c106b0066e95626",
|
|
38
|
+
hookRuntimeCodeHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
39
|
+
oracleFactoryCodeHash: "0x974fb5c72d91ccb99f1eb4679a5d4432c82bbaa0906b1c2917ec4da3499b7b98",
|
|
40
|
+
lockerCodeHash: "0x686024936a31a60193d2c6ea15ddb97ee36fc59a805eb1856cde61116075b370",
|
|
41
|
+
collectorFactoryCodeHash: "0xb0bd33507bebe1bb969153961237ee9a5661dd9ed60b9384a8869fa03ec5ed51",
|
|
42
|
+
collectorDeployerCodeHash: "0x79118bf755c242e2a190f9602c021e59ccc49d51eeff3b0c1524b4a53c25618d",
|
|
43
|
+
hookDeployerCodeHash: "0xe2a9753e6a8858b32443cd32b89c0ac9de95673a85611383d2a7bebc3b9d1187",
|
|
44
|
+
hookCreationCodeHash: "0xd448a4117b78714cc4f96824356c6a90ec666449fde5c57e2b7bbe072f5e4a7a",
|
|
45
|
+
codeChunk0: "0x550187621373bcece198E44EA31bE6b3936c699b",
|
|
46
|
+
codeChunk0Hash: "0x818c56bf10ae21129fe54f1b8991d6f807ce4ccef35b34c56251c1fd2d7d623f",
|
|
47
|
+
codeChunk1: "0x6C6c05e1C61c69C85548887e5E06384934AE7d71",
|
|
48
|
+
codeChunk1Hash: "0xbc73944bfa7e74df5968907a05fe2579a17ab155d3a0db407588d1baff51b4de",
|
|
49
|
+
sharedHookSalt: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
50
|
+
}),
|
|
51
|
+
}) satisfies LaunchEnvelopeV2;
|
|
52
|
+
export const ufgDynamicAdapter = Object.freeze({
|
|
53
|
+
implementation: "0x38FAA8061c91BA6B2D3ef735f110017d12705067",
|
|
54
|
+
codeHash: "0xa13c2d0e05e0cc6a366f85ecbfab37ef26136e0993999bed4dd46538eabce23c",
|
|
55
|
+
capabilities: 123n,
|
|
56
|
+
configVersion: 6,
|
|
57
|
+
enabled: true,
|
|
58
|
+
}) satisfies AdapterRegistrationV1;
|
|
59
|
+
export const ufgDynamicRegistration = Object.freeze({
|
|
60
|
+
adapterId: "0xc0906e17625b48240a87a3afc6ee921b653bdacd72906a16b359d0776d938518",
|
|
61
|
+
configSchema: "0x51c5ca14c40012f78425cd1afb1ea77980da3fed0490a00911a8a4ed34792045",
|
|
62
|
+
dependencyDigest: "0x43078cffe54d685efc2c8e05243c50956569c98fd608861b6ed81f6ab1ad49b3",
|
|
63
|
+
venue: "0x8366a39CC670B4001A1121B8F6A443A643e40951",
|
|
64
|
+
factory: "0x0000000000000000000000000000000000000000",
|
|
65
|
+
hook: "0x0000000000000000000000000000000000000000",
|
|
66
|
+
capabilities: 123n,
|
|
67
|
+
enabled: true,
|
|
68
|
+
}) satisfies ProfileRegistrationV1;
|
|
69
|
+
export const ufgDynamicTopology = Object.freeze({
|
|
70
|
+
hookTopology: 2,
|
|
71
|
+
configVersion: 6,
|
|
72
|
+
hookDeployer: "0x435E0cb9b4C244edD5Bed40F9e39acdfcCd8d0f3",
|
|
73
|
+
hookCreationCodeHash: "0xd448a4117b78714cc4f96824356c6a90ec666449fde5c57e2b7bbe072f5e4a7a",
|
|
74
|
+
}) satisfies ProfileTopologyV1;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { LaunchEnvelopeV2, AdapterRegistrationV1, ProfileRegistrationV1, ProfileTopologyV1 } from "../schema.js";
|
|
2
|
+
|
|
3
|
+
// Generated by scripts/generate-ufg-presets.mjs from ufg-registry-readback.json.
|
|
4
|
+
// Robinhood block 94551140, 0x0778956b5a7bad65f8182b969ac4efc6a7182a0a922fbcb94dc87bfea26e9cc4. Creation code is byte-identical to the existing fixed zero-royalty hook.
|
|
5
|
+
export const ufgFixedEnvelope = Object.freeze({
|
|
6
|
+
artifactDigest: "0x11c4c7b1f265ed708b7d53ab828c89d237ca821333daa808f1913986c6f8f1ab",
|
|
7
|
+
reviewManifestDigest: "0x0a3a0dfcae4fa51b2cccb2213e44800b7e3b74e681a66bc090f2e403a15b64b3",
|
|
8
|
+
configBoundsDigest: "0x72e0b2d542556d472a47f9c38b2c91079c72793af847dee77933f0eca203129d",
|
|
9
|
+
termsDigest: "0xf2503b91eeec87f95c4f5e47df517a143422c766528c403ea44bdbd9b40df10e",
|
|
10
|
+
topology: 2,
|
|
11
|
+
configVersion: 6,
|
|
12
|
+
economicVersion: 3,
|
|
13
|
+
capabilities: 123n,
|
|
14
|
+
flags: 0n,
|
|
15
|
+
callbackFlags: 6908,
|
|
16
|
+
callbackMask: 16383,
|
|
17
|
+
protocolTreasury: "0x961981916AB6575C3af3eeCecbf6A3b7Fad7C9e1",
|
|
18
|
+
protocolFeeDenominator: 6,
|
|
19
|
+
beneficiary: "0x7cb44b8693b3C350A2BcD4b681aF85365aa27783",
|
|
20
|
+
maximumDeveloperFeeBps: 0,
|
|
21
|
+
bounds: Object.freeze({
|
|
22
|
+
minimumTickSpacing: 1,
|
|
23
|
+
maximumTickSpacing: 32767,
|
|
24
|
+
maximumPositions: 32,
|
|
25
|
+
maximumOracleCardinality: 4096,
|
|
26
|
+
feeModeFlags: 3,
|
|
27
|
+
}),
|
|
28
|
+
graph: Object.freeze({
|
|
29
|
+
manager: "0x8366a39CC670B4001A1121B8F6A443A643e40951",
|
|
30
|
+
hookRoot: "0x0000000000000000000000000000000000000000",
|
|
31
|
+
oracleFactory: "0xe7feF2BC860B25bbdEB6F6AB96d88bAAa77ddad7",
|
|
32
|
+
locker: "0xfbc0AbCD1A3EA65aF2196fa8Af1327a49B4C92FC",
|
|
33
|
+
collectorFactory: "0x46a8493b8f84E3Da412856Dd256263107e3873F2",
|
|
34
|
+
collectorDeployer: "0x50d6C10435dD928e426B8094FB944582c4FCa6f7",
|
|
35
|
+
hookDeployer: "0x4b04Ed1B02885DE905d8a14207EAC93F6AB231b2",
|
|
36
|
+
coreCodeHash: "0x9fb8f46c3b980b9db25dfdb487a69a308a86be145860269b4036a4e74480787b",
|
|
37
|
+
managerCodeHash: "0xbd3881180b547f5fe817545743cfb4343e96b1bc6640dcd70c106b0066e95626",
|
|
38
|
+
hookRuntimeCodeHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
39
|
+
oracleFactoryCodeHash: "0x974fb5c72d91ccb99f1eb4679a5d4432c82bbaa0906b1c2917ec4da3499b7b98",
|
|
40
|
+
lockerCodeHash: "0xa6fd4decc88671feb632d1871688a96582b46fd500b53f587d0fe18dad23fc2d",
|
|
41
|
+
collectorFactoryCodeHash: "0xb0bd33507bebe1bb969153961237ee9a5661dd9ed60b9384a8869fa03ec5ed51",
|
|
42
|
+
collectorDeployerCodeHash: "0x79118bf755c242e2a190f9602c021e59ccc49d51eeff3b0c1524b4a53c25618d",
|
|
43
|
+
hookDeployerCodeHash: "0xc97b52314cfc0225fcc0a901382898870e57394b318124956367d8fd3818506c",
|
|
44
|
+
hookCreationCodeHash: "0x11c4c7b1f265ed708b7d53ab828c89d237ca821333daa808f1913986c6f8f1ab",
|
|
45
|
+
codeChunk0: "0x94Fd6fB6E3db16Ef97c9bEEA229617b2Ec00c196",
|
|
46
|
+
codeChunk0Hash: "0xe5f56287276954b21f4211cabf2d9c251aae68836c5fa86ce68091ffccdee26b",
|
|
47
|
+
codeChunk1: "0x191F43334Ed2D7d154BF635E961BCb0e9848CB7c",
|
|
48
|
+
codeChunk1Hash: "0x76efd61e1a109a85140ce3473c43694a83f490eaac54d18673c17872a5262a47",
|
|
49
|
+
sharedHookSalt: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
50
|
+
}),
|
|
51
|
+
}) satisfies LaunchEnvelopeV2;
|
|
52
|
+
export const ufgFixedAdapter = Object.freeze({
|
|
53
|
+
implementation: "0x3c7bb8541DBe2C8576E1000C4B6B803fDD36CA5d",
|
|
54
|
+
codeHash: "0x27079feaeb1a0ce8034588c46d15fead2a6407451d6f9e37059cade7c2df7610",
|
|
55
|
+
capabilities: 123n,
|
|
56
|
+
configVersion: 6,
|
|
57
|
+
enabled: true,
|
|
58
|
+
}) satisfies AdapterRegistrationV1;
|
|
59
|
+
export const ufgFixedRegistration = Object.freeze({
|
|
60
|
+
adapterId: "0x073a4d46af70a3d4f69641e4f9dcdc9d8c279cd443daff960bbdf65a3b5fc758",
|
|
61
|
+
configSchema: "0x51c5ca14c40012f78425cd1afb1ea77980da3fed0490a00911a8a4ed34792045",
|
|
62
|
+
dependencyDigest: "0xead7c65bd240844e961647f7cd059948afd384dfea490807f576325cad78aee0",
|
|
63
|
+
venue: "0x8366a39CC670B4001A1121B8F6A443A643e40951",
|
|
64
|
+
factory: "0x0000000000000000000000000000000000000000",
|
|
65
|
+
hook: "0x0000000000000000000000000000000000000000",
|
|
66
|
+
capabilities: 123n,
|
|
67
|
+
enabled: true,
|
|
68
|
+
}) satisfies ProfileRegistrationV1;
|
|
69
|
+
export const ufgFixedTopology = Object.freeze({
|
|
70
|
+
hookTopology: 2,
|
|
71
|
+
configVersion: 6,
|
|
72
|
+
hookDeployer: "0x4b04Ed1B02885DE905d8a14207EAC93F6AB231b2",
|
|
73
|
+
hookCreationCodeHash: "0x11c4c7b1f265ed708b7d53ab828c89d237ca821333daa808f1913986c6f8f1ab",
|
|
74
|
+
}) satisfies ProfileTopologyV1;
|
package/src/lifecycle/index.ts
CHANGED
package/src/lifecycle/markets.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { concatHex, decodeAbiParameters, encodeAbiParameters, getAddress, hexToB
|
|
|
2
2
|
import type { MarketConfigV1 } from "./schema.js";
|
|
3
3
|
import { LifecyclePlanningError, type LifecycleProfile, type PoolBoundHookSaltProgress } from "./types.js";
|
|
4
4
|
import { decodeHmmLaunchTail, encodeHmmLaunchTail, isHmmProfile, type HmmLaunchConfig } from "./hmm.js";
|
|
5
|
+
import { decodeUfgLaunchConfig, encodeUfgLaunchConfig, isUfgProfile, type UfgLaunchConfig } from "./ufg-config.js";
|
|
5
6
|
|
|
6
7
|
export type V4LifecyclePositionConfig = {
|
|
7
8
|
tickLower: number; tickUpper: number; liquidity: bigint; salt: Hex; maxTokenAmount: bigint;
|
|
@@ -44,6 +45,7 @@ export type V4PoolBoundLifecycleMarketConfigV5 = Omit<V4LifecycleMarketConfig, "
|
|
|
44
45
|
export type V4PoolBoundLifecycleMarketConfigV6 = Omit<V4LifecycleMarketConfig, "version"> & {
|
|
45
46
|
version: 6; minimumHookFeePips: number; feeSensitivityPipsSecondsPerTick: number; hookSalt: Hex;
|
|
46
47
|
hmmLaunchConfig?: HmmLaunchConfig;
|
|
48
|
+
ufgLaunchConfig?: UfgLaunchConfig;
|
|
47
49
|
};
|
|
48
50
|
export type V4PoolBoundLifecycleMarketConfig = V4PoolBoundLifecycleMarketConfigV5 | V4PoolBoundLifecycleMarketConfigV6;
|
|
49
51
|
export const V4_LIFECYCLE_CONFIG_SCHEMA = keccak256(stringToHex("(uint16,uint24,int24,uint160,uint24,uint8,uint8,address,bool,bytes32,bytes32,bytes32,address,uint16,(int24,int24,uint128,bytes32,uint256)[])"));
|
|
@@ -90,9 +92,16 @@ function assertV1HookFeeShape(config: object): void {
|
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
/**
|
|
95
|
+
/** Auxiliary launch profiles require their typed tail and append it automatically. */
|
|
94
96
|
export function encodePoolBoundV4LifecycleMarketConfig(config: V4PoolBoundLifecycleMarketConfig): Hex {
|
|
95
97
|
const prefix = encodePoolBoundV4Prefix(config);
|
|
98
|
+
if (isUfgProfile(config.profileId)) {
|
|
99
|
+
if (config.version !== 6 || config.ufgLaunchConfig === undefined) throw new LifecyclePlanningError("INVALID_UFG_CONFIG", "UFG profiles require config6 and an explicit ufgLaunchConfig");
|
|
100
|
+
if (config.hmmLaunchConfig !== undefined) throw new LifecyclePlanningError("INVALID_UFG_CONFIG", "UFG profiles cannot carry an HMM tail");
|
|
101
|
+
if (config.lpFeePips !== 0 || config.feeMode !== 1) throw new LifecyclePlanningError("INVALID_UFG_CONFIG", "UFG requires QuoteOnly feeMode 1 and zero LP fee");
|
|
102
|
+
return concatHex([prefix, encodeUfgLaunchConfig(config.ufgLaunchConfig)]);
|
|
103
|
+
}
|
|
104
|
+
if (config.version === 6 && config.ufgLaunchConfig !== undefined) throw new LifecyclePlanningError("INVALID_UFG_CONFIG", "A UFG launch tail requires a UFG profile");
|
|
96
105
|
if (isHmmProfile(config.profileId)) {
|
|
97
106
|
if (config.version !== 6 || config.hmmLaunchConfig === undefined) throw new LifecyclePlanningError("INVALID_HMM_CONFIG", "HMM profiles require config6 and an explicit hmmLaunchConfig");
|
|
98
107
|
if (config.lpFeePips !== 0) throw new LifecyclePlanningError("INVALID_HOOK_FEES", "HMM profiles require zero LP fee");
|
|
@@ -126,6 +135,10 @@ export function decodePoolBoundV4LifecycleMarketConfig(encoded: Hex, expectedVer
|
|
|
126
135
|
const prefixLength = encodePoolBoundV4Prefix(config).length;
|
|
127
136
|
config.hmmLaunchConfig = decodeHmmLaunchTail(`0x${encoded.slice(prefixLength)}`);
|
|
128
137
|
}
|
|
138
|
+
if (isUfgProfile(config.profileId)) {
|
|
139
|
+
if (config.version !== 6) throw new LifecyclePlanningError("INVALID_UFG_CONFIG", "UFG profiles require config6");
|
|
140
|
+
config.ufgLaunchConfig = decodeUfgLaunchConfig(`0x${encoded.slice(encodePoolBoundV4Prefix(config).length)}`);
|
|
141
|
+
}
|
|
129
142
|
if (encodePoolBoundV4LifecycleMarketConfig(config).toLowerCase() !== encoded.toLowerCase()) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Pool-bound config is not canonical for its explicit schema");
|
|
130
143
|
return config;
|
|
131
144
|
}
|
|
@@ -245,8 +258,8 @@ export function hashPoolBoundV4MarketCommitment(options: { chainId: bigint; core
|
|
|
245
258
|
if (!isPoolBoundV4ConfigVersion(market.configVersion)) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Pool-bound V4 market requires explicit config version 5 or 6");
|
|
246
259
|
const config = decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion);
|
|
247
260
|
if (config.profileId.toLowerCase() !== market.profileId.toLowerCase()) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Config profile differs from the committed market profile");
|
|
248
|
-
//
|
|
249
|
-
// The full plan commitment separately binds every byte of
|
|
261
|
+
// Collector factories commit only the V6 prefix, not auxiliary launch tails.
|
|
262
|
+
// The full plan commitment separately binds every byte of those tails.
|
|
250
263
|
const configHash = keccak256(encodePoolBoundV4Prefix({ ...config, hookSalt: zeroHash }));
|
|
251
264
|
return keccak256(encodeAbiParameters([
|
|
252
265
|
{ type: "bytes32" }, { type: "uint256" }, { type: "address" }, { type: "address" }, { type: "address" },
|
package/src/lifecycle/planner.ts
CHANGED
|
@@ -10,11 +10,14 @@ import { prepareLaunchSimulation, simulateLaunchTransactions, type NextTransacti
|
|
|
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";
|
|
13
|
-
import { buildLaunchTransactions } from "./transactions.js";
|
|
13
|
+
import { buildLaunchTransactions, packPreparationBatches } from "./transactions.js";
|
|
14
14
|
import { assertLifecycleMarketAllocation, assertLifecycleSupplyAllocation } from "./allocations.js";
|
|
15
|
+
import { assertUfgLaunchAccount, hashUfgLaunchConfig, UFG_LAUNCH_PROXY_ADDRESS } from "./ufg-config.js";
|
|
16
|
+
import { buildUfgLaunchTransactions, buildUfgCancelTransaction, validateUfgLaunchPlan } from "./ufg.js";
|
|
15
17
|
|
|
16
18
|
type ValidatedPlanShape = {
|
|
17
19
|
commitment: Hex;
|
|
20
|
+
ufgConfigHash?: Hex;
|
|
18
21
|
marketConfigs: (AbyssLifecycleMarketConfig | V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig)[];
|
|
19
22
|
};
|
|
20
23
|
|
|
@@ -75,19 +78,19 @@ function validatePlanShape(plan: LaunchPlanV1): ValidatedPlanShape {
|
|
|
75
78
|
return { commitment, marketConfigs };
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
async function readFunding(client: LifecycleRpcClient, plan: LaunchPlanV1, block: LifecycleBlock): Promise<{ prerequisites: LifecycleFundingPrerequisite[]; approvals: LifecycleTransaction[] }> {
|
|
81
|
+
async function readFunding(client: LifecycleRpcClient, plan: LaunchPlanV1, block: LifecycleBlock, account = plan.creator, ufg = false): Promise<{ prerequisites: LifecycleFundingPrerequisite[]; approvals: LifecycleTransaction[] }> {
|
|
79
82
|
if (plan.funding.length === 0) return { prerequisites: [], approvals: [] };
|
|
80
83
|
const preset = getKnownLifecycleDeployment({ chainId: plan.chainId, orchestrator: plan.orchestrator });
|
|
81
|
-
const escrow = preset?.fundingEscrow ?? await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "fundingEscrow", [], block);
|
|
84
|
+
const escrow = ufg ? UFG_LAUNCH_PROXY_ADDRESS : preset?.fundingEscrow ?? await readLifecycleContract<Address>(client, plan.orchestrator, launchLifecycleAbi, "fundingEscrow", [], block);
|
|
82
85
|
// Only allowance observations construct approval commands. Core execution
|
|
83
86
|
// enforces input/output asset, funding target, balance and conversion predicates.
|
|
84
87
|
const prerequisites = await Promise.all(plan.funding.map(async (funding): Promise<LifecycleFundingPrerequisite> => {
|
|
85
88
|
const native = funding.kind === LifecycleFundingKind.NativeWrap || funding.kind === LifecycleFundingKind.Swap && funding.inputAsset === zeroAddress;
|
|
86
89
|
const [inputBalance, allowance] = native
|
|
87
|
-
? [rpcQuantity(await lifecyclePinnedRpc(client, "eth_getBalance", [
|
|
90
|
+
? [rpcQuantity(await lifecyclePinnedRpc(client, "eth_getBalance", [account, toHex(block.number)], block), "creator native balance"), undefined] as const
|
|
88
91
|
: await Promise.all([
|
|
89
|
-
readLifecycleContract<bigint>(client, funding.inputAsset, lifecycleErc20Abi, "balanceOf", [
|
|
90
|
-
readLifecycleContract<bigint>(client, funding.inputAsset, lifecycleErc20Abi, "allowance", [
|
|
92
|
+
readLifecycleContract<bigint>(client, funding.inputAsset, lifecycleErc20Abi, "balanceOf", [account], block),
|
|
93
|
+
readLifecycleContract<bigint>(client, funding.inputAsset, lifecycleErc20Abi, "allowance", [account, escrow], block),
|
|
91
94
|
]);
|
|
92
95
|
return { funding, inputBalance, requiredInput: funding.inputAmount, allowance, spender: escrow, nativeValue: native ? funding.inputAmount : 0n, conversion: funding.kind === LifecycleFundingKind.NativeWrap ? "native-wrap" : funding.kind === LifecycleFundingKind.Swap ? "allowlisted-swap" : "none" };
|
|
93
96
|
}));
|
|
@@ -103,8 +106,8 @@ async function readFunding(client: LifecycleRpcClient, plan: LaunchPlanV1, block
|
|
|
103
106
|
const approvals: LifecycleTransaction[] = [];
|
|
104
107
|
for (const total of totals.values()) {
|
|
105
108
|
if (total.allowance >= total.amount) continue;
|
|
106
|
-
if (total.allowance > 0n) approvals.push({ id: `approve-reset:${total.asset}`, kind: "approve-reset", chainId: Number(plan.chainId), from:
|
|
107
|
-
approvals.push({ id: `approve:${total.asset}`, kind: "approve", chainId: Number(plan.chainId), from:
|
|
109
|
+
if (total.allowance > 0n) approvals.push({ id: `approve-reset:${total.asset}`, kind: "approve-reset", chainId: Number(plan.chainId), from: account, to: total.asset, data: encodeFunctionData({ abi: lifecycleErc20Abi, functionName: "approve", args: [escrow, 0n] }), value: 0n, dependencies: [], postconditions: [{ kind: "allowance", asset: total.asset, spender: escrow, minimum: 0n, exact: 0n }] });
|
|
110
|
+
approvals.push({ id: `approve:${total.asset}`, kind: "approve", chainId: Number(plan.chainId), from: account, to: total.asset, data: encodeFunctionData({ abi: lifecycleErc20Abi, functionName: "approve", args: [escrow, total.amount] }), value: 0n, dependencies: [], postconditions: [{ kind: "allowance", asset: total.asset, spender: escrow, minimum: total.amount }] });
|
|
108
111
|
}
|
|
109
112
|
return { prerequisites, approvals };
|
|
110
113
|
}
|
|
@@ -132,12 +135,12 @@ async function readPlanMetadata(client: LifecycleRpcClient, plan: LaunchPlanV1,
|
|
|
132
135
|
return { profiles, hookDeployments, reasons: live.filter((profile) => !profile.admitted).map((profile) => profile.reason ?? "Unlisted lifecycle profile is unavailable") };
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
function orderedTransactions(plan: LaunchPlanV1, mode: "atomic" | "staged", progress: CanonicalLaunchProgress, approvals: readonly LifecycleTransaction[],
|
|
138
|
+
function orderedTransactions(plan: LaunchPlanV1, mode: "atomic" | "staged", progress: CanonicalLaunchProgress, approvals: readonly LifecycleTransaction[], batches: readonly number[], ufg?: PlanLaunchOptions["ufg"]): LifecycleTransaction[] {
|
|
136
139
|
if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) return [];
|
|
137
140
|
const prepared = progress.canonical.preparedMarkets;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
const commands =
|
|
141
|
+
const compiled = ufg === undefined ? buildLaunchTransactions({ plan, mode, preparationBatches: mode === "staged" ? batches : undefined })
|
|
142
|
+
: buildUfgLaunchTransactions({ plan, config: ufg.config, mode, preparationBatches: mode === "staged" ? batches : undefined });
|
|
143
|
+
const commands = compiled.filter((transaction) =>
|
|
141
144
|
(transaction.kind !== "begin" && transaction.kind !== "atomic" || progress.canonical.phase === LifecyclePhase.None) &&
|
|
142
145
|
(transaction.kind !== "prepare" || (transaction.marketStart ?? 0) >= prepared));
|
|
143
146
|
const transactions = progress.canonical.phase === LifecyclePhase.None ? [...approvals, ...commands] : commands;
|
|
@@ -147,7 +150,8 @@ function orderedTransactions(plan: LaunchPlanV1, mode: "atomic" | "staged", prog
|
|
|
147
150
|
function attachEstimates(transactions: readonly LifecycleTransaction[], simulation: LifecycleSimulation): LifecycleTransaction[] {
|
|
148
151
|
return transactions.map((transaction) => {
|
|
149
152
|
const estimate = simulation.steps.find((step) => step.transactionId === transaction.id)?.estimate;
|
|
150
|
-
return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate,
|
|
153
|
+
return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate,
|
|
154
|
+
admission: estimate === undefined ? undefined : { admitted: simulation.admitted, confidence: simulation.confidence, executionProof: simulation.executionProof, protocolFit: simulation.protocolFit, transportPreflight: simulation.transportPreflight, blockNumber: simulation.blockNumber, blockHash: simulation.blockHash, account: simulation.account, chainId: simulation.chainId, reason: simulation.reason, limits: simulation.limits } };
|
|
151
155
|
});
|
|
152
156
|
}
|
|
153
157
|
type ReviewedNextTransaction = Pick<LifecycleTransaction, "id" | "kind" | "chainId" | "from" | "to" | "data" | "value"> & NextTransactionEnvelope;
|
|
@@ -176,7 +180,8 @@ function matchesReviewedNextTransaction(next: LifecycleTransaction | ReviewedNex
|
|
|
176
180
|
}
|
|
177
181
|
|
|
178
182
|
function assertReviewedTransactionUnchanged(options: BuildNextTransactionOptions, reviewed: ReviewedNextTransaction | undefined, next: LifecycleTransaction): void {
|
|
179
|
-
if (options.planned.
|
|
183
|
+
if (options.planned.ufg !== undefined) assertUfgLaunchAccount(options.planned.plan, options.planned.account);
|
|
184
|
+
if (options.planned.account.toLowerCase() !== next.from.toLowerCase() || options.planned.ufg === undefined && options.planned.plan.creator.toLowerCase() !== next.from.toLowerCase()) {
|
|
180
185
|
throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account changed during fresh execution or submission proof");
|
|
181
186
|
}
|
|
182
187
|
if (options.planned.chainId !== BigInt(next.chainId) || options.planned.plan.chainId !== BigInt(next.chainId)) {
|
|
@@ -203,6 +208,7 @@ async function readTokenFactoryBinding(client: LifecycleRpcClient, orchestrator:
|
|
|
203
208
|
|
|
204
209
|
function assertPlannedDeploymentBindings(stored: PlannedLaunch, current: PlannedLaunch): void {
|
|
205
210
|
if (hashLaunchPlan(stored.plan).toLowerCase() !== stored.planHash.toLowerCase() || current.planHash.toLowerCase() !== stored.planHash.toLowerCase() || current.launchId.toLowerCase() !== stored.launchId.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored economic plan no longer matches its finalized commitment");
|
|
211
|
+
if (stored.ufg === undefined ? current.ufg !== undefined : current.ufg === undefined || hashUfgLaunchConfig(stored.ufg.config) !== stored.ufg.configHash || current.ufg.configHash !== stored.ufg.configHash) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored UFG route or treasury config no longer matches its finalized commitment");
|
|
206
212
|
if (current.predictedToken.toLowerCase() !== stored.predictedToken.toLowerCase() || current.tokenFactory.toLowerCase() !== stored.tokenFactory.toLowerCase() || current.tokenFactoryCodeHash.toLowerCase() !== stored.tokenFactoryCodeHash.toLowerCase()) throw new LifecyclePlanningError("TOKEN_FACTORY_BINDING", "Final token prediction or its immutable factory code binding changed before execution");
|
|
207
213
|
if (!current.simulation.admitted || current.progress.canonical.phase === LifecyclePhase.Active || current.progress.canonical.phase === LifecyclePhase.Cancelled) return;
|
|
208
214
|
if (stored.hookDeployments.length !== current.hookDeployments.length || stored.hookDeployments.some((deployment, index) => {
|
|
@@ -219,9 +225,11 @@ function validatePlanningIntent(options: PlanLaunchOptions): ValidatedPlanShape
|
|
|
219
225
|
const { plan, account, mode } = options;
|
|
220
226
|
assertPlanningActive(options.signal);
|
|
221
227
|
if (mode !== "atomic" && mode !== "staged") throw new LifecyclePlanningError("EXPLICIT_MODE_REQUIRED", "Choose atomic or staged explicitly; staged is never silently substituted");
|
|
222
|
-
if (
|
|
228
|
+
if (options.ufg !== undefined) assertUfgLaunchAccount(plan, account);
|
|
229
|
+
else if (account.toLowerCase() !== plan.creator.toLowerCase()) throw new LifecyclePlanningError("ACCOUNT_MISMATCH", "Execution account must be the committed creator/payer/refund account");
|
|
223
230
|
if (plan.chainId > BigInt(Number.MAX_SAFE_INTEGER) || plan.chainId <= 0n) throw new LifecyclePlanningError("CHAIN_MISMATCH", "Chain ID must be a positive exact JavaScript integer");
|
|
224
|
-
|
|
231
|
+
const shape = validatePlanShape(plan);
|
|
232
|
+
return options.ufg === undefined ? shape : { ...shape, ufgConfigHash: hashUfgLaunchConfig(options.ufg.config) };
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
function validatePlanTopology(options: PlanLaunchOptions, marketConfigs: ValidatedPlanShape["marketConfigs"], token: Address): void {
|
|
@@ -257,12 +265,13 @@ function validatePlanTopology(options: PlanLaunchOptions, marketConfigs: Validat
|
|
|
257
265
|
function assertFinalPlan(planned: PlannedLaunch, signal?: AbortSignal): void {
|
|
258
266
|
assertPlanningActive(signal);
|
|
259
267
|
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");
|
|
268
|
+
if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash) throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed during asynchronous planning");
|
|
260
269
|
}
|
|
261
270
|
|
|
262
271
|
/** One owner constructs and mines, optionally measures actual ordered buy outputs,
|
|
263
272
|
* then proves only the final protected commitment. No cached execution certificates. */
|
|
264
273
|
export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifecycleLaunchOptions): Promise<PlannedLaunch> {
|
|
265
|
-
const { commitment } = validatePlanningIntent(options);
|
|
274
|
+
const { commitment, ufgConfigHash } = validatePlanningIntent(options);
|
|
266
275
|
if (options.buySlippageBps !== undefined) validateBuySlippageBps(options.buySlippageBps);
|
|
267
276
|
const needsMining = options.plan.markets.some((market) => isPoolBoundV4ConfigVersion(market.configVersion));
|
|
268
277
|
const needsProtection = options.buySlippageBps !== undefined && options.plan.buys.length !== 0;
|
|
@@ -271,6 +280,7 @@ export async function prepareAndPlanLifecycleLaunch(options: PrepareAndPlanLifec
|
|
|
271
280
|
const assertDraft = () => {
|
|
272
281
|
assertPlanningActive(options.signal);
|
|
273
282
|
if (hashLaunchPlan(options.plan).toLowerCase() !== commitment.toLowerCase()) throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed during asynchronous preparation; previous construction is invalid");
|
|
283
|
+
if ((options.ufg === undefined ? undefined : hashUfgLaunchConfig(options.ufg.config)) !== ufgConfigHash) throw new LifecyclePlanningError("PLAN_MUTATED", "UFG config changed during asynchronous preparation");
|
|
274
284
|
};
|
|
275
285
|
return withLifecycleReadClient(options.client, async (client) => {
|
|
276
286
|
const [block, chainId] = await Promise.all([readLifecycleBlock(client), readLifecycleChainId(client)]);
|
|
@@ -335,8 +345,8 @@ export async function planLaunch(options: PlanLaunchOptions): Promise<PlannedLau
|
|
|
335
345
|
}
|
|
336
346
|
|
|
337
347
|
async function planLaunchWithReads(options: PlanLaunchOptions, client: LifecycleRpcClient, pinnedBlock?: LifecycleBlock, ownedProgress?: CanonicalLaunchProgress, diagnostic = false, reviewed?: ReviewedNextTransaction): Promise<PlannedLaunch> {
|
|
338
|
-
const { plan, account, mode } = options;
|
|
339
|
-
const { commitment: planHash, marketConfigs } = validatePlanningIntent(options);
|
|
348
|
+
const { plan, account, mode, ufg } = options;
|
|
349
|
+
const { commitment: planHash, marketConfigs, ufgConfigHash } = validatePlanningIntent(options);
|
|
340
350
|
const launchId = hashLaunchIdentity(plan);
|
|
341
351
|
const [block, chainId] = await lifecycleStage(client, "plan.context", () => Promise.all([
|
|
342
352
|
pinnedBlock ?? readLifecycleBlock(client), readLifecycleChainId(client),
|
|
@@ -357,15 +367,17 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
357
367
|
resolveLifecycleLimits({ client, account, orchestrator: plan.orchestrator, block, chainId: plan.chainId }, options.limits),
|
|
358
368
|
lifecycleStage(client, "plan.progress", async () => {
|
|
359
369
|
const predictedToken = await readLifecycleTokenAtBlock(client, plan, block);
|
|
360
|
-
const identity = { plan, planHash, launchId, predictedToken, account, mode, confirmations: options.confirmations ?? 1
|
|
370
|
+
const identity = { plan, planHash, launchId, predictedToken, account, mode, confirmations: options.confirmations ?? 1,
|
|
371
|
+
...(ufg === undefined ? {} : { ufg: { config: ufg.config, configHash: ufgConfigHash! } }) };
|
|
372
|
+
if (ufg !== undefined) validateUfgLaunchPlan(plan, ufg.config, predictedToken);
|
|
361
373
|
const progress = ownedProgress ?? await readLaunchProgress({ client, planned: identity, receipts: options.receipts, signal: options.signal }, block);
|
|
362
374
|
if (ownedProgress !== undefined && progress.token.toLowerCase() !== predictedToken.toLowerCase()) throw new LifecyclePlanningError("TOKEN_IDENTITY", "Predicted token changed after its canonical progress was read");
|
|
363
375
|
return { identity, progress };
|
|
364
376
|
}),
|
|
365
377
|
]));
|
|
366
378
|
assertPlanningActive(options.signal);
|
|
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: [] };
|
|
368
|
-
let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, simulation: placeholder,
|
|
379
|
+
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: [], deferredTransactionIds: [] };
|
|
380
|
+
let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, simulation: placeholder, limits: options.limits,
|
|
369
381
|
allowTopologyGaps: options.allowTopologyGaps === true, allowMissingPermanentTail: options.allowMissingPermanentTail === true };
|
|
370
382
|
const receiptsPending = progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed");
|
|
371
383
|
if (!progress.confirmationSafe || receiptsPending) {
|
|
@@ -388,53 +400,63 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
|
|
|
388
400
|
// immutable onchain commitments. Waiting and terminal launches returned above.
|
|
389
401
|
if (progress.canonical.phase === LifecyclePhase.None && progress.head.phase === LifecyclePhase.None) validatePlanTopology(options, marketConfigs, identity.predictedToken);
|
|
390
402
|
const [funding, metadata] = await lifecycleStage(client, "plan.inputs", () => Promise.all([
|
|
391
|
-
progress.canonical.phase === LifecyclePhase.None ? readFunding(client, plan, block) : { prerequisites: [], approvals: [] },
|
|
403
|
+
progress.canonical.phase === LifecyclePhase.None ? readFunding(client, plan, block, ufg === undefined ? plan.creator : account, ufg !== undefined) : { prerequisites: [], approvals: [] },
|
|
392
404
|
readPlanMetadata(client, plan, identity.predictedToken, block),
|
|
393
405
|
]));
|
|
394
406
|
assertFinalPlan(planned, options.signal);
|
|
395
407
|
planned = { ...planned, prerequisites: funding.prerequisites, profiles: metadata.profiles, hookDeployments: metadata.hookDeployments };
|
|
396
|
-
|
|
397
|
-
|
|
408
|
+
const prepared = progress.canonical.preparedMarkets;
|
|
409
|
+
const computeCap = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
|
|
410
|
+
let batches: number[] = [];
|
|
411
|
+
if (mode === "staged") batches = reviewed === undefined ? packPreparationBatches(plan, prepared, computeCap)
|
|
412
|
+
: [...(prepared > 0 ? [prepared] : []), ...(prepared < plan.markets.length ? [plan.markets.length - prepared] : [])];
|
|
413
|
+
if (reviewed !== undefined && reviewed.kind === "prepare") {
|
|
414
|
+
// Canonical progress fixes the start; the exact held calldata fixes its span.
|
|
415
|
+
// Never rediscover a batch or enlarge a reviewed transaction.
|
|
416
|
+
const match = /^prepare:(\d+):(\d+)$/.exec(reviewed.id);
|
|
417
|
+
const start = Number(match?.[1]), count = Number(match?.[2]);
|
|
418
|
+
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");
|
|
419
|
+
batches = [...(prepared > 0 ? [prepared] : []), count, ...(start + count < plan.markets.length ? [plan.markets.length - start - count] : [])];
|
|
420
|
+
}
|
|
421
|
+
let transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg);
|
|
422
|
+
if (reviewed !== undefined) {
|
|
423
|
+
if (!matchesReviewedNextTransaction(transactions[0], reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
|
|
424
|
+
transactions = transactions.slice(0, 1);
|
|
425
|
+
}
|
|
398
426
|
if (metadata.reasons.length > 0) {
|
|
399
427
|
await assertLifecycleBlock(client, block, plan.chainId);
|
|
400
428
|
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) };
|
|
401
429
|
}
|
|
402
|
-
const preparedSimulation = prepareLaunchSimulation({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, block);
|
|
430
|
+
const preparedSimulation = prepareLaunchSimulation({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, block, true, reviewed);
|
|
403
431
|
try {
|
|
404
|
-
const execute = diagnostic ? preparedSimulation.measure : (candidate: readonly LifecycleTransaction[]) =>
|
|
405
|
-
if (reviewed === undefined) return preparedSimulation(candidate);
|
|
406
|
-
if (!matchesReviewedNextTransaction(candidate[0], reviewed)) {
|
|
407
|
-
// A fresh prepare batch may still require SDK-owned capacity discovery.
|
|
408
|
-
// Never use held fees for a different step or caller-selected calldata.
|
|
409
|
-
if (candidate[0]?.kind === "prepare" && reviewed.kind === "prepare") return preparedSimulation(candidate);
|
|
410
|
-
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
|
|
411
|
-
}
|
|
412
|
-
return preparedSimulation(candidate, reviewed);
|
|
413
|
-
};
|
|
432
|
+
const execute = diagnostic ? preparedSimulation.measure : (candidate: readonly LifecycleTransaction[]) => preparedSimulation(candidate, reviewed);
|
|
414
433
|
let simulation = await execute(transactions);
|
|
415
434
|
assertFinalPlan(planned, options.signal);
|
|
416
435
|
if (mode === "staged") {
|
|
417
|
-
// Only
|
|
418
|
-
//
|
|
419
|
-
while (!simulation.admitted && simulation.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
simulation.
|
|
423
|
-
const
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
|
|
436
|
+
// Only a measurement that consumed almost its entire legal envelope
|
|
437
|
+
// invalidates packing estimates, including gas hidden by CREATE wrappers.
|
|
438
|
+
while (reviewed === undefined && !simulation.admitted && simulation.failedTransactionId?.startsWith("prepare:")) {
|
|
439
|
+
const failedIndex = transactions.findIndex((transaction) => transaction.id === simulation.failedTransactionId);
|
|
440
|
+
const failedTransaction = transactions[failedIndex];
|
|
441
|
+
const step = simulation.steps.find((candidate) => candidate.transactionId === simulation.failedTransactionId);
|
|
442
|
+
const count = failedTransaction?.marketCount ?? 0;
|
|
443
|
+
if (count <= 1 || step?.estimate !== undefined || step?.gasUsed === undefined || step.gasLimit === undefined || step.gasUsed * 100n < step.gasLimit * 95n) break;
|
|
444
|
+
let first = 0;
|
|
445
|
+
const batchIndex = batches.findIndex((batch) => { const matches = first === failedTransaction?.marketStart; first += batch; return matches; });
|
|
446
|
+
const left = Math.ceil(count / 2);
|
|
447
|
+
batches.splice(batchIndex, 1, left, count - left);
|
|
448
|
+
transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches, ufg);
|
|
427
449
|
simulation = await execute(transactions);
|
|
428
450
|
assertFinalPlan(planned, options.signal);
|
|
429
451
|
}
|
|
430
452
|
if (!simulation.admitted && simulation.failedTransactionId === "activate") simulation = { ...simulation, reason: `${simulation.reason ?? "Final activation failed"}; final mint/lock/all-buys/public-opening is indivisible and will not be partitioned` };
|
|
431
|
-
} else if (!diagnostic && !simulation.admitted) {
|
|
453
|
+
} else if (!diagnostic && !simulation.admitted && !(simulation.capacityConstraint === "compute" && simulation.confidence === "provisional")) {
|
|
432
454
|
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` };
|
|
433
455
|
}
|
|
434
456
|
if (reviewed !== undefined && !matchesReviewedNextTransaction(transactions[0], reviewed)) {
|
|
435
457
|
throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
|
|
436
458
|
}
|
|
437
|
-
return { ...planned, simulation,
|
|
459
|
+
return { ...planned, simulation, transactions: attachEstimates(transactions, simulation), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
|
|
438
460
|
} finally { preparedSimulation.close(); }
|
|
439
461
|
}
|
|
440
462
|
|
|
@@ -498,7 +520,9 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
|
|
|
498
520
|
if (options.action === "cancel") {
|
|
499
521
|
if (progress.canonical.phase === LifecyclePhase.None) throw new LifecyclePlanningError("NOT_STARTED", "An unstarted plan has no launch escrow to cancel");
|
|
500
522
|
const { planned } = options;
|
|
501
|
-
|
|
523
|
+
assertFinalPlan(planned, options.signal);
|
|
524
|
+
const transaction: LifecycleTransaction = planned.ufg !== undefined ? buildUfgCancelTransaction({ plan: planned.plan, preparedMarkets: progress.canonical.preparedMarkets })
|
|
525
|
+
: { id: "cancel", kind: "cancel", chainId: Number(planned.chainId), from: planned.account, to: planned.plan.orchestrator, data: encodeFunctionData({ abi: launchLifecycleAbi, functionName: "cancelLaunch", args: [planned.plan] }), value: 0n, dependencies: [], postconditions: [{ kind: "launch", phase: "Cancelled", preparedMarkets: progress.canonical.preparedMarkets }] };
|
|
502
526
|
if (reviewed !== undefined && !matchesReviewedNextTransaction(transaction, reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical cancellation");
|
|
503
527
|
const simulation = await simulateLaunchTransactions({ client, planned, limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork, signal: options.signal }, [transaction], block, reviewed);
|
|
504
528
|
if (!simulation.admitted) throw new LifecyclePlanningError("CANCEL_NOT_ADMITTED", simulation.reason ?? "Cancellation could not be proved against current canonical state", simulation);
|
|
@@ -507,7 +531,7 @@ export async function buildNextTransaction(options: BuildNextTransactionOptions)
|
|
|
507
531
|
return preflightNextTransaction(options, next, simulation, reviewed);
|
|
508
532
|
}
|
|
509
533
|
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));
|
|
534
|
+
ufg: options.planned.ufg, allowTopologyGaps: options.planned.allowTopologyGaps, allowMissingPermanentTail: options.planned.allowMissingPermanentTail }, client, block, progress, false, reviewed));
|
|
511
535
|
assertFinalPlan(refreshed, options.signal);
|
|
512
536
|
if (!refreshed.simulation.admitted) throw new LifecyclePlanningError("PLAN_NOT_ADMITTED", refreshed.simulation.reason ?? "Remaining exact launch sequence is not admitted", refreshed.simulation);
|
|
513
537
|
assertPlannedDeploymentBindings(options.planned, refreshed);
|
|
@@ -526,7 +550,7 @@ export async function simulateLaunchPlan(options: SimulateLaunchPlanOptions): Pr
|
|
|
526
550
|
client, plan: planned.plan, account: planned.account, mode: planned.mode,
|
|
527
551
|
limits: options.limits === undefined ? planned.limits : options.limits, fork: options.fork,
|
|
528
552
|
receipts: options.receipts, confirmations: options.confirmations ?? planned.confirmations, signal: options.signal,
|
|
529
|
-
allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
|
|
553
|
+
ufg: planned.ufg, allowTopologyGaps: planned.allowTopologyGaps, allowMissingPermanentTail: planned.allowMissingPermanentTail,
|
|
530
554
|
});
|
|
531
555
|
assertPlannedDeploymentBindings(planned, current);
|
|
532
556
|
return current.simulation;
|
package/src/lifecycle/presets.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { Address, Hex } from "viem";
|
|
2
2
|
import type {
|
|
3
|
-
AdapterRegistrationV1, LaunchEnvelopeV2, ProfileRegistrationV1, ProfileTopologyV1,
|
|
3
|
+
AdapterRegistrationV1, LaunchEnvelopeV2, LaunchPlanV1, ProfileRegistrationV1, ProfileTopologyV1,
|
|
4
4
|
} from "./schema.js";
|
|
5
5
|
import type { LifecycleProfile } from "./types.js";
|
|
6
6
|
import { fixedFeePoolHookV1CreationCode } from "./deployments/fixed-fee-pool-hook-v1-20261008.js";
|
|
7
7
|
import { dynamicFeePoolHookV1CreationCode } from "./deployments/dynamic-fee-pool-hook-v1-20261011.js";
|
|
8
8
|
import { hmmFixedEnvelope, hmmFixedAdapter, hmmFixedRegistration, hmmFixedCreationCode } from "./deployments/hmm-fixed-pool-hook-v2-20261017.js";
|
|
9
9
|
import { hmmDynamicEnvelope, hmmDynamicAdapter, hmmDynamicRegistration, hmmDynamicCreationCode } from "./deployments/hmm-dynamic-pool-hook-v2-20261017.js";
|
|
10
|
-
import { HMM_FIXED_PROFILE_ID, HMM_DYNAMIC_PROFILE_ID } from "./hmm.js";
|
|
10
|
+
import { HMM_FIXED_PROFILE_ID, HMM_DYNAMIC_PROFILE_ID, isHmmProfile } from "./hmm.js";
|
|
11
|
+
import { ufgFixedEnvelope, ufgFixedAdapter, ufgFixedRegistration } from "./deployments/ufg-fixed-pool-20261021.js";
|
|
12
|
+
import { ufgDynamicEnvelope, ufgDynamicAdapter, ufgDynamicRegistration } from "./deployments/ufg-dynamic-pool-20261021.js";
|
|
13
|
+
import { UFG_FIXED_PROFILE_ID, UFG_DYNAMIC_PROFILE_ID } from "./ufg-config.js";
|
|
11
14
|
|
|
12
15
|
export enum KnownLifecycleProfile {
|
|
13
16
|
Abyss0 = "abyss-0",
|
|
@@ -18,6 +21,8 @@ export enum KnownLifecycleProfile {
|
|
|
18
21
|
V4DynamicFeePool = "v4-dynamic-fee-pool",
|
|
19
22
|
V4HmmFixedPool = "v4-hmm-fixed-pool",
|
|
20
23
|
V4HmmDynamicPool = "v4-hmm-dynamic-pool",
|
|
24
|
+
V4UfgFixedPool = "v4-ufg-fixed-pool",
|
|
25
|
+
V4UfgDynamicPool = "v4-ufg-dynamic-pool",
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
export enum KnownLifecycleHook {
|
|
@@ -280,7 +285,21 @@ const dynamicFeePoolPreset: LifecycleProfilePreset = Object.freeze({
|
|
|
280
285
|
}),
|
|
281
286
|
});
|
|
282
287
|
|
|
283
|
-
|
|
288
|
+
// HMMLiquidityHookV2.sol:98-119 requires ~3.06M available before manager calls,
|
|
289
|
+
// including opening and swaps. Robinhood fork measurements: activation minima
|
|
290
|
+
// 9.60M/15.40M/21.11M for 1/2/3 markets despite used gas 5.85M/11.13M/16.54M.
|
|
291
|
+
// Apply this forwarding target once per activate/atomic envelope, not per callback.
|
|
292
|
+
export const HMM_V2_FORWARDING_BASE_GAS = 4_250_000n;
|
|
293
|
+
export const HMM_V2_FORWARDING_EXTRA_MARKET_GAS = 500_000n;
|
|
294
|
+
export const HMM_V2_PREPARE_GAS = 14_500_000n;
|
|
295
|
+
|
|
296
|
+
export function hmmV2ForwardingReserve(plan: LaunchPlanV1): bigint {
|
|
297
|
+
let count = 0;
|
|
298
|
+
for (const market of plan.markets) if (isHmmProfile(market.profileId)) count += 1;
|
|
299
|
+
return count === 0 ? 0n : HMM_V2_FORWARDING_BASE_GAS + HMM_V2_FORWARDING_EXTRA_MARKET_GAS * BigInt(count - 1);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function poolBoundPreset(key: KnownLifecycleProfile, id: Hex, envelope: LaunchEnvelopeV2,
|
|
284
303
|
adapter: AdapterRegistrationV1, registration: ProfileRegistrationV1, creationCode: Hex): LifecycleProfilePreset {
|
|
285
304
|
return Object.freeze({
|
|
286
305
|
key, supported: true,
|
|
@@ -313,8 +332,10 @@ const knownDeployment: LifecycleDeploymentPreset = Object.freeze({
|
|
|
313
332
|
abyssPreset(KnownLifecycleProfile.Abyss3, "0xe1bd95e086469e6afd9791ccc957e4d22b735012cb3db4a747f703673c62c306"),
|
|
314
333
|
fixedFeePoolPreset,
|
|
315
334
|
dynamicFeePoolPreset,
|
|
316
|
-
|
|
317
|
-
|
|
335
|
+
poolBoundPreset(KnownLifecycleProfile.V4HmmFixedPool, HMM_FIXED_PROFILE_ID, hmmFixedEnvelope, hmmFixedAdapter, hmmFixedRegistration, hmmFixedCreationCode),
|
|
336
|
+
poolBoundPreset(KnownLifecycleProfile.V4HmmDynamicPool, HMM_DYNAMIC_PROFILE_ID, hmmDynamicEnvelope, hmmDynamicAdapter, hmmDynamicRegistration, hmmDynamicCreationCode),
|
|
337
|
+
poolBoundPreset(KnownLifecycleProfile.V4UfgFixedPool, UFG_FIXED_PROFILE_ID, ufgFixedEnvelope, ufgFixedAdapter, ufgFixedRegistration, fixedFeePoolHookV1CreationCode),
|
|
338
|
+
poolBoundPreset(KnownLifecycleProfile.V4UfgDynamicPool, UFG_DYNAMIC_PROFILE_ID, ufgDynamicEnvelope, ufgDynamicAdapter, ufgDynamicRegistration, dynamicFeePoolHookV1CreationCode),
|
|
318
339
|
]),
|
|
319
340
|
provenance: Object.freeze({ sourceCommit: "e174ce695c7010f50ca9211b07b9f178c990beac", deploymentBlock: 82880546n }),
|
|
320
341
|
});
|
|
@@ -344,7 +365,7 @@ export function getKnownLifecycleProfile(options: KnownLifecycleDeploymentIdenti
|
|
|
344
365
|
return undefined;
|
|
345
366
|
}
|
|
346
367
|
|
|
347
|
-
/** Stable, frozen order: Abyss variants
|
|
368
|
+
/** Stable, frozen order: Abyss variants, fixed/dynamic V4, HMM, then UFG fixed/dynamic. */
|
|
348
369
|
export function listKnownLifecycleProfiles(options: KnownLifecycleDeploymentIdentity): readonly LifecycleProfilePreset[] {
|
|
349
370
|
return getKnownLifecycleDeployment(options)?.profiles ?? noProfiles;
|
|
350
371
|
}
|