@black-market/sdk 0.10.0 → 0.11.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 (38) hide show
  1. package/README.md +4 -2
  2. package/dist/lifecycle/calibration.d.ts.map +1 -1
  3. package/dist/lifecycle/calibration.js +2 -0
  4. package/dist/lifecycle/calibration.js.map +1 -1
  5. package/dist/lifecycle/planner.d.ts.map +1 -1
  6. package/dist/lifecycle/planner.js +44 -35
  7. package/dist/lifecycle/planner.js.map +1 -1
  8. package/dist/lifecycle/presets.d.ts +5 -0
  9. package/dist/lifecycle/presets.d.ts.map +1 -1
  10. package/dist/lifecycle/presets.js +15 -1
  11. package/dist/lifecycle/presets.js.map +1 -1
  12. package/dist/lifecycle/rpc.js +3 -2
  13. package/dist/lifecycle/rpc.js.map +1 -1
  14. package/dist/lifecycle/simulation.d.ts +1 -1
  15. package/dist/lifecycle/simulation.d.ts.map +1 -1
  16. package/dist/lifecycle/simulation.js +115 -174
  17. package/dist/lifecycle/simulation.js.map +1 -1
  18. package/dist/lifecycle/transactions.d.ts +6 -0
  19. package/dist/lifecycle/transactions.d.ts.map +1 -1
  20. package/dist/lifecycle/transactions.js +39 -0
  21. package/dist/lifecycle/transactions.js.map +1 -1
  22. package/dist/lifecycle/types.d.ts +6 -3
  23. package/dist/lifecycle/types.d.ts.map +1 -1
  24. package/dist/lifecycle/types.js.map +1 -1
  25. package/docs/launch-lifecycle.md +77 -32
  26. package/examples/.env.example +4 -0
  27. package/examples/README.md +21 -2
  28. package/examples/launch-example-support.ts +1 -0
  29. package/examples/launch-example.ts +1 -0
  30. package/examples/launch.ts +1 -0
  31. package/package.json +1 -1
  32. package/src/lifecycle/calibration.ts +1 -0
  33. package/src/lifecycle/planner.ts +41 -32
  34. package/src/lifecycle/presets.ts +16 -2
  35. package/src/lifecycle/rpc.ts +3 -2
  36. package/src/lifecycle/simulation.ts +111 -165
  37. package/src/lifecycle/transactions.ts +38 -1
  38. package/src/lifecycle/types.ts +9 -4
@@ -7,6 +7,50 @@ The canonical authorities are the registry/certification/adapter/config contract
7
7
  in the protocol repository. Plan identity is `LaunchPlanV1`. Pool-bound V4 uses **config 6**,
8
8
  canonical Abyss uses **config 1**, and generic current shared-V4 support uses **config 4**.
9
9
 
10
+ ## 0.11.0 release: gas sizing, packing and prefix proof
11
+
12
+ New atomic/activation envelopes containing HMM V2 markets add one forwarding target:
13
+ `R = 4_250_000 + 500_000 * (hmmMarkets - 1)` gas (zero for non-HMM plans).
14
+ For measured gross gas `G` (`maxUsedGas`, falling back to `gasUsed`), compute is
15
+ `min(max(G + R, ceil(G * (10000 + headroomBps) / 10000)), computeCap)`.
16
+ Begin/preparation/approval/cancel commands do not receive this reserve. Nitro poster
17
+ gas is budgeted separately inside the same complete gas envelope. Optional padding
18
+ and the reserve are targets, not refusal thresholds; exact validated replay proves
19
+ the clamped envelope. There is no bisection or extra ordinary-path request.
20
+
21
+ Staged planning greedily packs contiguous markets against the unpadded compute cap:
22
+ HMM V2 costs 14.5M per market; other profiles cost a conservative 16M. At 32M,
23
+ 1/2/3/5 HMM markets produce `[1]`/`[2]`/`[2,1]`/`[2,2,1]`.
24
+ Only a packed measurement failure consuming at least 95% of its given gas splits
25
+ that batch in half, regardless of its error selector. Low-gas semantic failures
26
+ remain failures. `preparationBatchSize` is removed; inspect each preparation's
27
+ `marketStart`/`marketCount`. The pure unsigned compiler still accepts explicit
28
+ `preparationBatches`.
29
+
30
+ Robinhood chain 4663 defaults `limits.maxSimulationGas` to **50_000_000n** aggregate
31
+ gas **used**, not the sum of individual legal transaction envelopes. Override it
32
+ for a provider with a different budget. The SDK measures and validates the longest
33
+ estimated prefix that fits: begin 9.5M, preparations as above, activation 6M per
34
+ market plus `R`, and small approvals/cancellation. A measurement failure after
35
+ cumulative use reaches 95% of that cap defers the failed call and suffix.
36
+ If even the immediate estimate cannot fit that budget, refusal first identifies
37
+ any estimated per-transaction compute overflow (gas displayed in millions to one
38
+ decimal), rather than suggesting that a larger RPC budget solves it. Estimates
39
+ alone do not reject an envelope that can still be measured and exactly proved.
40
+
41
+ `simulation.admitted`, `executionProof: "proved"` and `protocolFit: "proved"` cover
42
+ only the successful proven prefix. **They do not promise eventual activation.**
43
+ `simulation.deferredTransactionIds` explicitly lists unproved remaining steps
44
+ (empty for full coverage); these commands carry no `gas`, `gasPrice`, `estimate`
45
+ or `admission`. Call `buildNextTransaction` before each signature: canonical
46
+ progress advances the proof window and gives the immediate next transaction exact
47
+ gas. Reviewed envelopes receive only a direct exact replay of that immediate step,
48
+ without new measurement, poster/price recommendations or suffix proof.
49
+
50
+ `buySlippageBps` requires actual activation outputs. If activation is deferred,
51
+ calibration refuses clearly; use a higher-budget native provider or preserve
52
+ already committed minima by omitting recalibration. Outputs are never fabricated.
53
+
10
54
  ## 0.9.1 release: Abyss 0.01% fee tier
