@black-market/sdk 0.15.0 → 0.16.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.
Files changed (113) hide show
  1. package/README.md +10 -8
  2. package/dist/lifecycle/auto-compound.d.ts +21 -0
  3. package/dist/lifecycle/auto-compound.d.ts.map +1 -0
  4. package/dist/lifecycle/auto-compound.js +34 -0
  5. package/dist/lifecycle/auto-compound.js.map +1 -0
  6. package/dist/lifecycle/calibration.d.ts +2 -4
  7. package/dist/lifecycle/calibration.d.ts.map +1 -1
  8. package/dist/lifecycle/calibration.js +1 -26
  9. package/dist/lifecycle/calibration.js.map +1 -1
  10. package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.d.ts +73 -0
  11. package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.d.ts.map +1 -0
  12. package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.js +75 -0
  13. package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.js.map +1 -0
  14. package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.d.ts +73 -0
  15. package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.d.ts.map +1 -0
  16. package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.js +75 -0
  17. package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.js.map +1 -0
  18. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.d.ts +71 -0
  19. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.d.ts.map +1 -0
  20. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.js +73 -0
  21. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.js.map +1 -0
  22. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.d.ts +71 -0
  23. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.d.ts.map +1 -0
  24. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.js +73 -0
  25. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.js.map +1 -0
  26. package/dist/lifecycle/gas-profile.d.ts +17 -13
  27. package/dist/lifecycle/gas-profile.d.ts.map +1 -1
  28. package/dist/lifecycle/gas-profile.js +133 -113
  29. package/dist/lifecycle/gas-profile.js.map +1 -1
  30. package/dist/lifecycle/index.d.ts +6 -4
  31. package/dist/lifecycle/index.d.ts.map +1 -1
  32. package/dist/lifecycle/index.js +6 -4
  33. package/dist/lifecycle/index.js.map +1 -1
  34. package/dist/lifecycle/markets.d.ts +4 -0
  35. package/dist/lifecycle/markets.d.ts.map +1 -1
  36. package/dist/lifecycle/markets.js +58 -3
  37. package/dist/lifecycle/markets.js.map +1 -1
  38. package/dist/lifecycle/opening-buys.js +3 -3
  39. package/dist/lifecycle/opening-buys.js.map +1 -1
  40. package/dist/lifecycle/planner.d.ts +6 -9
  41. package/dist/lifecycle/planner.d.ts.map +1 -1
  42. package/dist/lifecycle/planner.js +122 -208
  43. package/dist/lifecycle/planner.js.map +1 -1
  44. package/dist/lifecycle/presets.d.ts +9 -3
  45. package/dist/lifecycle/presets.d.ts.map +1 -1
  46. package/dist/lifecycle/presets.js +17 -1
  47. package/dist/lifecycle/presets.js.map +1 -1
  48. package/dist/lifecycle/raeto-abi.d.ts +175 -0
  49. package/dist/lifecycle/raeto-abi.d.ts.map +1 -0
  50. package/dist/lifecycle/raeto-abi.js +224 -0
  51. package/dist/lifecycle/raeto-abi.js.map +1 -0
  52. package/dist/lifecycle/raeto.d.ts +21 -0
  53. package/dist/lifecycle/raeto.d.ts.map +1 -0
  54. package/dist/lifecycle/raeto.js +59 -0
  55. package/dist/lifecycle/raeto.js.map +1 -0
  56. package/dist/lifecycle/rpc.d.ts +3 -13
  57. package/dist/lifecycle/rpc.d.ts.map +1 -1
  58. package/dist/lifecycle/rpc.js +11 -37
  59. package/dist/lifecycle/rpc.js.map +1 -1
  60. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  61. package/dist/lifecycle/snapshot.js +1 -2
  62. package/dist/lifecycle/snapshot.js.map +1 -1
  63. package/dist/lifecycle/transactions.d.ts +10 -3
  64. package/dist/lifecycle/transactions.d.ts.map +1 -1
  65. package/dist/lifecycle/transactions.js +47 -10
  66. package/dist/lifecycle/transactions.js.map +1 -1
  67. package/dist/lifecycle/types.d.ts +32 -107
  68. package/dist/lifecycle/types.d.ts.map +1 -1
  69. package/dist/lifecycle/types.js +3 -3
  70. package/dist/lifecycle/types.js.map +1 -1
  71. package/dist/lifecycle/ufg.d.ts +1 -1
  72. package/dist/lifecycle/ufg.d.ts.map +1 -1
  73. package/dist/lifecycle/ufg.js +1 -1
  74. package/dist/lifecycle/ufg.js.map +1 -1
  75. package/docs/launch-lifecycle.md +113 -1
  76. package/docs/lifecycle-gas-profiles.md +141 -24
  77. package/docs/reference.md +112 -3
  78. package/examples/.env.example +0 -3
  79. package/examples/README.md +11 -11
  80. package/examples/launch-example-support.ts +4 -9
  81. package/examples/launch-example.ts +12 -28
  82. package/examples/launch-lifecycle-smoke.ts +10 -22
  83. package/examples/launch.ts +8 -11
  84. package/package.json +1 -1
  85. package/src/lifecycle/auto-compound.ts +38 -0
  86. package/src/lifecycle/calibration.ts +3 -23
  87. package/src/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.ts +77 -0
  88. package/src/lifecycle/deployments/auto-compound-static-pool-v1-20261028.ts +77 -0
  89. package/src/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.ts +74 -0
  90. package/src/lifecycle/deployments/raeto-fixed-pool-v1-20261029.ts +74 -0
  91. package/src/lifecycle/gas-profile.ts +125 -104
  92. package/src/lifecycle/index.ts +6 -4
  93. package/src/lifecycle/markets.ts +47 -3
  94. package/src/lifecycle/opening-buys.ts +3 -3
  95. package/src/lifecycle/planner.ts +116 -191
  96. package/src/lifecycle/presets.ts +17 -1
  97. package/src/lifecycle/raeto-abi.ts +223 -0
  98. package/src/lifecycle/raeto.ts +61 -0
  99. package/src/lifecycle/rpc.ts +10 -32
  100. package/src/lifecycle/snapshot.ts +1 -2
  101. package/src/lifecycle/transactions.ts +46 -9
  102. package/src/lifecycle/types.ts +38 -87
  103. package/src/lifecycle/ufg.ts +1 -1
  104. package/dist/lifecycle/fork.d.ts +0 -15
  105. package/dist/lifecycle/fork.d.ts.map +0 -1
  106. package/dist/lifecycle/fork.js +0 -36
  107. package/dist/lifecycle/fork.js.map +0 -1
  108. package/dist/lifecycle/simulation.d.ts +0 -17
  109. package/dist/lifecycle/simulation.d.ts.map +0 -1
  110. package/dist/lifecycle/simulation.js +0 -605
  111. package/dist/lifecycle/simulation.js.map +0 -1
  112. package/src/lifecycle/fork.ts +0 -36
  113. package/src/lifecycle/simulation.ts +0 -543
