@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
@@ -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
+ }
@@ -22,8 +22,8 @@ type IndependentRead = { params: readonly unknown[]; resolve: (value: unknown) =
22
22
  const lifecycleReadConcurrency = 8;
23
23
  const diagnosticRpcMethods: Record<string, true> = {
24
24
  eth_chainId: true, eth_getBlockByNumber: true, eth_getBlockByHash: true, eth_getCode: true,
25
- eth_getBalance: true, eth_call: true, eth_estimateGas: true, eth_gasPrice: true,
26
- eth_simulateV1: true, eth_getTransactionCount: true, eth_getTransactionReceipt: true, eth_getTransactionByHash: true,
25
+ eth_getBalance: true, eth_call: true, eth_gasPrice: true,
26
+ eth_getTransactionReceipt: true, eth_getTransactionByHash: true,
27
27
  };
28
28
 
29
29
  type SessionReadGroup = {
@@ -70,7 +70,7 @@ class SessionLifecycleCache implements LifecycleSessionCache {
70
70
  }
71
71
  }
72
72
 
73
- /** Opt-in pinned read reuse; client-bound identity/capability facts are separate. */
73
+ /** Opt-in pinned read reuse; client-bound identity/deployment facts are separate. */
74
74
  export function createLifecycleSessionCache(): LifecycleSessionCache {
75
75
  return new SessionLifecycleCache();
76
76
  }
@@ -410,7 +410,7 @@ export function readLifecycleImmutable<T>(client: LifecycleRpcClient, key: strin
410
410
  return (client instanceof InvocationLifecycleClient ? client.immutableRead(key, work) : work()) as Promise<T>;
411
411
  }
412
412
 
413
- /** Successful deployment/backend observations live for the caller's session, not one block. */
413
+ /** Successful deployment observations live for the caller's session, not one block. */
414
414
  export function lifecycleSessionRead<T>(client: LifecycleRpcClient, key: string, work: () => Promise<T>): Promise<T> {
415
415
  const source = lifecycleSourceClient(client);
416
416
  const owner = source.sessionCache ?? source;
@@ -524,9 +524,7 @@ export async function readLifecycleProfileTopology(client: LifecycleRpcClient, r
524
524
  if (topology.hookTopology !== 0 && topology.hookTopology !== 1 && topology.hookTopology !== 2) throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", "Registry returned an unsupported hook topology");
525
525
  return { ...topology, hookTopology: topology.hookTopology };
526
526
  }
527
- export const nitroArbSys = "0x0000000000000000000000000000000000000064";
528
527
  export const nitroGasInfo = "0x000000000000000000000000000000000000006c";
529
- export const nitroArbSysAbi = parseAbi(["function arbOSVersion() view returns (uint256)"]);
530
528
  export const nitroGasInfoAbi = parseAbi([
531
529
  // Transaction ceilings are release preset metadata, not guard-only getter reads.
532
530
  "function getPricesInWei() view returns (uint256,uint256,uint256,uint256,uint256,uint256)",
@@ -534,7 +532,7 @@ export const nitroGasInfoAbi = parseAbi([
534
532
  export type LifecycleNitroPrices = readonly [bigint, bigint, bigint, bigint, bigint, bigint];
535
533
  const maxUint64 = (1n << 64n) - 1n;
536
534
 
537
- /** Seeded by the planning aggregate; standalone simulation batches this with limits. */
535
+ /** Seeded by the explicitly pinned planning aggregate. */
538
536
  export function readNitroPrices(client: LifecycleRpcClient, block: LifecycleBlock): Promise<LifecycleNitroPrices> {
539
537
  return readLifecycleContract(client, nitroGasInfo, nitroGasInfoAbi, "getPricesInWei", [], block, undefined, true, true);
540
538
  }
@@ -551,12 +549,6 @@ export function nitroPosterGasBound(transaction: Pick<LifecycleTransaction, "dat
551
549
  return { posterGas: 2n * ((feeBound + prices[5] - 1n) / prices[5]), feeBound };
552
550
  }
553
551
 
554
- async function readNitroScalar(context: LifecycleLimitContext, to: Address, abi: Abi, functionName: string): Promise<bigint> {
555
- const data = encodeFunctionData({ abi, functionName });
556
- const result = rpcHex(await lifecycleIndependentRpc(context.client, [{ to, data }, toHex(context.block.number)], context.block, true), functionName);
557
- if (result.length !== 66) throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", `${functionName} must return exactly one ABI word`);
558
- return decodeFunctionResult({ abi, functionName, data: result }) as bigint;
559
- }
560
552
 
561
553
  export type LifecycleLimitObservation = {
562
554
  limits: ResolvedLifecycleLimits;
@@ -591,7 +583,7 @@ async function resolveLifecycleLimitsFresh(context: LifecycleLimitContext, sourc
591
583
  };
592
584
  const headroomBps = configuration.headroomBps === undefined ? 1500 : configuration.headroomBps;
593
585
  if (!Number.isSafeInteger(headroomBps) || headroomBps < 0 || headroomBps > 10000) throw new LifecyclePlanningError("INVALID_LIMITS", "Headroom must be between zero and 10000 basis points");
594
- for (const limit of [context.block.gasLimit, configuration.chainGasLimit, configuration.rpcGasLimit, configuration.accountGasLimit, configuration.maxSimulationGas]) {
586
+ for (const limit of [context.block.gasLimit, configuration.chainGasLimit, configuration.rpcGasLimit, configuration.accountGasLimit]) {
595
587
  if (limit !== undefined && (typeof limit !== "bigint" || limit <= 0n || limit > maxUint64)) throw new LifecyclePlanningError("INVALID_LIMITS", "Gas caps must be positive exact uint64 bigint values");
596
588
  }
597
589
  if (configuration.maxCalldataBytes !== undefined && (!Number.isSafeInteger(configuration.maxCalldataBytes) || configuration.maxCalldataBytes <= 0)) throw new LifecyclePlanningError("INVALID_LIMITS", "Calldata cap must be a positive byte count");
@@ -604,37 +596,23 @@ async function resolveLifecycleLimitsFresh(context: LifecycleLimitContext, sourc
604
596
  throw new LifecyclePlanningError("LIMIT_CONTEXT_MISMATCH", "Supplied execution policy provenance differs from the canonical chain/account/core/block context");
605
597
  }
606
598
  const protocol = context.chainId === 4663n ? "nitro" : "evm";
607
- const maxSimulationGas = configuration.maxSimulationGas;
608
- let arbOSVersion: bigint | undefined;
609
- if (protocol === "nitro") {
610
- try {
611
- const rawVersion = await readNitroScalar(context, nitroArbSys, nitroArbSysAbi, "arbOSVersion");
612
- // ArbSys alone adds 55 to the logical Nitro ArbOS version.
613
- if (typeof rawVersion !== "bigint" || rawVersion < 105n) throw new Error("ArbOS version 50 or newer is required (ArbSys raw version >= 105)");
614
- arbOSVersion = rawVersion - 55n;
615
- } catch (failure) {
616
- if (failure instanceof Error && failure.name === "AbortError") throw failure;
617
- throw new LifecyclePlanningError("NITRO_LIMITS_UNAVAILABLE", `Pinned Nitro version could not be established: ${lifecycleFailureReason(failure)}`);
618
- }
619
- if (configuration.estimateDataFee !== undefined) throw new LifecyclePlanningError("INVALID_LIMITS", "Nitro poster fees are already included in gas; an external data-fee estimator would double-charge them");
620
- }
599
+ if (protocol === "nitro" && configuration.estimateDataFee !== undefined) throw new LifecyclePlanningError("INVALID_LIMITS", "Nitro poster fees are already included in gas; an external data-fee estimator would double-charge them");
621
600
  const envelopeCaps = [configuration.chainGasLimit, configuration.rpcGasLimit, configuration.accountGasLimit].filter((limit): limit is bigint => limit !== undefined);
622
601
  if (protocol === "evm") envelopeCaps.push(context.block.gasLimit);
623
602
  const transactionGasCeiling = envelopeCaps.length === 0 ? undefined : envelopeCaps.reduce((minimum, limit) => limit < minimum ? limit : minimum);
624
603
  const executionGasCeiling = protocol === "nitro" ? ROBINHOOD_NATIVE_TRANSACTION_GAS_LIMIT : transactionGasCeiling ?? context.block.gasLimit;
625
604
  const unknownExecutionConstraints = [
626
- ...(protocol === "evm" && configuration.chainGasLimit === undefined ? ["Independent chain per-transaction execution cap is unknown; proof is bounded by the pinned header and validated backend"] : []),
605
+ ...(protocol === "evm" && configuration.chainGasLimit === undefined ? ["Independent chain per-transaction cap is unknown; the budget is bounded by the pinned header"] : []),
627
606
  ...(configuration.rpcGasLimit === undefined ? ["RPC transaction gas-envelope cap is unknown"] : []),
628
607
  ...(configuration.accountGasLimit === undefined ? ["Current account/wallet transaction gas-envelope cap is unknown"] : []),
629
608
  ...(configuration.maxCalldataBytes === undefined ? ["Applicable chain/account/RPC calldata byte cap is unknown"] : []),
630
- ...(maxSimulationGas === undefined ? ["RPC aggregate simulation gas cap is unknown"] : []),
631
609
  ];
632
610
  assertCurrent();
633
611
  return {
634
612
  configuration, assertCurrent,
635
613
  limits: Object.freeze({
636
- protocol, executionGasCeiling, transactionGasCeiling, arbOSVersion, headroomBps,
637
- maxCalldataBytes: configuration.maxCalldataBytes, maxSimulationGas,
614
+ protocol, executionGasCeiling, transactionGasCeiling, headroomBps,
615
+ maxCalldataBytes: configuration.maxCalldataBytes,
638
616
  blockGasLimit: context.block.gasLimit, chainGasLimit: configuration.chainGasLimit,
639
617
  rpcGasLimit: configuration.rpcGasLimit, accountGasLimit: configuration.accountGasLimit,
640
618
  unknownExecutionConstraints: Object.freeze(unknownExecutionConstraints),
@@ -5,7 +5,7 @@ import { getKnownLifecycleDeployment, type LifecycleDeploymentPreset } from "./p
5
5
  import { lifecycleTokenPredictionAbi } from "./progress.js";
6
6
  import { launchLensAbi, observeLifecycleLens, type LifecycleLensSnapshot } from "./lens.js";
7
7
  import { hashLaunchIdentity, LifecycleFundingKind, type LaunchPlanV1 } from "./schema.js";
8
- import { attestLifecycleMulticall, lifecycleRpc, nitroArbSys, nitroArbSysAbi, nitroGasInfo, nitroGasInfoAbi, readLifecycleBlock, readLifecycleChainId, rpcHex, seedLifecycleRead } from "./rpc.js";
8
+ import { attestLifecycleMulticall, lifecycleRpc, nitroGasInfo, nitroGasInfoAbi, readLifecycleBlock, readLifecycleChainId, rpcHex, seedLifecycleRead } from "./rpc.js";
9
9
  import { LifecyclePlanningError, type LifecycleBlock, type LifecycleRpcClient } from "./types.js";
10
10
  import { UFG_LAUNCH_PROXY_ADDRESS } from "./ufg-config.js";
11
11
 
@@ -36,7 +36,6 @@ export async function readLifecyclePlanningBlock(client: LifecycleRpcClient, pla
36
36
  if (escrow !== undefined) add(asset, lifecycleErc20Abi, "allowance", [account, escrow]);
37
37
  }
38
38
  }
39
- add(nitroArbSys, nitroArbSysAbi, "arbOSVersion", []);
40
39
  add(nitroGasInfo, nitroGasInfoAbi, "getPricesInWei", []);
41
40
  await attestLifecycleMulticall(client, block);
42
41
  const calls = reads.map((read) => ({ target: read.to, allowFailure: true, callData: encodeFunctionData({ abi: read.abi, functionName: read.functionName, args: read.args }) }));
@@ -3,27 +3,64 @@ import { launchLifecycleAbi } from "./abi.js";
3
3
  import { LifecycleFundingKind, LifecycleMode, type LaunchPlanV1 } from "./schema.js";
4
4
  import { LifecyclePlanningError, type BuildLaunchTransactionsOptions, type LifecycleTransaction } from "./types.js";
5
5
  import { assertLifecycleTokenAllocation } from "./allocations.js";
6
- import { HMM_V2_PREPARE_GAS } from "./presets.js";
7
- import { isHmmProfile } from "./hmm.js";
8
6
  import { ufgLaunchProxyV1Abi } from "./ufg-abi.js";
9
- import { UFG_LAUNCH_PROXY_ADDRESS } from "./ufg-config.js";
7
+ import { UFG_LAUNCH_PROXY_ADDRESS, isUfgProfile } from "./ufg-config.js";
8
+ import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion } from "./markets.js";
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
- const OTHER_MARKET_PREPARE_GAS = 16_000_000n;
14
+ // Native9,723,187 Abyss3 (0x679d…e77), fork32-position10,900,786;
15
+ // fork V4 fixed/dynamic8-position8,804,466; UFG9,896,176; native HMM8-position14,676,638 (0x459c…5b7).
16
+ const preparationCosts = {
17
+ abyss: { market: 9_800_000n, position: 70_000n },
18
+ v4: { market: 9_000_000n, position: 40_000n },
19
+ ufg: { market: 10_000_000n, position: 70_000n },
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 },
25
+ } as const;
26
+ export const LIFECYCLE_CAPACITY_PADDING_BPS = 2500;
27
+ /** Full single-market observations include call overhead. Add conservative full
28
+ * calldata/hash processing (16 intrinsic gas per nonzero byte plus8 for copying). */
29
+ export function preparationCalldataGas(data: `0x${string}`): bigint { return 24n * BigInt((data.length - 2) / 2); }
13
30
 
14
31
  export function preparationGasEstimate(plan: LaunchPlanV1, first: number, count: number): bigint {
15
32
  let gas = 0n;
16
- for (let index = first; index < first + count; index += 1) gas += isHmmProfile(plan.markets[index]!.profileId) ? HMM_V2_PREPARE_GAS : OTHER_MARKET_PREPARE_GAS;
33
+ for (let index = first; index < first + count; index += 1) {
34
+ const market = plan.markets[index]!;
35
+ const positions = market.configVersion === 1 ? decodeAbyssLifecycleMarketConfig(market.config).positions.length
36
+ : isPoolBoundV4ConfigVersion(market.configVersion) ? decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion).positions.length
37
+ : decodeV4LifecycleMarketConfig(market.config).positions.length;
38
+ const cost = market.configVersion === 1 ? preparationCosts.abyss : isHmmProfile(market.profileId) ? preparationCosts.hmm
39
+ : isUfgProfile(market.profileId) ? preparationCosts.ufg : isRaetoProfile(market.profileId) ? preparationCosts.raeto
40
+ : isAutoCompoundProfile(market.profileId) ? preparationCosts.autoCompound : preparationCosts.v4;
41
+ gas += cost.market + cost.position * BigInt(positions - 1);
42
+ }
17
43
  return gas;
18
44
  }
19
45
 
20
- /** Contiguous cost packing preserves committed market order and recovery progress. */
21
- export function packPreparationBatches(plan: LaunchPlanV1, prepared: number, computeCap: bigint): number[] {
46
+ /** Contiguous deterministic packing with compute headroom. The caller reserves
47
+ * poster allocation separately from computeCap; neither resizes transaction gas. */
48
+ export function packPreparationBatches(plan: LaunchPlanV1, prepared: number, computeCap: bigint, headroomBps = 1500): number[] {
49
+ const data = buildLifecycleCommand(plan, { kind: "prepare", first: prepared, count: 1 }).data;
50
+ return packLifecyclePreparationBatches(plan, prepared, computeCap, headroomBps, preparationCalldataGas(data));
51
+ }
52
+
53
+ /** Internal path reuses the command already encoded for poster allocation. */
54
+ export function packLifecyclePreparationBatches(plan: LaunchPlanV1, prepared: number, computeCap: bigint, headroomBps: number, calldataGas: bigint): number[] {
55
+ if (!Number.isSafeInteger(headroomBps) || headroomBps < 0 || headroomBps > 10000) throw new LifecyclePlanningError("INVALID_LIMITS", "Headroom must be between zero and10000 basis points");
56
+ const padding = BigInt(10000 + Math.max(LIFECYCLE_CAPACITY_PADDING_BPS, headroomBps));
57
+ const fits = (gas: bigint) => ((gas + calldataGas) * padding + 9999n) / 10000n <= computeCap;
22
58
  const batches = prepared > 0 ? [prepared] : [];
23
59
  let count = 0, gas = 0n;
24
60
  for (let index = prepared; index < plan.markets.length; index += 1) {
25
61
  const estimate = preparationGasEstimate(plan, index, 1);
26
- if (count > 0 && gas + estimate > computeCap) { batches.push(count); count = 0; gas = 0n; }
62
+ if (!fits(estimate)) throw new LifecyclePlanningError("PREPARATION_GAS_LIMIT", "A market exceeds the preparation gas budget. Reduce its positions before launching.");
63
+ if (count > 0 && !fits(gas + estimate)) { batches.push(count); count = 0; gas = 0n; }
27
64
  count += 1;
28
65
  gas += estimate;
29
66
  }
@@ -53,7 +90,7 @@ export function buildLifecycleCommand(plan: LaunchPlanV1, command: LifecycleComm
53
90
 
54
91
  /** Pure unsigned launch commands. Every command carries the complete commitment;
55
92
  * preparation splits only empty markets, never positions, buys or public opening.
56
- * Allowance discovery, recovery and execution proof belong to the planner. */
93
+ * Allowance discovery, recovery and gas-budget checks belong to the planner. */
57
94
  export function buildLifecycleCommands({ plan, mode, preparationBatches }: BuildLaunchTransactionsOptions, ufg?: LifecycleUfgRoute): LifecycleTransaction[] {
58
95
  if (mode !== "atomic" && mode !== "staged") throw new LifecyclePlanningError("EXPLICIT_MODE_REQUIRED", "Choose atomic or staged explicitly; staged is never silently substituted");
59
96
  if (plan.chainId <= 0n || plan.chainId > BigInt(Number.MAX_SAFE_INTEGER)) throw new LifecyclePlanningError("CHAIN_MISMATCH", "Chain ID must be a positive exact JavaScript integer");