11
55
 
12
56
  `ABYSS_FEE_TIERS` now includes `{ feePips: 100, tickSpacing: 1, label: "0.01%" }`.
@@ -342,15 +386,13 @@ progress, funding/allowances and deployment/profile authority. It matches
342
386
  Preparation batching remains SDK-derived, not caller-selected. Malformed,
343
387
  wrong-step or changed envelopes refuse without returning executable calldata.
344
388
 
345
- Fresh measurement and complete native validated replay use the held price.
346
- The immediate replay uses the exact held gas; later unfinished steps receive
347
- fresh gas bounds and remain in the same stateful sequence. Current compute,
348
- poster, minimum headroom, tightening ceilings and full-sequence actual-balance
349
- affordability still apply. Exact replay also checks gross-gas headroom without
350
- counting Nitro's temporary compute hold. Neither natural child base fees nor
351
- payer balances are overridden. A higher advisory quote alone is not a refusal;
352
- an invalid current fee, insufficient gas or failed proof never silently raises
353
- the held envelope.
389
+ The SDK directly validates only the immediate transaction at its exact held
390
+ gas/price. It does not repeat measurement, quote a new gas price/poster budget or
391
+ simulate future steps. Current protocol/transaction limits, native metering,
392
+ postconditions and immediate actual-balance affordability still apply. Existing
393
+ padding may be consumed; neither a new padding target nor a higher advisory quote
394
+ alone invalidates a held envelope. Natural child base fees and payer balances are
395
+ never overridden, and failed replay never raises held gas or switches backend.
354
396
 
355
397
  Approvals/resets, atomic launches, every staged next step and cancellation use
356
398
  this same contract. Cancellation retains its adapter-independent recovery policy.
@@ -742,13 +784,13 @@ Validation uses `validateV4LifecycleMarket`; the collector factory getter is
742
784
  | --- | --- |
743
785
  | `readLifecycleProfiles({client, orchestrator, profileIds?, offset?, limit?})` | Bounded registry discovery, schema/topology dispatch, reviewed graph/terms certification and current registry admission. Default offset/limit is 0/100; explicit IDs can select a reviewed deployment's identities. |
744
786
  | `predictLifecycleToken({client, plan})` | Exact prediction from the explicit core. |
745
- | `prepareAndPlanLifecycleLaunch({client, account, plan, mode, allowTopologyGaps?, allowMissingPermanentTail?, limits?, fork?, receipts?, confirmations?, signal?, onProgress?})` | Mines config6/V2 salts offchain, verifies per-market launch topology and exact final deployment bindings, and returns complete selected-mode admission under one SDK-owned fresh post-mining snapshot. No source-chain deployment or broadcast. |
787
+ | `prepareAndPlanLifecycleLaunch({client, account, plan, mode, buySlippageBps?, allowTopologyGaps?, allowMissingPermanentTail?, limits?, fork?, receipts?, confirmations?, signal?, onProgress?})` | Mines config6/V2 salts offchain, verifies per-market topology and exact final deployment bindings, and proves the selected-mode prefix under one fresh post-mining snapshot. Reports deferred steps; buy calibration requires activation coverage. No source-chain deployment or broadcast. |
746
788
  | `readPoolBoundHookDeployment({client, plan, marketIndex})` | Verifies typed factory, code chunks, constructor, normalized economics, initcode, salt, CREATE2 prediction and recorded provenance of existing code. Works with draft salts; permission bits are additionally required for executable predeployment. |
747
789
  | `buildPoolBoundHookDeploymentTransaction({client, plan, marketIndex})` | Optional permissionless typed `deploy(parameters,salt)` calldata. Does not create a token, register/initialize a pool, bind a collector or activate a launch. |
748
790
  | `planLaunch({client, account, plan, mode, allowTopologyGaps?, allowMissingPermanentTail?, limits?, fork?, receipts?, confirmations?, signal?})` | Independently fresh per-market topology/domain/funding/profile/progress/execution admission of the already-finalized commitment, simulating only the explicitly selected mode. |
749
791
  | `simulateLaunchPlan({client, planned, limits?, fork?, receipts?, confirmations?})` | Rereads canonical state and proves remaining work. Preserves receipt evidence, economics and selected mode. |
750
792
  | `readLaunchProgress({client, planned, receipts?, confirmations?})` | Confirmation-bound canonical state, latest head, receipt/replacement/reorg status, account-nonce safety and actual prepared market/live state. |
751
- | `buildNextTransaction({client, planned, reviewedTransaction?, submissionClient?, limits?, fork?, receipts?, confirmations?, action?})` | Rereads progress, constraints and complete stateful proof before returning one admitted transaction. Optional untrusted `reviewedTransaction` must match the current canonical next step and freshly prove its exact gas/price. Optional `submissionClient` adds read-only immediate exact-envelope preflight. `action: "cancel"` uses the stored commitment without requiring fresh adapter/profile admission. Terminal launches return `undefined`. |
793
+ | `buildNextTransaction({client, planned, reviewedTransaction?, submissionClient?, limits?, fork?, receipts?, confirmations?, action?})` | Rereads canonical progress and constraints, proves the immediate next transaction plus the fitting prefix, and returns one admitted envelope. With `reviewedTransaction`, directly replays only that matching immediate step at its exact held gas/price. Optional `submissionClient` adds read-only exact-envelope preflight. `action: "cancel"` retains adapter-independent recovery. Terminal launches return `undefined`. |
752
794
 
753
795
  Clients may be viem public clients or raw `request({method,params})` RPC clients. Transport, wallet, signing and durable storage are application-owned. `PlannedLaunch` contains bigint values and live limit callbacks: persist `serializeLaunchPlan(plan)`, explicit execution mode, confirmation policy and receipt references, not the entire object.
754
796
 
