@black-market/sdk 0.15.1 → 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 (57) hide show
  1. package/README.md +5 -4
  2. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.d.ts +71 -0
  3. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.d.ts.map +1 -0
  4. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.js +73 -0
  5. package/dist/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.js.map +1 -0
  6. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.d.ts +71 -0
  7. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.d.ts.map +1 -0
  8. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.js +73 -0
  9. package/dist/lifecycle/deployments/raeto-fixed-pool-v1-20261029.js.map +1 -0
  10. package/dist/lifecycle/gas-profile.d.ts +1 -1
  11. package/dist/lifecycle/gas-profile.d.ts.map +1 -1
  12. package/dist/lifecycle/gas-profile.js +17 -7
  13. package/dist/lifecycle/gas-profile.js.map +1 -1
  14. package/dist/lifecycle/index.d.ts +2 -0
  15. package/dist/lifecycle/index.d.ts.map +1 -1
  16. package/dist/lifecycle/index.js +2 -0
  17. package/dist/lifecycle/index.js.map +1 -1
  18. package/dist/lifecycle/markets.d.ts +2 -0
  19. package/dist/lifecycle/markets.d.ts.map +1 -1
  20. package/dist/lifecycle/markets.js +30 -2
  21. package/dist/lifecycle/markets.js.map +1 -1
  22. package/dist/lifecycle/opening-buys.js +3 -3
  23. package/dist/lifecycle/opening-buys.js.map +1 -1
  24. package/dist/lifecycle/planner.d.ts.map +1 -1
  25. package/dist/lifecycle/planner.js +20 -1
  26. package/dist/lifecycle/planner.js.map +1 -1
  27. package/dist/lifecycle/presets.d.ts +4 -2
  28. package/dist/lifecycle/presets.d.ts.map +1 -1
  29. package/dist/lifecycle/presets.js +8 -1
  30. package/dist/lifecycle/presets.js.map +1 -1
  31. package/dist/lifecycle/raeto-abi.d.ts +175 -0
  32. package/dist/lifecycle/raeto-abi.d.ts.map +1 -0
  33. package/dist/lifecycle/raeto-abi.js +224 -0
  34. package/dist/lifecycle/raeto-abi.js.map +1 -0
  35. package/dist/lifecycle/raeto.d.ts +21 -0
  36. package/dist/lifecycle/raeto.d.ts.map +1 -0
  37. package/dist/lifecycle/raeto.js +59 -0
  38. package/dist/lifecycle/raeto.js.map +1 -0
  39. package/dist/lifecycle/transactions.d.ts.map +1 -1
  40. package/dist/lifecycle/transactions.js +8 -1
  41. package/dist/lifecycle/transactions.js.map +1 -1
  42. package/docs/launch-lifecycle.md +75 -6
  43. package/docs/lifecycle-gas-profiles.md +141 -24
  44. package/docs/reference.md +59 -4
  45. package/examples/README.md +2 -2
  46. package/package.json +1 -1
  47. package/src/lifecycle/deployments/raeto-dynamic-pool-v1-20261029.ts +74 -0
  48. package/src/lifecycle/deployments/raeto-fixed-pool-v1-20261029.ts +74 -0
  49. package/src/lifecycle/gas-profile.ts +17 -7
  50. package/src/lifecycle/index.ts +2 -0
  51. package/src/lifecycle/markets.ts +23 -1
  52. package/src/lifecycle/opening-buys.ts +3 -3
  53. package/src/lifecycle/planner.ts +18 -1
  54. package/src/lifecycle/presets.ts +8 -1
  55. package/src/lifecycle/raeto-abi.ts +223 -0
  56. package/src/lifecycle/raeto.ts +61 -0
  57. package/src/lifecycle/transactions.ts +8 -1