@@ -1,42 +1,27 @@
1
- import { toHex, zeroAddress, type Address } from "viem";
2
- import { AUCTION_QUOTE_OPTIONS } from "../auction.js";
3
- import { getTickAtSqrtRatio } from "../launch.js";
1
+ import { toHex, type Address } from "viem";
4
2
  import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig } from "./markets.js";
5
3
  import { computeLifecycleOpeningBuys, hasProfiledOpeningMath } from "./opening-buys.js";
6
- import { getKnownLifecycleProfile, hmmV2ForwardingReserve } from "./presets.js";
7
- import { lifecyclePinnedRpc, lifecycleSessionRead, nitroPosterGasBound, readNitroPrices, registerLifecycleFinalCheck, resolveLifecycleLimits, rpcHex, rpcQuantity } from "./rpc.js";
4
+ import { hmmV2ForwardingReserve } from "./presets.js";
5
+ import { lifecyclePinnedRpc, lifecycleSourceClient, nitroPosterGasBound, readLifecycleGasPrice, readNitroPrices, registerLifecycleFinalCheck, resolveLifecycleLimits, rpcQuantity } from "./rpc.js";
8
6
  import { LifecycleFundingKind, LifecycleRewardMode, LifecycleTokenKind, type LaunchPlanV1 } from "./schema.js";
9
- import { buildLifecycleCommand } from "./transactions.js";
10
- import { UFG_LAUNCH_PROXY_ADDRESS, ufgPlanPrincipal, type UfgLaunchConfig } from "./ufg-config.js";
11
- import type { NextTransactionEnvelope } from "./simulation.js";
12
- import { LifecyclePlanningError, type LifecycleBlock, type LifecycleLaunchGasBounds, type LifecycleOpeningBuyQuote, type LifecycleSimulation, type LifecycleTransaction, type SimulateLaunchPlanOptions } from "./types.js";
7
+ import { isAutoCompoundProfile } from "./auto-compound.js";
8
+ import { buildLifecycleCommand, LIFECYCLE_CAPACITY_PADDING_BPS, preparationCalldataGas, preparationGasEstimate } from "./transactions.js";
9
+ import { UFG_LAUNCH_PROXY_ADDRESS, isUfgProfile, ufgPlanPrincipal, type UfgLaunchConfig } from "./ufg-config.js";
10
+ import { LifecyclePlanningError, type LifecycleBlock, type LifecycleLaunchGasBounds, type LifecycleLimitSource, type LifecycleOpeningBuyQuote, type LifecyclePlanningAssessment, type LifecycleRpcClient, type LifecycleTransaction, type PlannedLaunch } from "./types.js";
11
+ import { isHmmProfile } from "./hmm.js";
12
+ import { isRaetoProfile } from "./raeto.js";
13
13
 
14
- export const LIFECYCLE_GAS_PROFILE_VERSION = "robinhood-4663-arbos61-v1";
15
-
16
- // Coverage, not token admission: unlisted currencies still use exact execution.
17
- const profiledQuotes = new Set<string>();
18
- for (const quote of AUCTION_QUOTE_OPTIONS) if (quote.address !== zeroAddress) profiledQuotes.add(quote.address.toLowerCase());
14
+ export const LIFECYCLE_GAS_PROFILE_VERSION = "robinhood-4663-capacity-v4";
15
+ export type NextTransactionEnvelope = Readonly<{ gas: bigint; gasPrice: bigint }>;
19
16
  const ceil = (value: bigint, divisor: bigint) => (value + divisor - 1n) / divisor;
20
17
  const approval = (transaction: LifecycleTransaction) => transaction.kind === "approve" || transaction.kind === "approve-reset";
21
- type MarketGas = { abyss: boolean; hmm: boolean; ufgHook: boolean; positions: number; spacing: number; openingTick: number; ticks: number[] };
22
-
23
- function covered(plan: LaunchPlanV1): boolean {
24
- return plan.chainId === 4663n && hasProfiledOpeningMath(plan) &&
25
- plan.markets.every((market) => profiledQuotes.has(market.quoteAsset.toLowerCase())) &&
26
- plan.funding.every((funding) => funding.kind !== LifecycleFundingKind.ERC20 || profiledQuotes.has(funding.inputAsset.toLowerCase()));
27
- }
18
+ type MarketGas = { abyss: boolean; hmm: boolean; ufgHook: boolean; raeto: boolean; autoCompound: boolean; positions: number };
28
19
 
29
20
  function marketGas(plan: LaunchPlanV1): MarketGas[] {
30
21
  return plan.markets.map((market) => {
31
- const preset = getKnownLifecycleProfile({ chainId: plan.chainId, orchestrator: plan.orchestrator, profileId: market.profileId })!;
32
22
  const abyss = market.configVersion === 1;
33
23
  const config = abyss ? decodeAbyssLifecycleMarketConfig(market.config) : decodePoolBoundV4LifecycleMarketConfig(market.config, 6);
34
- const price = "openingSqrtPriceX96" in config ? config.openingSqrtPriceX96 : config.sqrtPriceX96;
35
- const spacing = "tickSpacing" in config ? config.tickSpacing : config.fee === 100 ? 1 : config.fee === 500 ? 10 : config.fee === 3000 ? 60 : 200;
36
- const ticks = new Set<number>();
37
- for (const position of config.positions) { ticks.add(position.tickLower); ticks.add(position.tickUpper); }
38
- return { abyss, hmm: preset.key.includes("hmm"), ufgHook: preset.key.includes("ufg"), positions: config.positions.length,
39
- spacing, openingTick: getTickAtSqrtRatio(price), ticks: [...ticks] };
24
+ return { abyss, hmm: isHmmProfile(market.profileId), ufgHook: isUfgProfile(market.profileId), raeto: isRaetoProfile(market.profileId), autoCompound: isAutoCompoundProfile(market.profileId), positions: config.positions.length };
40
25
  });
41
26
  }
42
27
 