@@ -879,17 +921,17 @@ Mining is cancellable/nonblocking; `abort()` raises `AbortError` and does not ca
879
921
  Every planning call requires `mode: "atomic" | "staged"`. Only that requested mode receives economic simulation; staged does not compute or expose an unused atomic attempt.
880
922
 
881
923
  - Atomic: after funding prerequisites, token creation, empty infrastructure, all mint/lock/buys/public activation are one transaction.
882
- - Staged: prerequisites, `beginLaunch(Staged)`, contiguous empty `prepareMarkets` batches, then one `activateLaunch`. Only a stateful preparation refusal classified as capacity with a compute or gas-envelope constraint can partition into smaller empty batches. Opaque `DeploymentFailed`, unclassified/source/provider-clamp errors, byte-cap refusals and near-cap consumption do not authorize that search. Core/validator/registry/hook invariant errors stop planning.
924
+ - Staged: prerequisites, `beginLaunch(Staged)`, greedily packed contiguous empty `prepareMarkets` batches, then one `activateLaunch`. A packed measurement failure using at least 95% of its given gas splits only that batch; low-gas semantic errors, byte caps and unlocated provider failures do not justify a split. Proof may cover only a prefix, with the unproved suffix explicitly deferred.
883
925
  - Activation cannot split. If mint/lock/all buys/opening cannot fit, staged is also refused. No position/buy/minimum is omitted to manufacture success.
884
926
 
885
927
  No automatic atomic-to-staged fallback exists. Obtain separate consent to review the same commitment as staged. Both venues use token transfer restrictions plus activation-time canonical opening-state checks, not a pool gate. Manipulated opening price or preactivation liquidity fails verification; the token stays inactive and the launch remains Ready/cancellable. Failed activation rolls back every mint, seal, buy and opening together.
886
928
 
887
929
  ## Execution constraints and stateful simulation
888
930
 
889
- Optional `chainGasLimit`, `rpcGasLimit`, `accountGasLimit`, `maxCalldataBytes` and
890
- `maxSimulationGas` tighten admission when supplied. Missing policy remains explicit in
891
- `limits.unknownExecutionConstraints`/`unknownConstraints`; it does not reject an otherwise
892
- proved exact sequence or invent a provider/account cap. No policy service is required.
931
+ Optional `chainGasLimit`, `rpcGasLimit`, `accountGasLimit` and `maxCalldataBytes`
932
+ tighten admission. `maxSimulationGas` sets the aggregate used-gas proof-window
933
+ budget (default 50M on 4663, unknown on other chains). Missing policy stays explicit
934
+ in `limits.unknownExecutionConstraints`/`unknownConstraints`; no policy service is required.
893
935
  A `LifecycleLimitSource` callback receives the canonical chain/account/core/block context.
894
936
  Any supplied provenance must match it; stale/mismatched/malformed facts or a failed supplied
895
937
  resolver reject rather than disappearing into optional uncertainty. Gas caps are positive
@@ -897,7 +939,9 @@ exact uint64 `bigint`; calldata is a positive safe integer byte count.
897
939
  Reviewed gas envelopes are positive uint64 too; zero-metering observations cannot admit
898
940
  a zero-gas transaction. Headroom calculations retain wide exact bigint intermediates.
899
941
 
900
- Headroom defaults to 1,500 bps, bounded 0–10,000. Generic EVM admission uses the pinned
942
+ Headroom defaults to 1,500 bps, bounded 0–10,000; it clamps to current legal ceilings
943
+ instead of rejecting a fitting measured requirement. HMM V2 adds the forwarding
944
+ target documented above once on atomic/activation. Generic EVM admission uses the pinned
901
945
  bounded header and supplied total-envelope restrictions plus actual validated RPC proof.
902
946
  It reports an absent independent per-transaction cutoff honestly, without importing a
903
947
  universal Ethereum cutoff. `executionGasCeiling` and `transactionGasCeiling` are equal on
@@ -910,8 +954,8 @@ carries that successfully replayed ceiling, never a smaller unproved envelope.
910
954
  Replay source, HTTP and malformed-response failures remain provisional with
911
955
  `executionProof: "unavailable"` and do not retry economic execution. `AbortError`
912
956
  propagates unchanged, including cancellation during supplied policy and Nitro getter
913
- reads. A recognized execution refusal may discover at most one genuinely larger
914
- validated envelope within the same legal compute/total limits. Without returned
957
+ reads. Only explicit genuine native out-of-gas evidence may discover at most one
958
+ larger validated envelope within the same legal compute/total limits. Without returned
915
959
  failing-call evidence, `failedTransactionId` stays absent; an opaque RPC error does
916
960
  not invent a failing transaction or authorize preparation partitioning.
917
961
  Returned failures retain the actual exact-replay prefix and its first failing
@@ -949,10 +993,10 @@ The buffered compute-discovery envelope plus buffered poster budget is carried i
949
993
  exact validated replay; an approximate poster quote is never subtracted from total
950
994
  gas use to certify exact compute fit.
951
995
  Measurement waits for successful capability and candidate-specific poster evidence,
952
- then uses the legal complete compute-plus-poster envelope. Near-cap gas consumption
953
- does not diagnose OOG, constructor failure or collision. Unsupported/transport
954
- evidence remains unavailable, and only a recognized execution-envelope refusal
955
- can trigger bounded envelope discovery.
996
+ then uses the legal complete compute-plus-poster envelope. A packed preparation
997
+ failure consuming at least 95% may invalidate its cost estimate without claiming
998
+ its selector diagnoses OOG. Unsupported/transport evidence remains unavailable,
999
+ except an observed aggregate-budget exhaustion can preserve a successful prefix.
956
1000
 
957
1001
  A separate pinned validated simulation probes all three ArbOS/version/getter values
958
1002
  against the canonical observations. Only that isolated capability probe temporarily
@@ -975,13 +1019,13 @@ does not manufacture an all-in estimate. All observations and limits refresh for
975
1019
  For initial construction, the exact legacy gas-price envelope is the greater of