@@ -9,18 +9,19 @@ import { buildLifecycleCommand, LIFECYCLE_CAPACITY_PADDING_BPS, preparationCalld
9
9
  import { UFG_LAUNCH_PROXY_ADDRESS, isUfgProfile, ufgPlanPrincipal, type UfgLaunchConfig } from "./ufg-config.js";
10
10
  import { LifecyclePlanningError, type LifecycleBlock, type LifecycleLaunchGasBounds, type LifecycleLimitSource, type LifecycleOpeningBuyQuote, type LifecyclePlanningAssessment, type LifecycleRpcClient, type LifecycleTransaction, type PlannedLaunch } from "./types.js";
11
11
  import { isHmmProfile } from "./hmm.js";
12
+ import { isRaetoProfile } from "./raeto.js";
12
13
 
13
- export const LIFECYCLE_GAS_PROFILE_VERSION = "robinhood-4663-capacity-v3";
14
+ export const LIFECYCLE_GAS_PROFILE_VERSION = "robinhood-4663-capacity-v4";
14
15
  export type NextTransactionEnvelope = Readonly<{ gas: bigint; gasPrice: bigint }>;
15
16
  const ceil = (value: bigint, divisor: bigint) => (value + divisor - 1n) / divisor;
16
17
  const approval = (transaction: LifecycleTransaction) => transaction.kind === "approve" || transaction.kind === "approve-reset";
17
- type MarketGas = { abyss: boolean; hmm: boolean; ufgHook: boolean; positions: number };
18
+ type MarketGas = { abyss: boolean; hmm: boolean; ufgHook: boolean; raeto: boolean; autoCompound: boolean; positions: number };
18
19
 
19
20
  function marketGas(plan: LaunchPlanV1): MarketGas[] {
20
21
  return plan.markets.map((market) => {
21
22
  const abyss = market.configVersion === 1;
22
23
  const config = abyss ? decodeAbyssLifecycleMarketConfig(market.config) : decodePoolBoundV4LifecycleMarketConfig(market.config, 6);
23
- return { abyss, hmm: isHmmProfile(market.profileId), ufgHook: isUfgProfile(market.profileId), positions: config.positions.length };
24
+ return { abyss, hmm: isHmmProfile(market.profileId), ufgHook: isUfgProfile(market.profileId), raeto: isRaetoProfile(market.profileId), autoCompound: isAutoCompoundProfile(market.profileId), positions: config.positions.length };
24
25
  });
25
26
  }
26
27
 
@@ -51,6 +52,8 @@ const activationCosts = {
51
52
  call: 1_500_000n,
52
53
  abyss: { market: 500_000n, position: 1_250_000n },
53
54
  v4: { market: 250_000n, position: 500_000n },
55
+ raeto: { market: 500_000n, position: 500_000n },
56
+ autoCompound: { market: 750_000n, position: 500_000n },
54
57
  hmm: 1_000_000n,
55
58
  ufg: 250_000n,
56
59
  // Fork Abyss32: 64 buys78.44M minimum versus one buy47.49M;
@@ -62,17 +65,26 @@ const activationCosts = {
62
65
  /** Measured-max template budgets, never gas-limit sizing or execution evidence. */
63
66
  function capacityBasis(plan: LaunchPlanV1, transaction: LifecycleTransaction, markets: readonly MarketGas[], quotes: readonly LifecycleOpeningBuyQuote[], ufgRoute: boolean): bigint {
64
67
  const atomic = transaction.kind === "atomic";
65
- let gas = atomic ? 22_000_000n + tokenCreationGas(plan.token) + (ufgRoute ? 6_500_000n : 0n)
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)
66
72
  : activationCosts.call + (ufgRoute ? activationCosts.ufg : 0n);
67
73
  for (const market of markets) {
68
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
76
  else {
71
- const cost = market.abyss ? activationCosts.abyss : activationCosts.v4;
77
+ const cost = market.abyss ? activationCosts.abyss : market.raeto ? activationCosts.raeto : market.autoCompound ? activationCosts.autoCompound : activationCosts.v4;
72
78
  gas += cost.market + cost.position * BigInt(market.positions) + (market.hmm ? activationCosts.hmm : market.ufgHook ? activationCosts.ufg : 0n);
73
79
  }
74
80
  }
75
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.
84
+ for (const quote of quotes) {
85
+ const market = markets[quote.marketIndex]!;
86
+ if (market.raeto || market.autoCompound) gas += 25_000n * BigInt(market.positions);
87
+ }
76
88
  if (plan.token.kind === LifecycleTokenKind.ERC404) for (const quote of quotes) gas += activationCosts.nft * ceil(quote.tokenOut, plan.token.nftUnit);
77
89
  return gas + 24n * BigInt((transaction.data.length - 2) / 2) + 100_000n * BigInt(plan.feeAssets.length);
78
90
  }
@@ -87,8 +99,6 @@ export type ProfiledLaunchCapacity = {
87
99
  export function deriveLifecycleLaunchCapacity(options: CapacityOptions, openingBuys?: readonly LifecycleOpeningBuyQuote[]): ProfiledLaunchCapacity | undefined {
88
100
  const { plan, ufg } = options;
89
101
  if (!hasProfiledOpeningMath(plan) || (plan.creator.toLowerCase() === UFG_LAUNCH_PROXY_ADDRESS.toLowerCase()) !== (ufg !== undefined)) return undefined;
90
- // Auto-compounding templates have no measured gas yet; never invent their budgets.
91
- if (plan.markets.some((market) => isAutoCompoundProfile(market.profileId))) return undefined;
92
102
  const headroomBps = options.headroomBps ?? 1500;
93
103
  if (!Number.isSafeInteger(headroomBps) || headroomBps < 0 || headroomBps > 10000) throw new LifecyclePlanningError("INVALID_LIMITS", "Headroom must be between zero and 10000 basis points");
94
104
  const initialBuys = openingBuys ?? computeLifecycleOpeningBuys(options);
@@ -6,6 +6,8 @@ export * from "./markets.js";
6
6
  export * from "./hmm.js";
7
7
  export * from "./ufg.js";
8
8
  export * from "./auto-compound.js";
9
+ export * from "./raeto.js";
10
+ export * from "./raeto-abi.js";
9
11
  export { ABYSS_LIFECYCLE_CONFIG_SCHEMA, readLifecycleTokenAtBlock, readLifecycleHeadProgress, predictLifecycleToken,
10
12
  validateV4LifecycleOracle, readLifecycleProfiles, readPoolBoundHookDeployment,
11
13
  buildPoolBoundHookDeploymentTransaction, validateLifecycleMarketIdentity, readLaunchProgress } from "./progress.js";
@@ -4,6 +4,7 @@ import { LifecyclePlanningError, type LifecycleProfile, type PoolBoundHookSaltPr
4
4
  import { decodeHmmLaunchTail, encodeHmmLaunchTail, isHmmProfile, type HmmLaunchConfig } from "./hmm.js";
5
5
  import { decodeUfgLaunchConfig, encodeUfgLaunchConfig, isUfgProfile, type UfgLaunchConfig } from "./ufg-config.js";
6
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";
7
8
 
8
9
  export type V4LifecyclePositionConfig = {
9
10
  tickLower: number; tickUpper: number; liquidity: bigint; salt: Hex; maxTokenAmount: bigint;
@@ -48,6 +49,7 @@ export type V4PoolBoundLifecycleMarketConfigV6 = Omit<V4LifecycleMarketConfig, "
48
49
  hmmLaunchConfig?: HmmLaunchConfig;
49
50
  ufgLaunchConfig?: UfgLaunchConfig;
50
51
  autoCompoundConfig?: AutoCompoundConfigV1;
52
+ raetoLaunchConfig?: RaetoLaunchConfigV1;
51
53
  };
52
54
  export type V4PoolBoundLifecycleMarketConfig = V4PoolBoundLifecycleMarketConfigV5 | V4PoolBoundLifecycleMarketConfigV6;
53
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)[])"));
@@ -96,15 +98,27 @@ function assertV1HookFeeShape(config: object): void {
96
98
 
97
99
  function validateAutoCompoundMarket(config: V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig): asserts config is V4PoolBoundLifecycleMarketConfigV6 & { autoCompoundConfig: AutoCompoundConfigV1 } {
98
100
  if (config.version !== 6 || config.autoCompoundConfig === undefined) throw new LifecyclePlanningError("INVALID_AUTO_COMPOUND_CONFIG", "Auto-compounding profiles require config6 and an explicit autoCompoundConfig");
99
- if (config.hmmLaunchConfig !== undefined || config.ufgLaunchConfig !== undefined) throw new LifecyclePlanningError("INVALID_AUTO_COMPOUND_CONFIG", "Auto-compounding profiles cannot carry HMM or UFG tails");
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");
100
102
  if (config.lpFeePips !== 0) throw new LifecyclePlanningError("INVALID_HOOK_FEES", "Auto-compounding profiles require zero LP fee");
101
103
  if (config.developerFeeBps !== 0) throw new LifecyclePlanningError("DEVELOPER_FEE_CEILING", "Auto-compounding profiles require zero developer fee");
102
104
  validateAutoCompoundConfig(config.autoCompoundConfig);
103
105
  }
104
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
+
105
114
  /** Auxiliary launch profiles require their typed tail and append it automatically. */
106
115
  export function encodePoolBoundV4LifecycleMarketConfig(config: V4PoolBoundLifecycleMarketConfig): Hex {
107
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");
108
122
  if (isAutoCompoundProfile(config.profileId)) {
109
123
  validateAutoCompoundMarket(config);
110
124
  return concatHex([prefix, encodeAutoCompoundTail(config.autoCompoundConfig)]);
@@ -145,6 +159,10 @@ export function decodePoolBoundV4LifecycleMarketConfig(encoded: Hex, expectedVer
145
159
  const config = version === 5
146
160
  ? decodeAbiParameters([{ type: "tuple", components: poolBoundV4LifecycleMarketComponentsV5 }], encoded)[0] as V4PoolBoundLifecycleMarketConfigV5
147
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
+ }
148
166
  if (isAutoCompoundProfile(config.profileId)) {
149
167
  if (config.version !== 6) throw new LifecyclePlanningError("INVALID_AUTO_COMPOUND_CONFIG", "Auto-compounding profiles require config6");
150
168
  config.autoCompoundConfig = decodeAutoCompoundTail(`0x${encoded.slice(encodePoolBoundV4Prefix(config).length)}`);
@@ -180,6 +198,10 @@ export function validateV4LifecycleMarket(options: {
180
198
  if (config.version === 6) validatePoolBoundHookFees(config);
181
199
  else if (config.version === 5) assertV1HookFeeShape(config);
182
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");
183
205
  const { envelope, developerTerms: terms, protocolMaximumDeveloperFeeBps: protocolCeiling } = profile;
184
206
  if (envelope === undefined || terms === undefined || protocolCeiling === undefined || !terms.enabled ||
185
207
  profile.topology.hookTopology !== (config.version === 4 ? 1 : 2) || market.configVersion !== config.version ||
@@ -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 || preset.key === KnownLifecycleProfile.V4AutoCompoundDynamicPool;
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>();
@@ -2,7 +2,7 @@ import { encodeFunctionData, keccak256, toHex, zeroAddress, type Address, type H
2
2
  import { ABYSS_FEE_TIERS } from "../abyss.js";
3
3
  import { assertLaunchTopologyCoverage, getTickAtSqrtRatio } from "../launch.js";
4
4
  import { launchLifecycleAbi, lifecycleAdapterAbi, lifecycleErc20Abi } from "./abi.js";
5
- import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, LIFECYCLE_MAX_ERC404_SUPPLY, parseLaunchPlan, serializeLaunchPlan, type LaunchPlanV1 } from "./schema.js";
5
+ import { hashLaunchIdentity, hashLaunchPlan, LifecycleFundingKind, LifecyclePhase, LifecycleRewardMode, LifecycleTokenKind, LIFECYCLE_MAX_ERC404_SUPPLY, LIFECYCLE_MAX_REWARD_ERC20_SUPPLY, parseLaunchPlan, serializeLaunchPlan, type LaunchPlanV1, type MarketConfigV1 } from "./schema.js";
6
6
  import { readLaunchProgress, readLifecycleExecutionProgress, readLifecycleHeadProgress, readLifecycleProfiles, readLifecycleTokenAtBlock, readPoolBoundHookDeployment } from "./progress.js";
7
7
  import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, encodePoolBoundV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion, type AbyssLifecycleMarketConfig, type V4LifecycleMarketConfig, type V4PoolBoundLifecycleMarketConfig } from "./markets.js";
8
8
  import { lifecyclePinnedRpc, lifecycleStaticRpc, lifecycleStage, nitroPosterGasBound, readLifecycleChainId, readLifecycleContract, readNitroPrices, registerLifecycleFinalCheck, resolveLifecycleLimits, rpcHex, rpcQuantity, withLifecycleReadClient } from "./rpc.js";
@@ -18,6 +18,7 @@ import { mineLifecycleHooks } from "./mining.js";
18
18
  import { computeLifecycleOpeningBuys, hasProfiledOpeningMath } from "./opening-buys.js";
19
19
  import { assessLifecycleTransactions, deriveLifecycleLaunchCapacity, type NextTransactionEnvelope, type ProfiledLaunchCapacity } from "./gas-profile.js";
20
20
  import type { LifecycleOpeningBuyQuote } from "./types.js";
21
+ import { isRaetoProfile, RAETO_QUOTE_ASSETS } from "./raeto.js";
21
22
 
22
23
  type ValidatedPlanShape = {
23
24
  commitment: Hex;
@@ -53,6 +54,20 @@ async function preparationBatches(plan: LaunchPlanV1, prepared: number, ufg: Pla
53
54
  return packPreparationBatches(plan, prepared, limits.executionGasCeiling - poster, limits.headroomBps);
54
55
  }
55
56
 
57
+ /** Preserve Raeto's outer quote/profile binding even on known-preset recovery. */
58
+ function validateRaetoMarketContext(market: MarketConfigV1, config?: V4LifecycleMarketConfig | V4PoolBoundLifecycleMarketConfig): void {
59
+ if (!isRaetoProfile(market.profileId)) return;
60
+ if (market.configVersion !== 6 || !RAETO_QUOTE_ASSETS.some((quote) => quote.toLowerCase() === market.quoteAsset.toLowerCase())) {
61
+ throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto requires config6 and a WETH or USDG quote");
62
+ }
63
+ // Normal planning supplies its already decoded config. Continuation must also
64
+ // reject malformed fee/share tails before reading or building its next command.
65
+ const decoded = config ?? decodePoolBoundV4LifecycleMarketConfig(market.config, 6);
66
+ if (decoded.version !== 6 || decoded.profileId.toLowerCase() !== market.profileId.toLowerCase()) {
67
+ throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto config must bind its market's Raeto profile");
68
+ }
69
+ }
70
+
56
71
  function validatePlanShape(plan: LaunchPlanV1): ValidatedPlanShape {
57
72
  // Encoding checks all exact integer widths, addresses, byte strings and tuple fields.
58
73
  const commitment = hashLaunchPlan(plan);
@@ -85,6 +100,7 @@ function validatePlanShape(plan: LaunchPlanV1): ValidatedPlanShape {
85
100
  if (market.configVersion !== 1 && market.configVersion !== 4 && !isPoolBoundV4ConfigVersion(market.configVersion)) throw new LifecyclePlanningError("UNSUPPORTED_CONFIG_VERSION", "Encode reviewed SharedV4 version 4 or pool-bound version 5/6 only on its exact matching deployment");
86
101
  if (market.configVersion === 4) config = decodeV4LifecycleMarketConfig(market.config);
87
102
  else if (isPoolBoundV4ConfigVersion(market.configVersion)) config = decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion);
103
+ validateRaetoMarketContext(market, config);
88
104
  if (config !== undefined) {
89
105
  if (config.profileId.toLowerCase() !== market.profileId.toLowerCase()) throw new LifecyclePlanningError("PROFILE_TERMS_MISMATCH", "Inner config profile differs from the committed market profile");
90
106
  const quote = market.quoteAsset.toLowerCase();
@@ -455,6 +471,7 @@ function finalizeNextTransaction(options: BuildNextTransactionOptions, next: Lif
455
471
  /** Recovery observes actual canonical receipts, then constructs and budgets only
456
472
  * the next command. No launch transaction is executed during planning. */
457
473
  export async function buildNextTransaction(options: BuildNextTransactionOptions): Promise<LifecycleTransaction | undefined> {
474
+ if (options.action !== "cancel") for (const market of options.planned.plan.markets) validateRaetoMarketContext(market);
458
475
  const original = options.planned, matches = observedFields(original);
459
476
  const assertInput = () => {
460
477
  if (options.planned !== original || !matches()) throw new LifecyclePlanningError("PLAN_MUTATED", "Stored launch context changed while constructing its next transaction");
@@ -14,6 +14,9 @@ import { UFG_FIXED_PROFILE_ID, UFG_DYNAMIC_PROFILE_ID } from "./ufg-config.js";
14
14
  import { autoCompoundStaticEnvelope, autoCompoundStaticAdapter, autoCompoundStaticRegistration, autoCompoundStaticCreationCode } from "./deployments/auto-compound-static-pool-v1-20261028.js";
15
15
  import { autoCompoundDynamicEnvelope, autoCompoundDynamicAdapter, autoCompoundDynamicRegistration, autoCompoundDynamicCreationCode } from "./deployments/auto-compound-dynamic-pool-v1-20261028.js";
16
16
  import { AUTO_COMPOUND_STATIC_V1_PROFILE_ID, AUTO_COMPOUND_DYNAMIC_V1_PROFILE_ID } from "./auto-compound.js";
17
+ import { raetoFixedEnvelope, raetoFixedAdapter, raetoFixedRegistration } from "./deployments/raeto-fixed-pool-v1-20261029.js";
18
+ import { raetoDynamicEnvelope, raetoDynamicAdapter, raetoDynamicRegistration } from "./deployments/raeto-dynamic-pool-v1-20261029.js";
19
+ import { RAETO_FIXED_PROFILE_ID, RAETO_DYNAMIC_PROFILE_ID } from "./raeto.js";
17
20
 
18
21
  export enum KnownLifecycleProfile {
19
22
  Abyss0 = "abyss-0",
@@ -28,6 +31,8 @@ export enum KnownLifecycleProfile {
28
31
  V4UfgDynamicPool = "v4-ufg-dynamic-pool",
29
32
  V4AutoCompoundStaticPool = "v4-auto-compound-static-pool",
30
33
  V4AutoCompoundDynamicPool = "v4-auto-compound-dynamic-pool",
34
+ V4RaetoFixedPool = "v4-raeto-fixed-pool",
35
+ V4RaetoDynamicPool = "v4-raeto-dynamic-pool",
31
36
  }
32
37
 
33
38
  export enum KnownLifecycleHook {
@@ -360,6 +365,8 @@ const knownDeployment: LifecycleDeploymentPreset = Object.freeze({
360
365
  poolBoundPreset(KnownLifecycleProfile.V4UfgDynamicPool, UFG_DYNAMIC_PROFILE_ID, ufgDynamicEnvelope, ufgDynamicAdapter, ufgDynamicRegistration, dynamicFeePoolHookV1CreationCode),
361
366
  poolBoundPreset(KnownLifecycleProfile.V4AutoCompoundStaticPool, AUTO_COMPOUND_STATIC_V1_PROFILE_ID, autoCompoundStaticEnvelope, autoCompoundStaticAdapter, autoCompoundStaticRegistration, autoCompoundStaticCreationCode),
362
367
  poolBoundPreset(KnownLifecycleProfile.V4AutoCompoundDynamicPool, AUTO_COMPOUND_DYNAMIC_V1_PROFILE_ID, autoCompoundDynamicEnvelope, autoCompoundDynamicAdapter, autoCompoundDynamicRegistration, autoCompoundDynamicCreationCode),
368
+ poolBoundPreset(KnownLifecycleProfile.V4RaetoFixedPool, RAETO_FIXED_PROFILE_ID, raetoFixedEnvelope, raetoFixedAdapter, raetoFixedRegistration, fixedFeePoolHookV1CreationCode),
369
+ poolBoundPreset(KnownLifecycleProfile.V4RaetoDynamicPool, RAETO_DYNAMIC_PROFILE_ID, raetoDynamicEnvelope, raetoDynamicAdapter, raetoDynamicRegistration, dynamicFeePoolHookV1CreationCode),
363
370
  ]),
364
371
  provenance: Object.freeze({ sourceCommit: "e174ce695c7010f50ca9211b07b9f178c990beac", deploymentBlock: 82880546n }),
365
372
  });
@@ -389,7 +396,7 @@ export function getKnownLifecycleProfile(options: KnownLifecycleDeploymentIdenti
389
396
  return undefined;
390
397
  }
391
398
 
392
- /** Stable, frozen order: Abyss, fixed/dynamic V4, HMM, UFG, then auto-compounding. */
399
+ /** Stable, frozen order: Abyss, fixed/dynamic V4, HMM, UFG, auto-compounding, then Raeto. */
393
400
  export function listKnownLifecycleProfiles(options: KnownLifecycleDeploymentIdentity): readonly LifecycleProfilePreset[] {
394
401
  return getKnownLifecycleDeployment(options)?.profiles ?? noProfiles;
395
402
  }
@@ -0,0 +1,223 @@
1
+ // Generated from the verified RaetoFeeCollectorV1 ABI by scripts/generate-raeto-presets.mjs.
2
+ // Provenance: test/fixtures/raeto-registry-readback.json, abiProvenance.
3
+ export const raetoFeeCollectorV1Abi = [
4
+ {
5
+ "anonymous": false,
6
+ "inputs": [
7
+ {
8
+ "indexed": true,
9
+ "internalType": "address",
10
+ "name": "launchToken",
11
+ "type": "address"
12
+ },
13
+ {
14
+ "indexed": true,
15
+ "internalType": "bytes32",
16
+ "name": "poolId",
17
+ "type": "bytes32"
18
+ },
19
+ {
20
+ "indexed": true,
21
+ "internalType": "address",
22
+ "name": "asset",
23
+ "type": "address"
24
+ },
25
+ {
26
+ "indexed": false,
27
+ "internalType": "uint256",
28
+ "name": "grossQuoteCollected",
29
+ "type": "uint256"
30
+ },
31
+ {
32
+ "indexed": false,
33
+ "internalType": "uint256",
34
+ "name": "raetoAmount",
35
+ "type": "uint256"
36
+ },
37
+ {
38
+ "indexed": false,
39
+ "internalType": "uint256",
40
+ "name": "forwardedQuote",
41
+ "type": "uint256"
42
+ },
43
+ {
44
+ "indexed": false,
45
+ "internalType": "uint16",
46
+ "name": "shareBps",
47
+ "type": "uint16"
48
+ },
49
+ {
50
+ "indexed": false,
51
+ "internalType": "uint256",
52
+ "name": "owedAfter",
53
+ "type": "uint256"
54
+ }
55
+ ],
56
+ "name": "RaetoFeesBooked",
57
+ "type": "event"
58
+ },
59
+ {
60
+ "anonymous": false,
61
+ "inputs": [
62
+ {
63
+ "indexed": true,
64
+ "internalType": "address",
65
+ "name": "launchToken",
66
+ "type": "address"
67
+ },
68
+ {
69
+ "indexed": true,
70
+ "internalType": "bytes32",
71
+ "name": "poolId",
72
+ "type": "bytes32"
73
+ },
74
+ {
75
+ "indexed": true,
76
+ "internalType": "address",
77
+ "name": "payout",
78
+ "type": "address"
79
+ },
80
+ {
81
+ "indexed": false,
82
+ "internalType": "address",
83
+ "name": "asset",
84
+ "type": "address"
85
+ },
86
+ {
87
+ "indexed": false,
88
+ "internalType": "uint256",
89
+ "name": "amount",
90
+ "type": "uint256"
91
+ }
92
+ ],
93
+ "name": "RaetoFeesClaimed",
94
+ "type": "event"
95
+ },
96
+ {
97
+ "anonymous": false,
98
+ "inputs": [
99
+ {
100
+ "indexed": true,
101
+ "internalType": "address",
102
+ "name": "launchToken",
103
+ "type": "address"
104
+ },
105
+ {
106
+ "indexed": true,
107
+ "internalType": "bytes32",
108
+ "name": "poolId",
109
+ "type": "bytes32"
110
+ },
111
+ {
112
+ "indexed": true,
113
+ "internalType": "address",
114
+ "name": "operator",
115
+ "type": "address"
116
+ },
117
+ {
118
+ "indexed": false,
119
+ "internalType": "uint16",
120
+ "name": "previousShareBps",
121
+ "type": "uint16"
122
+ },
123
+ {
124
+ "indexed": false,
125
+ "internalType": "uint16",
126
+ "name": "newShareBps",
127
+ "type": "uint16"
128
+ }
129
+ ],
130
+ "name": "RaetoShareUpdated",
131
+ "type": "event"
132
+ },
133
+ {
134
+ "inputs": [
135
+ {
136
+ "internalType": "address",
137
+ "name": "asset",
138
+ "type": "address"
139
+ }
140
+ ],
141
+ "name": "claimRaeto",
142
+ "outputs": [
143
+ {
144
+ "internalType": "uint256",
145
+ "name": "amount",
146
+ "type": "uint256"
147
+ }
148
+ ],
149
+ "stateMutability": "nonpayable",
150
+ "type": "function"
151
+ },
152
+ {
153
+ "inputs": [],
154
+ "name": "launchToken",
155
+ "outputs": [
156
+ {
157
+ "internalType": "address",
158
+ "name": "",
159
+ "type": "address"
160
+ }
161
+ ],
162
+ "stateMutability": "view",
163
+ "type": "function"
164
+ },
165
+ {
166
+ "inputs": [
167
+ {
168
+ "internalType": "address",
169
+ "name": "asset",
170
+ "type": "address"
171
+ }
172
+ ],
173
+ "name": "owed",
174
+ "outputs": [
175
+ {
176
+ "internalType": "uint256",
177
+ "name": "amount",
178
+ "type": "uint256"
179
+ }
180
+ ],
181
+ "stateMutability": "view",
182
+ "type": "function"
183
+ },
184
+ {
185
+ "inputs": [],
186
+ "name": "quoteAsset",
187
+ "outputs": [
188
+ {
189
+ "internalType": "address",
190
+ "name": "",
191
+ "type": "address"
192
+ }
193
+ ],
194
+ "stateMutability": "view",
195
+ "type": "function"
196
+ },
197
+ {
198
+ "inputs": [
199
+ {
200
+ "internalType": "uint16",
201
+ "name": "newShareBps",
202
+ "type": "uint16"
203
+ }
204
+ ],
205
+ "name": "setShareBps",
206
+ "outputs": [],
207
+ "stateMutability": "nonpayable",
208
+ "type": "function"
209
+ },
210
+ {
211
+ "inputs": [],
212
+ "name": "shareBps",
213
+ "outputs": [
214
+ {
215
+ "internalType": "uint16",
216
+ "name": "",
217
+ "type": "uint16"
218
+ }
219
+ ],
220
+ "stateMutability": "view",
221
+ "type": "function"
222
+ }
223
+ ] as const;
@@ -0,0 +1,61 @@
1
+ import { encodeAbiParameters, type Hex } from "viem";
2
+ import { decodePoolBoundV4LifecycleMarketConfig } from "./markets.js";
3
+ import type { LaunchPlanV1 } from "./schema.js";
4
+ import { LifecyclePlanningError } from "./types.js";
5
+
6
+ export const RAETO_FIXED_PROFILE_ID: Hex = "0x9161833b162e7ca8160b11c64aedb307561e19a9136fd32f99cd797aab6da3ca";
7
+ export const RAETO_DYNAMIC_PROFILE_ID: Hex = "0x08387d00ee4f75f860810cc8fb434087328592c2f39ea3321109e589039f80e1";
8
+ export const RAETO_FIXED_ADAPTER_ID: Hex = "0x911d7ecefe56489d329235e3484829717dc00400d4181902f0f2de45f9338615";
9
+ export const RAETO_DYNAMIC_ADAPTER_ID: Hex = "0x8ab9c8c9b8fd4c8bbf12b780d9474463fa0fa52e3b450f0d5f68fd067dc1674d";
10
+ export const RAETO_PAYOUT_REGISTRY_ADDRESS = "0xBEC2C0e91612090F82df6743F31CDF18B4386b50";
11
+ /** The deployed collectors accept this menu, not the wider range in later source. */
12
+ export const RAETO_SHARE_BPS_OPTIONS = Object.freeze([500, 1000, 2500, 5000, 7500] as const);
13
+ export const RAETO_QUOTE_ASSETS = Object.freeze([
14
+ "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
15
+ "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
16
+ ] as const);
17
+ export type RaetoLaunchConfigV1 = { shareBps: number };
18
+
19
+ export function isRaetoProfile(profileId: Hex): boolean {
20
+ const id = profileId.toLowerCase();
21
+ return id === RAETO_FIXED_PROFILE_ID || id === RAETO_DYNAMIC_PROFILE_ID;
22
+ }
23
+
24
+ export function validateRaetoLaunchConfig(config: RaetoLaunchConfigV1): void {
25
+ if (!RAETO_SHARE_BPS_OPTIONS.some((share) => share === config.shareBps)) {
26
+ throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto shareBps must be one of 500, 1000, 2500, 5000 or 7500");
27
+ }
28
+ }
29
+
30
+ /** Exactly abi.encode(RaetoLaunchConfigV1): one static uint16 word. */
31
+ export function encodeRaetoLaunchTail(config: RaetoLaunchConfigV1): Hex {
32
+ validateRaetoLaunchConfig(config);
33
+ return encodeAbiParameters([{ type: "uint16" }], [config.shareBps]);
34
+ }
35
+
36
+ export function decodeRaetoLaunchTail(encoded: Hex): RaetoLaunchConfigV1 {
37
+ if (!/^0x[0-9a-fA-F]{64}$/.test(encoded)) throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto tail must be exactly one ABI word");
38
+ const share = BigInt(encoded);
39
+ if (share > 65535n) throw new LifecyclePlanningError("INVALID_RAETO_CONFIG", "Raeto tail must contain a canonical uint16");
40
+ const config = { shareBps: Number(share) };
41
+ validateRaetoLaunchConfig(config);
42
+ return config;
43
+ }
44
+
45
+ /** Raeto uses the normal lifecycle creator/funding route, not the UFG proxy. */
46
+ export function validateRaetoLaunchPlan(plan: LaunchPlanV1): void {
47
+ try {
48
+ if (plan.markets.length === 0) throw new Error("A Raeto plan requires at least one market");
49
+ for (const market of plan.markets) {
50
+ if (!isRaetoProfile(market.profileId) || market.configVersion !== 6 ||
51
+ !RAETO_QUOTE_ASSETS.some((quote) => quote.toLowerCase() === market.quoteAsset.toLowerCase()) ||
52
+ market.adapterId.toLowerCase() !== (market.profileId.toLowerCase() === RAETO_FIXED_PROFILE_ID ? RAETO_FIXED_ADAPTER_ID : RAETO_DYNAMIC_ADAPTER_ID)) {
53
+ throw new Error("Every Raeto market requires its Raeto config6 profile/adapter and a WETH or USDG quote");
54
+ }
55
+ const config = decodePoolBoundV4LifecycleMarketConfig(market.config, 6);
56
+ if (config.profileId.toLowerCase() !== market.profileId.toLowerCase()) throw new Error("Raeto config profile differs from its market");
57
+ }
58
+ } catch (error) {
59
+ throw new LifecyclePlanningError("INVALID_RAETO_PLAN", error instanceof Error ? error.message : "Invalid Raeto launch plan");
60
+ }
61
+ }
@@ -7,6 +7,8 @@ import { ufgLaunchProxyV1Abi } from "./ufg-abi.js";
7
7
  import { UFG_LAUNCH_PROXY_ADDRESS, isUfgProfile } from "./ufg-config.js";
8
8
  import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion } from "./markets.js";
9
9
  import { isHmmProfile } from "./hmm.js";
10
+ import { isRaetoProfile } from "./raeto.js";
11
+ import { isAutoCompoundProfile } from "./auto-compound.js";
10
12
  import type { UfgLaunchConfig } from "./ufg-config.js";
11
13
 
12
14
  // Native9,723,187 Abyss3 (0x679d…e77), fork32-position10,900,786;
@@ -16,6 +18,10 @@ const preparationCosts = {
16
18
  v4: { market: 9_000_000n, position: 40_000n },
17
19
  ufg: { market: 10_000_000n, position: 70_000n },
18
20
  hmm: { market: 15_000_000n, position: 50_000n },
21
+ // template-gas-matrix.json: Raeto1 <=9.13M,32 <=11.23M.
22
+ raeto: { market: 9_300_000n, position: 70_000n },
23
+ // Same matrix: auto-compound1 <=8.71M,32 <=10.76M.
24
+ autoCompound: { market: 8_900_000n, position: 70_000n },
19
25
  } as const;
20
26
  export const LIFECYCLE_CAPACITY_PADDING_BPS = 2500;
21
27
  /** Full single-market observations include call overhead. Add conservative full
@@ -30,7 +36,8 @@ export function preparationGasEstimate(plan: LaunchPlanV1, first: number, count:
30
36
  : isPoolBoundV4ConfigVersion(market.configVersion) ? decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion).positions.length
31
37
  : decodeV4LifecycleMarketConfig(market.config).positions.length;
32
38
  const cost = market.configVersion === 1 ? preparationCosts.abyss : isHmmProfile(market.profileId) ? preparationCosts.hmm
33
- : isUfgProfile(market.profileId) ? preparationCosts.ufg : preparationCosts.v4;
39
+ : isUfgProfile(market.profileId) ? preparationCosts.ufg : isRaetoProfile(market.profileId) ? preparationCosts.raeto
40
+ : isAutoCompoundProfile(market.profileId) ? preparationCosts.autoCompound : preparationCosts.v4;
34
41
  gas += cost.market + cost.position * BigInt(positions - 1);
35
42
  }
36
43
  return gas;