@@ -55,121 +40,157 @@ function utf8Bytes(value: string): number {
55
40
  function tokenCreationGas(token: LaunchPlanV1["token"]): bigint {
56
41
  return (token.kind === LifecycleTokenKind.ERC404 ? 2_500_000n : 0n) +
57
42
  (token.rewardMode === LifecycleRewardMode.Staking ? 2_500_000n : token.rewardMode === LifecycleRewardMode.Dividends ? 500_000n : 0n) +
58
- 22_000n * BigInt(Math.ceil(utf8Bytes(token.name) / 32) + Math.ceil(utf8Bytes(token.symbol) / 32) + Math.ceil(utf8Bytes(token.metadataURI) / 32));
43
+ 25_000n * BigInt(Math.ceil(utf8Bytes(token.name) / 32) + Math.ceil(utf8Bytes(token.symbol) / 32) + Math.ceil(utf8Bytes(token.metadataURI) / 32));
59
44
  }
60
45
 
61
- /** Capacity classification only. No value returned here ever sets a transaction gas limit. */
46
+ // Max observed compute: native Abyss atomic21,584,262; V4 fixed20,833,003;
47
+ // HMM26,395,772; UFG27,941,420 (historical-receipts.json,195 transactions).
48
+ // fork-matrix.json: Abyss activation8 positions11.08M/32 positions39.72M;
49
+ // fork-v4-matrix.json: dynamic V4 eight positions5,906,285.
50
+ // Native mixed HMM8/Abyss8/HMM8 activation23,883,893 (0xf05e…fe98).
51
+ const activationCosts = {
52
+ call: 1_500_000n,
53
+ abyss: { market: 500_000n, position: 1_250_000n },
54
+ v4: { market: 250_000n, position: 500_000n },
55
+ raeto: { market: 500_000n, position: 500_000n },
56
+ autoCompound: { market: 750_000n, position: 500_000n },
57
+ hmm: 1_000_000n,
58
+ ufg: 250_000n,
59
+ // Fork Abyss32: 64 buys78.44M minimum versus one buy47.49M;
60
+ // max observed marginal is below0.5M/buy. ERC404 pairs add below0.1M/NFT.
61
+ buy: 500_000n,
62
+ nft: 100_000n,
63
+ } as const;
64
+
65
+ /** Measured-max template budgets, never gas-limit sizing or execution evidence. */
62
66
  function capacityBasis(plan: LaunchPlanV1, transaction: LifecycleTransaction, markets: readonly MarketGas[], quotes: readonly LifecycleOpeningBuyQuote[], ufgRoute: boolean): bigint {
63
67
  const atomic = transaction.kind === "atomic";
64
- let gas = atomic ? 19_500_000n + tokenCreationGas(plan.token) + (ufgRoute ? 6_500_000n : 0n) : 1_000_000n;
65
- for (const [index, market] of markets.entries()) {
66
- if (!atomic) gas += market.abyss ? 2_500_000n + 1_600_000n * BigInt(market.positions) : 2_000_000n + 600_000n * BigInt(market.positions);
67
- else gas += (index === 0 ? market.hmm ? 7_000_000n : market.ufgHook ? 1_000_000n : market.abyss ? 500_000n : 0n
68
- : market.hmm ? 15_000_000n : market.ufgHook ? 9_000_000n : 8_000_000n) +
68
+ // template-gas-matrix.json: Raeto/auto-compound atomic1 <=19.60M,
69
+ // atomic8 <=23.99M; atomic32 exceeds32M. The18M calibration base plus
70
+ // position/buy work and25% padding bounds compute and forwarding envelopes.
71
+ let gas = atomic ? (markets[0]!.raeto || markets[0]!.autoCompound ? 18_000_000n : 22_000_000n) + tokenCreationGas(plan.token) + (ufgRoute ? 6_500_000n : 0n)
72
+ : activationCosts.call + (ufgRoute ? activationCosts.ufg : 0n);
73
+ for (const market of markets) {
74
+ if (atomic) gas += (market.hmm ? 7_000_000n : market.ufgHook ? 1_000_000n : market.abyss ? 500_000n : 0n) +
69
75
  (market.abyss ? 1_500_000n : 600_000n) * BigInt(market.positions - 1);
70
- if (!atomic && market.hmm) gas += 6_000_000n;
76
+ else {
77
+ const cost = market.abyss ? activationCosts.abyss : market.raeto ? activationCosts.raeto : market.autoCompound ? activationCosts.autoCompound : activationCosts.v4;
78
+ gas += cost.market + cost.position * BigInt(market.positions) + (market.hmm ? activationCosts.hmm : market.ufgHook ? activationCosts.ufg : 0n);
79
+ }
71
80
  }
72
- const ticks = markets.map((market) => market.openingTick);
81
+ gas += activationCosts.buy * BigInt(quotes.length);
82
+ // These collectors revalidate the prepared position set for each buy. At32
83
+ // positions the measured marginal exceeds1M/buy; flat0.5M is insufficient.
73
84
  for (const quote of quotes) {
74
- const market = markets[quote.marketIndex]!, before = ticks[quote.marketIndex]!, after = getTickAtSqrtRatio(quote.sqrtPriceAfterX96);
75
- const low = Math.min(before, after), high = Math.max(before, after);
76
- const words = Math.abs(Math.floor(after / (market.spacing * 256)) - Math.floor(before / (market.spacing * 256))) + 2;
77
- let crossings = 0;
78
- for (const tick of market.ticks) if (tick >= low && tick <= high) crossings += 1;
79
- gas += 300_000n + 8_000n * BigInt(words) + 80_000n * BigInt(crossings);
80
- if (plan.token.kind === LifecycleTokenKind.ERC404) gas += 70_000n * ceil(quote.tokenOut, plan.token.nftUnit);
81
- ticks[quote.marketIndex] = after;
85
+ const market = markets[quote.marketIndex]!;
86
+ if (market.raeto || market.autoCompound) gas += 25_000n * BigInt(market.positions);
82
87
  }
88
+ if (plan.token.kind === LifecycleTokenKind.ERC404) for (const quote of quotes) gas += activationCosts.nft * ceil(quote.tokenOut, plan.token.nftUnit);
83
89
  return gas + 24n * BigInt((transaction.data.length - 2) / 2) + 100_000n * BigInt(plan.feeAssets.length);
84
90
  }
85
91
 
86
92
  type CapacityOptions = { plan: LaunchPlanV1; predictedToken: Address; ufg?: { config: UfgLaunchConfig }; headroomBps?: number };
87
93
  export type ProfiledLaunchCapacity = {
88
94
  bounds: LifecycleLaunchGasBounds; initialBuys: readonly LifecycleOpeningBuyQuote[]; headroomBps: number;
89
- atomic: LifecycleTransaction; activation: LifecycleTransaction;
95
+ atomic?: LifecycleTransaction; activation: LifecycleTransaction;
90
96
  };
91
97
 
92
- /** Internal shared computation; callers reuse both compiled commands and ordered math. */
98
+ /** No quote-token allowlist: costs and exact curve math belong to the release template. */
93
99
  export function deriveLifecycleLaunchCapacity(options: CapacityOptions, openingBuys?: readonly LifecycleOpeningBuyQuote[]): ProfiledLaunchCapacity | undefined {
94
100
  const { plan, ufg } = options;
95
- if (!covered(plan) || plan.funding.some((funding) => funding.kind === LifecycleFundingKind.Swap) ||
96
- (plan.creator.toLowerCase() === UFG_LAUNCH_PROXY_ADDRESS.toLowerCase()) !== (ufg !== undefined)) return undefined;
101
+ if (!hasProfiledOpeningMath(plan) || (plan.creator.toLowerCase() === UFG_LAUNCH_PROXY_ADDRESS.toLowerCase()) !== (ufg !== undefined)) return undefined;
97
102
  const headroomBps = options.headroomBps ?? 1500;
98
- if (!Number.isSafeInteger(headroomBps) || headroomBps < 0 || headroomBps > 10000) throw new LifecyclePlanningError("INVALID_LIMITS", "Headroom must be between zero and10000 basis points");
103
+ if (!Number.isSafeInteger(headroomBps) || headroomBps < 0 || headroomBps > 10000) throw new LifecyclePlanningError("INVALID_LIMITS", "Headroom must be between zero and 10000 basis points");
99
104
  const initialBuys = openingBuys ?? computeLifecycleOpeningBuys(options);
100
105
  const route = ufg === undefined ? undefined : { account: ufgPlanPrincipal(plan), config: ufg.config };
101
- const atomic = buildLifecycleCommand(plan, { kind: "atomic" }, route), activation = buildLifecycleCommand(plan, { kind: "activate" }, route);
106
+ const atomic = plan.markets.length === 1 && !plan.funding.some((funding) => funding.kind === LifecycleFundingKind.Swap)
107
+ ? buildLifecycleCommand(plan, { kind: "atomic" }, route) : undefined;
108
+ const activation = buildLifecycleCommand(plan, { kind: "activate" }, route);
102
109
  const markets = marketGas(plan), reserve = hmmV2ForwardingReserve(plan);
103
110
  const bound = (transaction: LifecycleTransaction) => {
104
111
  const basis = capacityBasis(plan, transaction, markets, initialBuys, ufg !== undefined);
105
- const buffered = ceil(basis * BigInt(10000 + headroomBps), 10000n);
112
+ const buffered = ceil(basis * BigInt(10000 + Math.max(LIFECYCLE_CAPACITY_PADDING_BPS, headroomBps)), 10000n);
106
113
  return buffered > basis + reserve ? buffered : basis + reserve;
107
114
  };
108
- return { bounds: { atomicComputeGasUpperBound: bound(atomic), activationComputeGasUpperBound: bound(activation) }, initialBuys, headroomBps, atomic, activation };
115
+ return { bounds: { ...(atomic === undefined ? {} : { atomicComputeGasUpperBound: bound(atomic) }), activationComputeGasUpperBound: bound(activation) }, initialBuys, headroomBps, atomic, activation };
109
116
  }
110
117
 
111
- /** Pure, versioned conservative compute bounds for mode/builder capacity, excluding poster gas.
112
- * Undefined means unprofiled, not unavailable functionality. A large upper bound is uncertain. */
118
+ /** Pure release-template budgets with headroom, excluding poster allocation.
119
+ * Missing coverage refuses planning; there is no execution fallback. */
113
120
  export function computeLifecycleLaunchGasBounds(options: CapacityOptions): LifecycleLaunchGasBounds | undefined {
114
121
  return deriveLifecycleLaunchCapacity(options)?.bounds;
115
122
  }
116
123
 
117
- export type ProfiledLifecycleTransactions = { simulation: LifecycleSimulation; profileOnly: boolean };
124
+ export type LifecycleBudgetOptions = { client: LifecycleRpcClient; planned: PlannedLaunch; limits?: LifecycleLimitSource; signal?: AbortSignal };
118
125
 
119
- /** Native ceilings determine every launch envelope. Profiles decide capacity only;
120
- * conversions and uncertain activation shapes still require exact execution. */
121
- export async function profileLifecycleTransactions(options: SimulateLaunchPlanOptions, transactions: readonly LifecycleTransaction[], block: LifecycleBlock,
122
- reviewed?: NextTransactionEnvelope, openingBuys?: readonly LifecycleOpeningBuyQuote[], capacity?: ProfiledLaunchCapacity): Promise<ProfiledLifecycleTransactions | undefined> {
126
+ /** Checks pinned inputs and deterministic capacity only. Never executes launch calldata. */
127
+ export async function assessLifecycleTransactions(options: LifecycleBudgetOptions, transactions: readonly LifecycleTransaction[], block: LifecycleBlock,
128
+ reviewed?: NextTransactionEnvelope, openingBuys?: readonly LifecycleOpeningBuyQuote[], capacity?: ProfiledLaunchCapacity): Promise<LifecyclePlanningAssessment> {
123
129
  const { client, planned } = options, plan = planned.plan;
124
- const onlyApprovals = transactions.every(approval);
125
- if (planned.chainId !== 4663n || !onlyApprovals && !covered(plan)) return undefined;
126
130
  const context = { client, account: planned.account, orchestrator: plan.orchestrator, block, chainId: planned.chainId };
127
131
  const observed = await resolveLifecycleLimits(context, options.limits === undefined ? planned.limits : options.limits);
128
- if (!onlyApprovals && observed.limits.arbOSVersion !== 61n) return undefined;
129
- const [prices, balanceValue, accountCode] = await Promise.all([
130
- readNitroPrices(client, block),
132
+ const [recommendation, balanceValue, prices] = await Promise.all([
133
+ readLifecycleGasPrice(client, block, planned.chainId),
131
134
  lifecyclePinnedRpc(client, "eth_getBalance", [planned.account, toHex(block.number)], block),
132
- lifecycleSessionRead(client, `${planned.chainId}:account-code:${planned.account.toLowerCase()}`, () => lifecyclePinnedRpc(client, "eth_getCode", [planned.account, toHex(block.number)], block)),
135
+ observed.limits.protocol === "nitro" ? readNitroPrices(client, block) : undefined,
133
136
  ]);
134
- if (rpcHex(accountCode, "account code") !== "0x") return undefined;
135
137
  options.signal?.throwIfAborted(); observed.assertCurrent(); registerLifecycleFinalCheck(client, observed.assertCurrent);
136
- const { limits } = observed, baseFee = block.baseFeePerGas ?? 0n;
137
- const requiredGasPrice = prices[5] > baseFee ? prices[5] : baseFee;
138
- const gasPrice = reviewed?.gasPrice ?? (prices[5] > baseFee * 2n ? prices[5] : baseFee * 2n);
139
- if (reviewed !== undefined && !onlyApprovals && reviewed.gas !== limits.executionGasCeiling) throw new LifecyclePlanningError("STALE_REVIEWED_GAS", "Reviewed gas differs from the native transaction ceiling; review the launch again");
140
- const needsBuys = transactions.some((transaction) => transaction.kind === "atomic" || transaction.kind === "activate");
141
- const currentCapacity = needsBuys ? capacity?.headroomBps === limits.headroomBps ? capacity : deriveLifecycleLaunchCapacity({ plan, predictedToken: planned.predictedToken, ufg: planned.ufg, headroomBps: limits.headroomBps }, openingBuys) : undefined;
142
- if (needsBuys && currentCapacity === undefined) return undefined;
143
- const quotes = currentCapacity?.initialBuys ?? [], bounds = currentCapacity?.bounds;
144
- let profileOnly = !plan.funding.some((funding) => funding.kind === LifecycleFundingKind.Swap) ||
145
- !transactions.some((transaction) => transaction.kind === "begin" || transaction.kind === "atomic" || transaction.kind === "activate");
138
+ const { limits } = observed, baseFee = block.baseFeePerGas ?? 0n, gasLimit = limits.executionGasCeiling;
139
+ const requiredGasPrice = limits.protocol === "nitro" && recommendation > baseFee ? recommendation : baseFee;
140
+ const gasPrice = reviewed?.gasPrice ?? (recommendation > baseFee * 2n ? recommendation : baseFee * 2n);
141
+ if (reviewed !== undefined && reviewed.gas !== gasLimit) throw new LifecyclePlanningError("STALE_REVIEWED_GAS", "Reviewed gas differs from the native transaction ceiling. Refresh the review before continuing.");
142
+ const cancelOnly = transactions.every((transaction) => transaction.kind === "cancel");
143
+ const currentCapacity = cancelOnly ? undefined : capacity?.headroomBps === limits.headroomBps ? capacity
144
+ : deriveLifecycleLaunchCapacity({ plan, predictedToken: planned.predictedToken, ufg: planned.ufg, headroomBps: limits.headroomBps }, openingBuys);
145
+ const immediate = transactions[0], launch = transactions.find((transaction) => !approval(transaction));
146
+ const poster = (transaction: LifecycleTransaction) => prices === undefined ? 0n : nitroPosterGasBound(transaction, prices).posterGas;
147
+ const assessment: LifecyclePlanningAssessment = { ready: true, blockNumber: block.number, blockHash: block.hash,
148
+ account: planned.account, chainId: planned.chainId, limits,
149
+ ...(currentCapacity === undefined ? {} : { gasProfile: { version: LIFECYCLE_GAS_PROFILE_VERSION, bounds: currentCapacity.bounds, initialBuys: currentCapacity.initialBuys } }) };
150
+ const refuse = (code: string, reason: string, failureCategory: LifecyclePlanningAssessment["failureCategory"], capacityConstraint?: LifecyclePlanningAssessment["capacityConstraint"], failedTransactionId?: string): LifecyclePlanningAssessment =>
151
+ ({ ...assessment, ready: false, code, reason, failureCategory, capacityConstraint, failedTransactionId });
152
+ if (!cancelOnly && currentCapacity === undefined) return refuse("UNKNOWN_GAS_PROFILE", "Gas budgets are unavailable for this launch profile. Use a supported profile or update the SDK.", "capacity");
153
+ if (!cancelOnly && currentCapacity!.bounds.activationComputeGasUpperBound + poster(currentCapacity!.activation) > gasLimit) {
154
+ return refuse("ACTIVATION_GAS_LIMIT", "Too many positions or pools for one activation. Reduce them to fit the native transaction ceiling.", "capacity", "activation", "activate");
155
+ }
146
156
  for (const transaction of transactions) {
147
- if (transaction.kind !== "atomic" && transaction.kind !== "activate") continue;
148
- const compute = transaction.kind === "atomic" ? bounds!.atomicComputeGasUpperBound : bounds!.activationComputeGasUpperBound;
149
- if (compute + nitroPosterGasBound(transaction, prices).posterGas > limits.executionGasCeiling) profileOnly = false;
157
+ if (transaction.kind === "atomic" && (currentCapacity?.bounds.atomicComputeGasUpperBound === undefined || currentCapacity.bounds.atomicComputeGasUpperBound + poster(transaction) > gasLimit))
158
+ return refuse("STAGED_EXECUTION_REQUIRED", "This launch requires staged execution. Refresh the review before continuing.", "capacity", "gas-envelope", transaction.id);
159
+ if (transaction.kind === "prepare" && ceil((preparationGasEstimate(plan, transaction.marketStart!, transaction.marketCount!) + preparationCalldataGas(transaction.data)) * BigInt(10000 + Math.max(LIFECYCLE_CAPACITY_PADDING_BPS, limits.headroomBps)), 10000n) + poster(transaction) > gasLimit)
160
+ return refuse("PREPARATION_GAS_LIMIT", "This preparation batch exceeds the gas budget. Refresh the review to split it.", "capacity", "preparation", transaction.id);
161
+ if (limits.maxCalldataBytes !== undefined && (transaction.data.length - 2) / 2 > limits.maxCalldataBytes)
162
+ return refuse("CALLDATA_LIMIT", "The launch exceeds the transaction data limit. Reduce its positions or pools.", "capacity", "calldata", transaction.id);
150
163
  }
151
- const launch = transactions.find((transaction) => !approval(transaction));
152
- const immediate = transactions[0], gasLimit = limits.executionGasCeiling;
153
- const envelopeFailure = launch !== undefined && limits.transactionGasCeiling !== undefined && gasLimit > limits.transactionGasCeiling;
154
- const calldataFailure = transactions.find((transaction) => limits.maxCalldataBytes !== undefined && (transaction.data.length - 2) / 2 > limits.maxCalldataBytes);
155
- const feeFailure = reviewed !== undefined && gasPrice < requiredGasPrice;
156
- const affordabilityFailure = launch !== undefined && launch.value + gasLimit * gasPrice > rpcQuantity(balanceValue, "creator native balance");
157
- const semanticFailure = quotes.find((quote) => quote.tokenOut < plan.buys[quote.buyIndex]!.minTokenOut);
158
- const expired = block.timestamp > plan.deadline && transactions.some((transaction) => transaction.kind !== "cancel");
159
- const refused = envelopeFailure || calldataFailure !== undefined || feeFailure || affordabilityFailure || semanticFailure !== undefined || expired;
160
- const poster = immediate === undefined || approval(immediate) ? undefined : nitroPosterGasBound(immediate, prices).posterGas;
161
- const simulation: LifecycleSimulation = {
162
- backend: "profiled", confidence: "profiled", executionProof: "not-run", protocolFit: "profiled", admitted: profileOnly && !refused,
163
- blockNumber: block.number, blockHash: block.hash, account: planned.account, chainId: planned.chainId, limits, steps: [], deferredTransactionIds: [],
164
- ...(bounds === undefined ? {} : { gasProfile: { version: LIFECYCLE_GAS_PROFILE_VERSION, bounds, initialBuys: quotes } }),
165
- ...(immediate === undefined || approval(immediate) ? {} : { estimate: { gasLimit, gasPrice, executionFee: gasLimit * gasPrice, dataFee: 0n, totalFee: gasLimit * gasPrice,
166
- posterGas: poster, posterFee: poster! * gasPrice, dataFeeIncludedInGas: true, feeConfidence: "native-ceiling-and-data" as const } }),
167
- ...(envelopeFailure ? { failureCategory: "capacity" as const, capacityConstraint: "gas-envelope" as const, failedTransactionId: launch!.id, reason: "Native transaction ceiling exceeds the supplied complete gas policy" } : {}),
168
- ...(calldataFailure === undefined ? {} : { failureCategory: "capacity" as const, capacityConstraint: "calldata" as const, failedTransactionId: calldataFailure.id, reason: "Encoded command exceeds the caller's calldata byte policy" }),
169
- ...(affordabilityFailure ? { failureCategory: "affordability" as const, failedTransactionId: launch!.id, reason: "Creator native balance cannot reserve transaction value and the native gas ceiling" } : {}),
170
- ...(semanticFailure === undefined ? {} : { failureCategory: "semantic" as const, failedTransactionId: transactions.find((transaction) => transaction.kind === "atomic" || transaction.kind === "activate")?.id, reason: "Opening curve output is below the committed minimum" }),
171
- ...(expired ? { failureCategory: "semantic" as const, failedTransactionId: transactions.find((transaction) => transaction.kind !== "cancel")?.id, reason: "Launch deadline has expired at the pinned block" } : {}),
172
- ...(feeFailure ? { failedTransactionId: immediate?.id, reason: "Reviewed gas price is below the pinned required fee" } : {}),
173
- };
174
- return { simulation, profileOnly: profileOnly || refused };
164
+ if (launch !== undefined && limits.transactionGasCeiling !== undefined && gasLimit > limits.transactionGasCeiling)
165
+ return refuse("GAS_POLICY_LIMIT", "The configured gas limit is below the chain's native transaction ceiling.", "capacity", "gas-envelope", launch.id);
166
+ if (reviewed !== undefined && gasPrice < requiredGasPrice) return refuse("REVIEWED_FEE_TOO_LOW", "The reviewed gas price is below the current required fee. Refresh the review before continuing.", "semantic", undefined, immediate?.id);
167
+ const quotes = currentCapacity?.initialBuys ?? [];
168
+ if (quotes.some((quote) => quote.tokenOut < plan.buys[quote.buyIndex]!.minTokenOut))
169
+ return refuse("BUY_MINIMUM_NOT_MET", "An opening buy is below its minimum output. Refresh the review before continuing.", "semantic");
170
+ if (block.timestamp > plan.deadline && !cancelOnly) return refuse("PLAN_EXPIRED", "The launch deadline has passed. Cancel an unfinished launch to recover its funds.", "semantic");
171
+ const inputs = new Map<string, { required: bigint; balance: bigint }>();
172
+ for (const prerequisite of planned.prerequisites) {
173
+ if (prerequisite.allowance === undefined) continue;
174
+ const key = prerequisite.funding.inputAsset.toLowerCase(), total = inputs.get(key) ?? { required: 0n, balance: prerequisite.inputBalance };
175
+ total.required += prerequisite.requiredInput; inputs.set(key, total);
176
+ }
177
+ if ([...inputs.values()].some((input) => input.required > input.balance)) return refuse("INSUFFICIENT_FUNDING_BALANCE", "There are not enough tokens to fund this launch. Add funds or reduce the purchase.", "affordability");
178
+ const balance = rpcQuantity(balanceValue, "creator native balance");
179
+ if (gasPrice >= 1n << 256n || launch !== undefined && launch.value + gasLimit * gasPrice >= 1n << 256n) throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Transaction value and native-ceiling fee must fit uint256");
180
+ if (launch !== undefined && launch.value + gasLimit * gasPrice > balance) return refuse("INSUFFICIENT_NATIVE_BALANCE", "There is not enough native currency for the launch amount and gas budget. Add funds or reduce the purchase.", "affordability", undefined, launch.id);
181
+ if (immediate !== undefined && !approval(immediate)) {
182
+ const source = lifecycleSourceClient(client);
183
+ const dataFee = limits.protocol === "nitro" ? 0n : observed.configuration.estimateDataFee === undefined ? undefined
184
+ : await observed.configuration.estimateDataFee({ ...immediate, gas: gasLimit, gasPrice }, source === client ? context : { ...context, client: source });
185
+ if (dataFee !== undefined && (typeof dataFee !== "bigint" || dataFee < 0n || dataFee >= 1n << 256n)) throw new LifecyclePlanningError("INVALID_DATA_FEE", "Data-fee estimate must be a nonnegative uint256 amount");
186
+ const executionFee = gasLimit * gasPrice, required = immediate.value + executionFee + (dataFee ?? 0n);
187
+ if (required >= 1n << 256n) throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Transaction value and gas/data fee budget must fit uint256");
188
+ if (required > balance) return refuse("INSUFFICIENT_NATIVE_BALANCE", "There is not enough native currency for the launch amount and gas budget. Add funds or reduce the purchase.", "affordability", undefined, immediate.id);
189
+ const posterGas = prices === undefined ? undefined : poster(immediate);
190
+ assessment.estimate = { gasLimit, gasPrice, executionFee, dataFee, totalFee: dataFee === undefined ? undefined : executionFee + dataFee,
191
+ posterGas, posterFee: posterGas === undefined ? undefined : posterGas * gasPrice, dataFeeIncludedInGas: limits.protocol === "nitro",
192
+ feeConfidence: dataFee === undefined ? "native-ceiling-only" : "native-ceiling-and-data" };
193
+ }
194
+ options.signal?.throwIfAborted(); observed.assertCurrent();
195
+ return assessment;
175
196
  }
@@ -1,21 +1,23 @@
1
- /** Versioned construction metadata and live launch execution calibration. */
1
+ /** Versioned construction metadata, exact local curve math and launch budgets. */
2
2
  export * from "./schema.js";
3
3
  export * from "./types.js";
4
4
  export * from "./abi.js";
5
5
  export * from "./markets.js";
6
6
  export * from "./hmm.js";
7
7
  export * from "./ufg.js";
8
+ export * from "./auto-compound.js";
9
+ export * from "./raeto.js";
10
+ export * from "./raeto-abi.js";
8
11
  export { ABYSS_LIFECYCLE_CONFIG_SCHEMA, readLifecycleTokenAtBlock, readLifecycleHeadProgress, predictLifecycleToken,
9
12
  validateV4LifecycleOracle, readLifecycleProfiles, readPoolBoundHookDeployment,
10
13
  buildPoolBoundHookDeploymentTransaction, validateLifecycleMarketIdentity, readLaunchProgress } from "./progress.js";
11
- export * from "./fork.js";
12
14
  export * from "./authors.js";
13
15
  export * from "./presets.js";
14
- export { preparationGasEstimate, packPreparationBatches, buildLaunchTransactions } from "./transactions.js";
16
+ export { preparationGasEstimate, packPreparationBatches, buildLaunchTransactions, LIFECYCLE_CAPACITY_PADDING_BPS } from "./transactions.js";
15
17
  export { createLifecycleSessionCache } from "./rpc.js";
16
18
  export { launchLensAbi } from "./lens.js";
17
19
  export { hookSaltMinerAbi } from "./mining.js";
18
- export { planLaunch, simulateLaunchPlan, buildNextTransaction, prepareAndPlanLifecycleLaunch } from "./planner.js";
20
+ export { planLaunch, buildNextTransaction, prepareAndPlanLifecycleLaunch } from "./planner.js";
19
21
  export { computeLifecycleOpeningBuys } from "./opening-buys.js";
20
22
  export { computeLifecycleLaunchGasBounds } from "./gas-profile.js";
21
23
  export { assertLaunchTopologyCoverage, getTickAtSqrtRatio, type LaunchTopologyCoverageInput, type LaunchTopologyPosition } from "../launch.js";
@@ -3,6 +3,8 @@ 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
5
  import { decodeUfgLaunchConfig, encodeUfgLaunchConfig, isUfgProfile, type UfgLaunchConfig } from "./ufg-config.js";
6
+ import { decodeAutoCompoundTail, encodeAutoCompoundTail, isAutoCompoundProfile, validateAutoCompoundConfig, type AutoCompoundConfigV1 } from "./auto-compound.js";
7
+ import { decodeRaetoLaunchTail, encodeRaetoLaunchTail, isRaetoProfile, RAETO_QUOTE_ASSETS, validateRaetoLaunchConfig, type RaetoLaunchConfigV1 } from "./raeto.js";
6
8
 
7
9
  export type V4LifecyclePositionConfig = {
8
10
  tickLower: number; tickUpper: number; liquidity: bigint; salt: Hex; maxTokenAmount: bigint;
@@ -46,6 +48,8 @@ export type V4PoolBoundLifecycleMarketConfigV6 = Omit<V4LifecycleMarketConfig, "
46
48
  version: 6; minimumHookFeePips: number; feeSensitivityPipsSecondsPerTick: number; hookSalt: Hex;
47
49
  hmmLaunchConfig?: HmmLaunchConfig;
48
50
  ufgLaunchConfig?: UfgLaunchConfig;
51
+ autoCompoundConfig?: AutoCompoundConfigV1;
52
+ raetoLaunchConfig?: RaetoLaunchConfigV1;
49
53
  };
50
54
  export type V4PoolBoundLifecycleMarketConfig = V4PoolBoundLifecycleMarketConfigV5 | V4PoolBoundLifecycleMarketConfigV6;
51
55
  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)[])"));
@@ -92,9 +96,34 @@ function assertV1HookFeeShape(config: object): void {
92
96
  }
93
97
  }
94
98
 
99
+ function validateAutoCompoundMarket(config: V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig): asserts config is V4PoolBoundLifecycleMarketConfigV6 & { autoCompoundConfig: AutoCompoundConfigV1 } {
100
+ if (config.version !== 6 || config.autoCompoundConfig === undefined) throw new LifecyclePlanningError("INVALID_AUTO_COMPOUND_CONFIG", "Auto-compounding profiles require config6 and an explicit autoCompoundConfig");
101
+ if (config.hmmLaunchConfig !== undefined || config.ufgLaunchConfig !== undefined || config.raetoLaunchConfig !== undefined) throw new LifecyclePlanningError("INVALID_AUTO_COMPOUND_CONFIG", "Auto-compounding profiles cannot carry HMM, UFG or Raeto tails");
102
+ if (config.lpFeePips !== 0) throw new LifecyclePlanningError("INVALID_HOOK_FEES", "Auto-compounding profiles require zero LP fee");
103
+ if (config.developerFeeBps !== 0) throw new LifecyclePlanningError("DEVELOPER_FEE_CEILING", "Auto-compounding profiles require zero developer fee");
104
+ validateAutoCompoundConfig(config.autoCompoundConfig);
105
+ }
106
+
107
+ function validateRaetoMarket(config: V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig): asserts config is V4PoolBoundLifecycleMarketConfigV6 & { raetoLaunchConfig: RaetoLaunchConfigV1 } {
108
+ if (config.version !== 6 || config.raetoLaunchConfig === undefined) throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto profiles require config6 and an explicit raetoLaunchConfig");
109
+ if (config.hmmLaunchConfig !== undefined || config.ufgLaunchConfig !== undefined || config.autoCompoundConfig !== undefined) throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto profiles cannot carry HMM, UFG or auto-compounding tails");
110
+ if (config.feeMode !== 1 || config.lpFeePips !== 0 || config.developerFeeBps !== 0) throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto requires QuoteOnly feeMode 1, zero LP fee and zero developer fee");
111
+ validateRaetoLaunchConfig(config.raetoLaunchConfig);
112
+ }
113
+
95
114
  /** Auxiliary launch profiles require their typed tail and append it automatically. */
96
115
  export function encodePoolBoundV4LifecycleMarketConfig(config: V4PoolBoundLifecycleMarketConfig): Hex {
97
116
  const prefix = encodePoolBoundV4Prefix(config);
117
+ if (isRaetoProfile(config.profileId)) {
118
+ validateRaetoMarket(config);
119
+ return concatHex([prefix, encodeRaetoLaunchTail(config.raetoLaunchConfig)]);
120
+ }
121
+ if (config.version === 6 && config.raetoLaunchConfig !== undefined) throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "A Raeto tail requires a Raeto profile");
122
+ if (isAutoCompoundProfile(config.profileId)) {
123
+ validateAutoCompoundMarket(config);
124
+ return concatHex([prefix, encodeAutoCompoundTail(config.autoCompoundConfig)]);
125
+ }
126
+ if (config.version === 6 && config.autoCompoundConfig !== undefined) throw new LifecyclePlanningError("INVALID_AUTO_COMPOUND_CONFIG", "An auto-compounding tail requires an auto-compounding profile");
98
127
  if (isUfgProfile(config.profileId)) {
99
128
  if (config.version !== 6 || config.ufgLaunchConfig === undefined) throw new LifecyclePlanningError("INVALID_UFG_CONFIG", "UFG profiles require config6 and an explicit ufgLaunchConfig");
100
129
  if (config.hmmLaunchConfig !== undefined) throw new LifecyclePlanningError("INVALID_UFG_CONFIG", "UFG profiles cannot carry an HMM tail");
@@ -130,6 +159,14 @@ export function decodePoolBoundV4LifecycleMarketConfig(encoded: Hex, expectedVer
130
159
  const config = version === 5
131
160
  ? decodeAbiParameters([{ type: "tuple", components: poolBoundV4LifecycleMarketComponentsV5 }], encoded)[0] as V4PoolBoundLifecycleMarketConfigV5
132
161
  : decodeAbiParameters([{ type: "tuple", components: poolBoundV4LifecycleMarketComponentsV6 }], encoded)[0] as V4PoolBoundLifecycleMarketConfigV6;
162
+ if (isRaetoProfile(config.profileId)) {
163
+ if (config.version !== 6) throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto profiles require config6");
164
+ config.raetoLaunchConfig = decodeRaetoLaunchTail(`0x${encoded.slice(encodePoolBoundV4Prefix(config).length)}`);
165
+ }
166
+ if (isAutoCompoundProfile(config.profileId)) {
167
+ if (config.version !== 6) throw new LifecyclePlanningError("INVALID_AUTO_COMPOUND_CONFIG", "Auto-compounding profiles require config6");
168
+ config.autoCompoundConfig = decodeAutoCompoundTail(`0x${encoded.slice(encodePoolBoundV4Prefix(config).length)}`);
169
+ }
133
170
  if (isHmmProfile(config.profileId)) {
134
171
  if (config.version !== 6) throw new LifecyclePlanningError("INVALID_HMM_CONFIG", "HMM profiles require config6");
135
172
  const prefixLength = encodePoolBoundV4Prefix(config).length;
@@ -160,6 +197,11 @@ export function validateV4LifecycleMarket(options: {
160
197
  if (config.version !== 4 && !isPoolBoundV4ConfigVersion(config.version)) throw new LifecyclePlanningError("UNSUPPORTED_CONFIG_VERSION", "Reviewed V4 config version must be 4, 5 or 6");
161
198
  if (config.version === 6) validatePoolBoundHookFees(config);
162
199
  else if (config.version === 5) assertV1HookFeeShape(config);
200
+ if (isAutoCompoundProfile(config.profileId)) validateAutoCompoundMarket(config);
201
+ if (isRaetoProfile(config.profileId)) {
202
+ validateRaetoMarket(config);
203
+ if (!RAETO_QUOTE_ASSETS.some((quote) => quote.toLowerCase() === market.quoteAsset.toLowerCase())) throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto quote asset must be WETH or USDG");
204
+ } else if (config.version === 6 && config.raetoLaunchConfig !== undefined) throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "A Raeto tail requires a Raeto profile");
163
205
  const { envelope, developerTerms: terms, protocolMaximumDeveloperFeeBps: protocolCeiling } = profile;
164
206
  if (envelope === undefined || terms === undefined || protocolCeiling === undefined || !terms.enabled ||
165
207
  profile.topology.hookTopology !== (config.version === 4 ? 1 : 2) || market.configVersion !== config.version ||
@@ -258,9 +300,11 @@ export function hashPoolBoundV4MarketCommitment(options: { chainId: bigint; core
258
300
  if (!isPoolBoundV4ConfigVersion(market.configVersion)) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Pool-bound V4 market requires explicit config version 5 or 6");
259
301
  const config = decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion);
260
302
  if (config.profileId.toLowerCase() !== market.profileId.toLowerCase()) throw new LifecyclePlanningError("INVALID_BOUND_MARKET", "Config profile differs from the committed market profile");
261
- // Collector factories commit only the V6 prefix, not auxiliary launch tails.
262
- // The full plan commitment separately binds every byte of those tails.
263
- const configHash = keccak256(encodePoolBoundV4Prefix({ ...config, hookSalt: zeroHash }));
303
+ // Auto-compounding binds its tail into constructor economics; older factories
304
+ // bind only the prefix. Every full plan commitment also binds every tail byte.
305
+ const normalized = { ...config, hookSalt: zeroHash };
306
+ const configHash = keccak256(isAutoCompoundProfile(config.profileId)
307
+ ? encodePoolBoundV4LifecycleMarketConfig(normalized) : encodePoolBoundV4Prefix(normalized));
264
308
  return keccak256(encodeAbiParameters([
265
309
  { type: "bytes32" }, { type: "uint256" }, { type: "address" }, { type: "address" }, { type: "address" },
266
310
  { type: "bytes32" }, { type: "bytes32" }, { type: "address" }, { type: "uint256" }, { type: "uint32" }, { type: "bytes32" },
@@ -47,12 +47,12 @@ function openingState(plan: LaunchPlanV1, marketIndex: number): OpeningState {
47
47
  price = config.openingSqrtPriceX96; spacing = tier.tickSpacing; lpFee = BigInt(config.fee); positions = config.positions;
48
48
  } else {
49
49
  // All current release-owned V4 offerings use config6. Dynamic first buys
50
- // share the opening block's minimum fee; HMM strategy work starts onOpened,
51
- // after the ordered initial buys, never during this unopened curve.
50
+ // share the opening block's minimum fee. HMM work starts onOpened and
51
+ // auto-compounding defers until openingCompletedAt, after all opening buys.
52
52
  const config = decodePoolBoundV4LifecycleMarketConfig(market.config, 6);
53
53
  if (config.profileId.toLowerCase() !== market.profileId.toLowerCase()) throw new LifecyclePlanningError("INVALID_MARKET", "V4 opening config differs from the selected profile");
54
54
  price = config.sqrtPriceX96; spacing = config.tickSpacing; lpFee = BigInt(config.lpFeePips); positions = config.positions;
55
- const dynamic = preset.key === KnownLifecycleProfile.V4DynamicFeePool || preset.key === KnownLifecycleProfile.V4HmmDynamicPool || preset.key === KnownLifecycleProfile.V4UfgDynamicPool;
55
+ const dynamic = preset.key === KnownLifecycleProfile.V4DynamicFeePool || preset.key === KnownLifecycleProfile.V4HmmDynamicPool || preset.key === KnownLifecycleProfile.V4UfgDynamicPool || preset.key === KnownLifecycleProfile.V4AutoCompoundDynamicPool || preset.key === KnownLifecycleProfile.V4RaetoDynamicPool;
56
56
  hookFee = BigInt(dynamic ? config.minimumHookFeePips! : config.hookFeePips);
57
57
  }
58
58
  const tick = getTickAtSqrtRatio(price), liquidityNet = new Map<number, bigint>();