976
1020
  the fresh RPC recommendation and twice the pinned base fee. This avoids
977
1021
  underpriced submission when `eth_gasPrice` lags the block base fee. Reviewed
978
- envelopes retain their exact gas/price only after fresh full-sequence measurement,
979
- native validated replay, current minimum headroom and affordability checks.
1022
+ envelopes retain exact gas/price through direct immediate native validated replay
1023
+ and immediate affordability, without repeating measurement or applying new padding.
980
1024
  The held price need not equal a new recommendation or twice the current base fee;
981
1025
  natural child fees and the actual submission estimate must accept it. Replay,
982
1026
  affordability and submission use that same price, without a base-fee override.
983
- A known aggregate budget whose legal minimum already exceeds its ceiling refuses
984
- before capability probes or poster work; equality is allowed. A definitive returned
1027
+ An aggregate budget selects the longest fitting estimated prefix; only when even
1028
+ the immediate estimate does not fit is no proof possible. A definitive returned
985
1029
  failure from the final exact replay skips a later payer-balance lookup, while
986
1030
  retaining fee/estimate validation and final canonical checks. Successful replay
987
1031
  still requires current actual-balance affordability.
@@ -1013,14 +1057,15 @@ Simulation and `next.admission` carry flat, independent proof outcomes:
1013
1057
 
1014
1058
  | Field | Values | Scope |
1015
1059
  | --- | --- | --- |
1016
- | `executionProof` | `"proved"`, `"failed"`, `"unavailable"` | Complete stateful exact-envelope execution and postconditions, never permissive measurement alone. |
1060
+ | `executionProof` | `"proved"`, `"failed"`, `"unavailable"` | Stateful exact-envelope execution/postconditions for the proven prefix (or the immediate reviewed step), never measurement alone or an unproved suffix. |
1017
1061
  | `protocolFit` | `"proved"`, `"failed"`, `"unknown"` | Applicable proven protocol and known tightening restrictions. Missing optional policy remains separately disclosed. |
1018
1062
  | `transportPreflight` | `"not-requested"`, `"passed"`, `"failed"` | Immediate exact next-transaction acceptance estimate on the supplied submission RPC. |
1019
1063
 
1020
- `admitted` means the current exact sequence is proved to fit and affordable; it is
1021
- not guaranteed wallet submission or future execution. Admission reuses simulation
1022
- `blockNumber`, `blockHash`, `account`, `chainId` and `limits`. Plans never preflight
1023
- future dependent transactions against state that does not exist.
1064
+ `admitted` means the proven prefix fits and is affordable, not that deferred
1065
+ transactions have executed or that eventual activation is guaranteed. Always
1066
+ inspect `simulation.deferredTransactionIds`. Admission reuses `blockNumber`,
1067
+ `blockHash`, `account`, `chainId` and `limits`; signing uses only fresh build-next
1068
+ admission. Plans never preflight future steps independently against nonexistent state.
1024
1069
 
1025
1070
  ```ts
1026
1071
  const planned = await planLaunch({ client, account: plan.creator, plan, mode: "atomic" });
@@ -35,3 +35,7 @@ NFT_BASE_URI=
35
35
  # EXAMPLE_ACCOUNT_GAS_CAP=
36
36
  # EXAMPLE_MAX_CALLDATA_BYTES=
37
37
  # EXAMPLE_HEADROOM_BPS=1500
38
+ #
39
+ # Robinhood aggregate simulation gas-used budget defaults to 50M. Override only for
40
+ # a provider supporting that budget; buy-output calibration needs activation coverage.
41
+ # EXAMPLE_SIMULATION_GAS_CAP=100000000
@@ -1,8 +1,8 @@
1
1
  # Examples
2
2
 
3
- The launch examples use the public `@black-market/sdk` 0.9.0 package and viem.
3
+ The launch examples use the public `@black-market/sdk` 0.11.0 package and viem.
4
4
  Build this checkout before running its examples; in a consumer project install
5
- `@black-market/sdk@0.8.0` or later for the `positionGaps` and recovery APIs.
5
+ `@black-market/sdk@0.11.0` for current gas packing and proof-coverage semantics.
6
6
  The default chain-4663 launch cases retain
7
7
  the October8 mined fixed pool-bound V4 config6/V2 and canonical Abyss config1 deployment.
8
8
  The SDK additionally knows the October11 zero-author-royalty Dynamic Fee Hook;
@@ -73,6 +73,18 @@ generic EVM, not as a Nitro metering substitute. Unsigned review reports
73
73
  RPC to build-next. See the
74
74
  [lifecycle guide](../docs/launch-lifecycle.md#execution-constraints-and-stateful-simulation).
75
75
 
76
+ Robinhood defaults to a 50M aggregate simulation gas-used budget. Inspect
77
+ `simulation.deferredTransactionIds`: admitted/proved means the proven prefix,
78
+ not every future transaction or eventual activation. Deferred commands have no
79
+ gas/price/estimate/admission; obtain each fresh immediate envelope through
80
+ `buildNextTransaction` after canonical progress advances. Reviewed envelopes are
81
+ replayed directly, without measuring/replaying the suffix again. The saved-plan
82
+ CLI prints proof coverage and never signs. Override `limits.maxSimulationGas` only
83
+ for a provider that supports the selected budget. Examples requiring actual
84
+ activation outputs for `buySlippageBps` or economic review refuse if activation is
85
+ deferred; use a suitably higher-budget provider rather than inventing buy outputs.
86
+
87
+
76
88
  For a real new graph, create the saved plan using `readLifecycleProfiles` with its
77
89
  explicit chain/core and admitted pool-bound config6 profile. Supply that graph's RPC
78
90
  to `launch.ts`; do not substitute the historical known preset. Applications may use
@@ -381,6 +393,13 @@ to tighten the complete transaction envelope: `EXAMPLE_CHAIN_GAS_CAP`,
381
393
  restrictions, not invented provider/account assurances or admission bypasses.
382
394
  Poster allocation is already inside Nitro gas/affordability and is not charged twice.
383
395
 
396
+ Robinhood's separate aggregate simulation gas-used budget defaults to **50M**.
397
+ `EXAMPLE_SIMULATION_GAS_CAP` overrides that budget (positive decimal gas) for
398
+ providers supporting more or less. It does not change per-transaction compute
399
+ capacity. Multi-step examples requiring full activation outputs for buy protection
400
+ and economic review need a provider/budget covering activation, or refuse before
401
+ signing; do not raise the setting beyond the provider's actual supported budget.
402
+
384
403
  ### Evidence and failure recovery
385
404
 
386
405
  Each dry run or broadcast automatically creates `launch-results/<timestamp-case-random>/`;
@@ -540,6 +540,7 @@ export async function readExampleConfiguration(sdk, redactor, env = process.env)
540
540
  chainTransactionGasLimit: gasCap("EXAMPLE_CHAIN_GAS_CAP"),
541
541
  rpcTransactionGasLimit: gasCap("EXAMPLE_RPC_GAS_CAP"),
542
542
  accountTransactionGasLimit: gasCap("EXAMPLE_ACCOUNT_GAS_CAP"),
543
+ rpcTotalSimulationGasLimit: gasCap("EXAMPLE_SIMULATION_GAS_CAP"),
543
544
  maxCalldataBytes: integer("EXAMPLE_MAX_CALLDATA_BYTES", undefined, 1, Number.MAX_SAFE_INTEGER),
544
545
  headroomBps: integer("EXAMPLE_HEADROOM_BPS", 1500, 0, 10000),
545
546
  provenance: { scope: "user-ceilings", note: "Optional local tightening restrictions, not verified provider or account limits" },
@@ -477,6 +477,7 @@ function limitSource(fixture, artifacts) {
477
477
  chainGasLimit: policy.chainTransactionGasLimit === undefined ? undefined : BigInt(policy.chainTransactionGasLimit),
478
478
  rpcGasLimit: policy.rpcTransactionGasLimit === undefined ? undefined : BigInt(policy.rpcTransactionGasLimit),
479
479
  accountGasLimit: policy.accountTransactionGasLimit === undefined ? undefined : BigInt(policy.accountTransactionGasLimit),
480
+ maxSimulationGas: policy.rpcTotalSimulationGasLimit === undefined ? undefined : BigInt(policy.rpcTotalSimulationGasLimit),
480
481
  maxCalldataBytes: policy.maxCalldataBytes, headroomBps: policy.headroomBps,
481
482
  };
482
483
  artifacts.event("execution-limits", { limits, provenance: policy.provenance });
@@ -30,6 +30,7 @@ const options = { client, account: plan.creator, plan, mode, limits, fork, recei
30
30
  const planned = args.includes("--mine")
31
31
  ? await prepareAndPlanLifecycleLaunch({ ...options, onProgress: ({ marketIndex, attempts }) => console.error("salt mining", marketIndex, attempts.toString()) })
32
32
  : await planLaunch(options);
33
+ console.error("Proof coverage:", planned.simulation.steps.length, "steps; deferred:", planned.simulation.deferredTransactionIds);
33
34
  const progress = await readLaunchProgress({ client, planned, receipts, confirmations });
34
35
  const next = planned.simulation.admitted ? await buildNextTransaction({ client, planned, limits, fork, receipts, confirmations }) : undefined;
35
36
  console.log(JSON.stringify({ plan: JSON.parse(serializeLaunchPlan(planned.plan)), mode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@black-market/sdk",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "TypeScript SDK for Abyss token launches, swaps, and lending.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -43,6 +43,7 @@ export function validateBuySlippageBps(bps: number): void {
43
43
  export function protectOrderedBuyMinimums(planned: PlannedLaunch, bps: number): LaunchPlanV1 {
44
44
  validateBuySlippageBps(bps);
45
45
  const { simulation, plan } = planned;
46
+ if (simulation.deferredTransactionIds.length !== 0) throw new LifecyclePlanningError("LAUNCH_DIAGNOSTIC_FAILED", "Buy slippage calibration requires actual activation outputs beyond the proven RPC prefix; use a higher aggregate simulation cap/provider or preserve already committed minima without buySlippageBps", simulation);
46
47
  if (simulation.confidence !== "stateful" || simulation.backend === "unavailable" || simulation.steps.length !== planned.transactions.length || simulation.steps.some((step, index) => !step.success || step.transactionId !== planned.transactions[index]?.id)) throw new LifecyclePlanningError("LAUNCH_DIAGNOSTIC_FAILED", simulation.reason ?? "The complete diagnostic launch sequence did not execute successfully", simulation);
47
48
  const activation = planned.transactions.find((transaction) => transaction.kind === "atomic" || transaction.kind === "activate");
48
49
  const step = simulation.steps.find((candidate) => candidate.transactionId === activation?.id);
@@ -10,7 +10,7 @@ import { prepareLaunchSimulation, simulateLaunchTransactions, type NextTransacti
10
10
  import { LifecyclePlanningError, type BuildNextTransactionOptions, type CanonicalLaunchProgress, type LifecycleBlock, type LifecycleConstructionProfile, type LifecycleFundingPrerequisite, type LifecyclePoolBoundHookDeployment, type LifecycleProfile, type LifecycleRpcClient, type LifecycleSimulation, type LifecycleTransaction, type PlannedLaunch, type PlanLaunchOptions, type PrepareAndPlanLifecycleLaunchOptions, type SimulateLaunchPlanOptions } from "./types.js";
11
11
  import { deriveKnownPoolBoundHookDeployment, protectOrderedBuyMinimums, validateBuySlippageBps } from "./calibration.js";
12
12
  import { getKnownLifecycleDeployment, getKnownLifecycleProfile } from "./presets.js";
13
- import { buildLaunchTransactions } from "./transactions.js";
13
+ import { buildLaunchTransactions, packPreparationBatches } from "./transactions.js";
14
14
  import { assertLifecycleMarketAllocation, assertLifecycleSupplyAllocation } from "./allocations.js";
15
15
 
16
16
  type ValidatedPlanShape = {
@@ -132,11 +132,9 @@ async function readPlanMetadata(client: LifecycleRpcClient, plan: LaunchPlanV1,
132
132
  return { profiles, hookDeployments, reasons: live.filter((profile) => !profile.admitted).map((profile) => profile.reason ?? "Unlisted lifecycle profile is unavailable") };
133
133
  }
134
134
 
135
- function orderedTransactions(plan: LaunchPlanV1, mode: "atomic" | "staged", progress: CanonicalLaunchProgress, approvals: readonly LifecycleTransaction[], batchSize: number): LifecycleTransaction[] {
135
+ function orderedTransactions(plan: LaunchPlanV1, mode: "atomic" | "staged", progress: CanonicalLaunchProgress, approvals: readonly LifecycleTransaction[], batches: readonly number[]): LifecycleTransaction[] {
136
136
  if (progress.canonical.phase === LifecyclePhase.Active || progress.canonical.phase === LifecyclePhase.Cancelled) return [];
137
137
  const prepared = progress.canonical.preparedMarkets;
138
- const batches: number[] = prepared > 0 ? [prepared] : [];
139
- for (let first = prepared; first < plan.markets.length; first += batchSize) batches.push(Math.min(batchSize, plan.markets.length - first));
140
138
  const commands = buildLaunchTransactions({ plan, mode, preparationBatches: mode === "staged" ? batches : undefined }).filter((transaction) =>
141
139
  (transaction.kind !== "begin" && transaction.kind !== "atomic" || progress.canonical.phase === LifecyclePhase.None) &&
142
140
  (transaction.kind !== "prepare" || (transaction.marketStart ?? 0) >= prepared));
@@ -147,7 +145,8 @@ function orderedTransactions(plan: LaunchPlanV1, mode: "atomic" | "staged", prog
147
145
  function attachEstimates(transactions: readonly LifecycleTransaction[], simulation: LifecycleSimulation): LifecycleTransaction[] {
148
146
  return transactions.map((transaction) => {
149
147
  const estimate = simulation.steps.find((step) => step.transactionId === transaction.id)?.estimate;
150
- return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate, admission: { admitted: simulation.admitted, confidence: simulation.confidence, executionProof: simulation.executionProof, protocolFit: simulation.protocolFit, transportPreflight: simulation.transportPreflight, blockNumber: simulation.blockNumber, blockHash: simulation.blockHash, account: simulation.account, chainId: simulation.chainId, reason: simulation.reason, limits: simulation.limits } };
148
+ return { ...transaction, calldataBytes: (transaction.data.length - 2) / 2, gas: estimate?.gasLimit, gasPrice: estimate?.gasPrice, estimate,
149
+ admission: estimate === undefined ? undefined : { admitted: simulation.admitted, confidence: simulation.confidence, executionProof: simulation.executionProof, protocolFit: simulation.protocolFit, transportPreflight: simulation.transportPreflight, blockNumber: simulation.blockNumber, blockHash: simulation.blockHash, account: simulation.account, chainId: simulation.chainId, reason: simulation.reason, limits: simulation.limits } };
151
150
  });
152
151
  }
153
152
  type ReviewedNextTransaction = Pick<LifecycleTransaction, "id" | "kind" | "chainId" | "from" | "to" | "data" | "value"> & NextTransactionEnvelope;
@@ -364,8 +363,8 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
364
363
  }),
365
364
  ]));
366
365
  assertPlanningActive(options.signal);
367
- const placeholder: LifecycleSimulation = { backend: "unavailable", confidence: "provisional", admitted: false, executionProof: "unavailable", protocolFit: "unknown", transportPreflight: "not-requested", blockNumber: block.number, blockHash: block.hash, account, chainId: plan.chainId, limits, steps: [] };
368
- let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, simulation: placeholder, preparationBatchSize: plan.markets.length, limits: options.limits,
366
+ const placeholder: LifecycleSimulation = { backend: "unavailable", confidence: "provisional", admitted: false, executionProof: "unavailable", protocolFit: "unknown", transportPreflight: "not-requested", blockNumber: block.number, blockHash: block.hash, account, chainId: plan.chainId, limits, steps: [], deferredTransactionIds: [] };
367
+ let planned: PlannedLaunch = { ...identity, tokenFactory, tokenFactoryCodeHash, chainId: plan.chainId, transactions: [], totalCalldataBytes: 0, prerequisites: [], profiles: [], hookDeployments: [], progress, simulation: placeholder, limits: options.limits,
369
368
  allowTopologyGaps: options.allowTopologyGaps === true, allowMissingPermanentTail: options.allowMissingPermanentTail === true };
370
369
  const receiptsPending = progress.receipts.some((receipt) => receipt.status === "pending" || receipt.status === "unconfirmed");
371
370
  if (!progress.confirmationSafe || receiptsPending) {
@@ -393,48 +392,58 @@ async function planLaunchWithReads(options: PlanLaunchOptions, client: Lifecycle
393
392
  ]));
394
393
  assertFinalPlan(planned, options.signal);
395
394
  planned = { ...planned, prerequisites: funding.prerequisites, profiles: metadata.profiles, hookDeployments: metadata.hookDeployments };
396
- let preparationBatchSize = plan.markets.length;
397
- let transactions = orderedTransactions(plan, mode, progress, funding.approvals, preparationBatchSize);
395
+ const prepared = progress.canonical.preparedMarkets;
396
+ const computeCap = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
397
+ let batches: number[] = [];
398
+ if (mode === "staged") batches = reviewed === undefined ? packPreparationBatches(plan, prepared, computeCap)
399
+ : [...(prepared > 0 ? [prepared] : []), ...(prepared < plan.markets.length ? [plan.markets.length - prepared] : [])];
400
+ if (reviewed !== undefined && reviewed.kind === "prepare") {
401
+ // Canonical progress fixes the start; the exact held calldata fixes its span.
402
+ // Never rediscover a batch or enlarge a reviewed transaction.
403
+ const match = /^prepare:(\d+):(\d+)$/.exec(reviewed.id);
404
+ const start = Number(match?.[1]), count = Number(match?.[2]);
405
+ if (start !== prepared || !Number.isSafeInteger(count) || count <= 0 || start + count > plan.markets.length) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed preparation differs from canonical unfinished progress");
406
+ batches = [...(prepared > 0 ? [prepared] : []), count, ...(start + count < plan.markets.length ? [plan.markets.length - start - count] : [])];
407
+ }
408
+ let transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches);
409
+ if (reviewed !== undefined) {
410
+ if (!matchesReviewedNextTransaction(transactions[0], reviewed)) throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
411
+ transactions = transactions.slice(0, 1);
412
+ }
398
413
  if (metadata.reasons.length > 0) {
399
414
  await assertLifecycleBlock(client, block, plan.chainId);
400
415
  return { ...planned, simulation: { ...placeholder, confidence: "stateful", reason: metadata.reasons.join("; "), failureCategory: "semantic" }, transactions, totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
401
416
  }
402
- const preparedSimulation = prepareLaunchSimulation({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, block);
417
+ const preparedSimulation = prepareLaunchSimulation({ client, planned, limits: options.limits, fork: options.fork, signal: options.signal }, block, true, reviewed);
403
418
  try {
404
- const execute = diagnostic ? preparedSimulation.measure : (candidate: readonly LifecycleTransaction[]) => {
405
- if (reviewed === undefined) return preparedSimulation(candidate);
406
- if (!matchesReviewedNextTransaction(candidate[0], reviewed)) {
407
- // A fresh prepare batch may still require SDK-owned capacity discovery.
408
- // Never use held fees for a different step or caller-selected calldata.
409
- if (candidate[0]?.kind === "prepare" && reviewed.kind === "prepare") return preparedSimulation(candidate);
410
- throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
411
- }
412
- return preparedSimulation(candidate, reviewed);
413
- };
419
+ const execute = diagnostic ? preparedSimulation.measure : (candidate: readonly LifecycleTransaction[]) => preparedSimulation(candidate, reviewed);
414
420
  let simulation = await execute(transactions);
415
421
  assertFinalPlan(planned, options.signal);
416
422
  if (mode === "staged") {
417
- // Only explicit gas/compute evidence can partition empty preparations.
418
- // Byte caps cannot shrink: each prepare still carries the entire plan.
419
- while (!simulation.admitted && simulation.failureCategory === "capacity" &&
420
- (simulation.capacityConstraint === "compute" || simulation.capacityConstraint === "gas-envelope") &&
421
- !(reviewed !== undefined && simulation.capacityConstraint === "gas-envelope" && simulation.failedTransactionId === transactions[0]?.id && matchesReviewedNextTransaction(transactions[0], reviewed)) &&
422
- simulation.failedTransactionId?.startsWith("prepare:") && preparationBatchSize > 1) {
423
- const failedMarketCount = transactions.find((transaction) => transaction.id === simulation.failedTransactionId)?.marketCount ?? 0;
424
- if (failedMarketCount <= 1) break;
425
- preparationBatchSize = Math.ceil(failedMarketCount / 2);
426
- transactions = orderedTransactions(plan, mode, progress, funding.approvals, preparationBatchSize);
423
+ // Only a measurement that consumed almost its entire legal envelope
424
+ // invalidates packing estimates, including gas hidden by CREATE wrappers.
425
+ while (reviewed === undefined && !simulation.admitted && simulation.failedTransactionId?.startsWith("prepare:")) {
426
+ const failedIndex = transactions.findIndex((transaction) => transaction.id === simulation.failedTransactionId);
427
+ const failedTransaction = transactions[failedIndex];
428
+ const step = simulation.steps.find((candidate) => candidate.transactionId === simulation.failedTransactionId);
429
+ const count = failedTransaction?.marketCount ?? 0;
430
+ if (count <= 1 || step?.estimate !== undefined || step?.gasUsed === undefined || step.gasLimit === undefined || step.gasUsed * 100n < step.gasLimit * 95n) break;
431
+ let first = 0;
432
+ const batchIndex = batches.findIndex((batch) => { const matches = first === failedTransaction?.marketStart; first += batch; return matches; });
433
+ const left = Math.ceil(count / 2);
434
+ batches.splice(batchIndex, 1, left, count - left);
435
+ transactions = orderedTransactions(plan, mode, progress, funding.approvals, batches);
427
436
  simulation = await execute(transactions);
428
437
  assertFinalPlan(planned, options.signal);
429
438
  }
430
439
  if (!simulation.admitted && simulation.failedTransactionId === "activate") simulation = { ...simulation, reason: `${simulation.reason ?? "Final activation failed"}; final mint/lock/all-buys/public-opening is indivisible and will not be partitioned` };
431
- } else if (!diagnostic && !simulation.admitted) {
440
+ } else if (!diagnostic && !simulation.admitted && !(simulation.capacityConstraint === "compute" && simulation.confidence === "provisional")) {
432
441
  simulation = { ...simulation, reason: `${simulation.reason ?? "Exact atomic plan is not admitted"}; choose staged explicitly only if empty preparation can be partitioned and complete activation fits` };
433
442
  }
434
443
  if (reviewed !== undefined && !matchesReviewedNextTransaction(transactions[0], reviewed)) {
435
444
  throw new LifecyclePlanningError("REVIEWED_TRANSACTION_MISMATCH", "Reviewed transaction differs from the current canonical unfinished next step");
436
445
  }
437
- return { ...planned, simulation, preparationBatchSize, transactions: attachEstimates(transactions, simulation), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
446
+ return { ...planned, simulation, transactions: attachEstimates(transactions, simulation), totalCalldataBytes: transactions.reduce((total, transaction) => total + (transaction.data.length - 2) / 2, 0) };
438
447
  } finally { preparedSimulation.close(); }
439
448
  }
440
449
 
@@ -1,13 +1,13 @@
1
1
  import type { Address, Hex } from "viem";
2
2
  import type {
3
- AdapterRegistrationV1, LaunchEnvelopeV2, ProfileRegistrationV1, ProfileTopologyV1,
3
+ AdapterRegistrationV1, LaunchEnvelopeV2, LaunchPlanV1, ProfileRegistrationV1, ProfileTopologyV1,
4
4
  } from "./schema.js";
5
5
  import type { LifecycleProfile } from "./types.js";
6
6
  import { fixedFeePoolHookV1CreationCode } from "./deployments/fixed-fee-pool-hook-v1-20261008.js";
7
7
  import { dynamicFeePoolHookV1CreationCode } from "./deployments/dynamic-fee-pool-hook-v1-20261011.js";
8
8
  import { hmmFixedEnvelope, hmmFixedAdapter, hmmFixedRegistration, hmmFixedCreationCode } from "./deployments/hmm-fixed-pool-hook-v2-20261017.js";
9
9
  import { hmmDynamicEnvelope, hmmDynamicAdapter, hmmDynamicRegistration, hmmDynamicCreationCode } from "./deployments/hmm-dynamic-pool-hook-v2-20261017.js";
10
- import { HMM_FIXED_PROFILE_ID, HMM_DYNAMIC_PROFILE_ID } from "./hmm.js";
10
+ import { HMM_FIXED_PROFILE_ID, HMM_DYNAMIC_PROFILE_ID, isHmmProfile } from "./hmm.js";
11
11
 
12
12
  export enum KnownLifecycleProfile {
13
13
  Abyss0 = "abyss-0",
@@ -280,6 +280,20 @@ const dynamicFeePoolPreset: LifecycleProfilePreset = Object.freeze({
280
280
  }),
281
281
  });
282
282
 
283
+ // HMMLiquidityHookV2.sol:98-119 requires ~3.06M available before manager calls,
284
+ // including opening and swaps. Robinhood fork measurements: activation minima
285
+ // 9.60M/15.40M/21.11M for 1/2/3 markets despite used gas 5.85M/11.13M/16.54M.
286
+ // Apply this forwarding target once per activate/atomic envelope, not per callback.
287
+ export const HMM_V2_FORWARDING_BASE_GAS = 4_250_000n;
288
+ export const HMM_V2_FORWARDING_EXTRA_MARKET_GAS = 500_000n;
289
+ export const HMM_V2_PREPARE_GAS = 14_500_000n;
290
+
291
+ export function hmmV2ForwardingReserve(plan: LaunchPlanV1): bigint {
292
+ let count = 0;
293
+ for (const market of plan.markets) if (isHmmProfile(market.profileId)) count += 1;
294
+ return count === 0 ? 0n : HMM_V2_FORWARDING_BASE_GAS + HMM_V2_FORWARDING_EXTRA_MARKET_GAS * BigInt(count - 1);
295
+ }
296
+
283
297
  function hmmPreset(key: KnownLifecycleProfile, id: Hex, envelope: LaunchEnvelopeV2,
284
298
  adapter: AdapterRegistrationV1, registration: ProfileRegistrationV1, creationCode: Hex): LifecycleProfilePreset {
285
299
  return Object.freeze({
@@ -433,6 +433,7 @@ async function resolveLifecycleLimitsFresh(context: LifecycleLimitContext, sourc
433
433
  throw new LifecyclePlanningError("LIMIT_CONTEXT_MISMATCH", "Supplied execution policy provenance differs from the canonical chain/account/core/block context");
434
434
  }
435
435
  const protocol = context.chainId === 4663n ? "nitro" : "evm";
436
+ const maxSimulationGas = configuration.maxSimulationGas ?? (context.chainId === 4663n ? 50_000_000n : undefined);
436
437
  let arbOSVersion: bigint | undefined; let maxTxComputeGas: bigint | undefined; let maxBlockComputeGas: bigint | undefined;
437
438
  if (protocol === "nitro") {
438
439
  try {
@@ -464,14 +465,14 @@ async function resolveLifecycleLimitsFresh(context: LifecycleLimitContext, sourc
464
465
  ...(configuration.rpcGasLimit === undefined ? ["RPC transaction gas-envelope cap is unknown"] : []),
465
466
  ...(configuration.accountGasLimit === undefined ? ["Current account/wallet transaction gas-envelope cap is unknown"] : []),
466
467
  ...(configuration.maxCalldataBytes === undefined ? ["Applicable chain/account/RPC calldata byte cap is unknown"] : []),
467
- ...(configuration.maxSimulationGas === undefined ? ["RPC aggregate simulation gas cap is unknown"] : []),
468
+ ...(maxSimulationGas === undefined ? ["RPC aggregate simulation gas cap is unknown"] : []),
468
469
  ];
469
470
  assertCurrent();
470
471
  return {
471
472
  configuration, assertCurrent,
472
473
  limits: Object.freeze({
473
474
  protocol, executionGasCeiling, transactionGasCeiling, arbOSVersion, maxTxComputeGas, maxBlockComputeGas, headroomBps,
474
- maxCalldataBytes: configuration.maxCalldataBytes, maxSimulationGas: configuration.maxSimulationGas,
475
+ maxCalldataBytes: configuration.maxCalldataBytes, maxSimulationGas,
475
476
  blockGasLimit: context.block.gasLimit, chainGasLimit: configuration.chainGasLimit,
476
477
  rpcGasLimit: configuration.rpcGasLimit, accountGasLimit: configuration.accountGasLimit,
477
478
  unknownExecutionConstraints: Object.freeze(unknownExecutionConstraints),