@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.
- package/README.md +4 -2
- package/dist/lifecycle/calibration.d.ts.map +1 -1
- package/dist/lifecycle/calibration.js +2 -0
- package/dist/lifecycle/calibration.js.map +1 -1
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +44 -35
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +5 -0
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +15 -1
- package/dist/lifecycle/presets.js.map +1 -1
- package/dist/lifecycle/rpc.js +3 -2
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/simulation.d.ts +1 -1
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +115 -174
- package/dist/lifecycle/simulation.js.map +1 -1
- package/dist/lifecycle/transactions.d.ts +6 -0
- package/dist/lifecycle/transactions.d.ts.map +1 -1
- package/dist/lifecycle/transactions.js +39 -0
- package/dist/lifecycle/transactions.js.map +1 -1
- package/dist/lifecycle/types.d.ts +6 -3
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/docs/launch-lifecycle.md +77 -32
- package/examples/.env.example +4 -0
- package/examples/README.md +21 -2
- package/examples/launch-example-support.ts +1 -0
- package/examples/launch-example.ts +1 -0
- package/examples/launch.ts +1 -0
- package/package.json +1 -1
- package/src/lifecycle/calibration.ts +1 -0
- package/src/lifecycle/planner.ts +41 -32
- package/src/lifecycle/presets.ts +16 -2
- package/src/lifecycle/rpc.ts +3 -2
- package/src/lifecycle/simulation.ts +111 -165
- package/src/lifecycle/transactions.ts +38 -1
- package/src/lifecycle/types.ts +9 -4
package/docs/launch-lifecycle.md
CHANGED
|
@@ -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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
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
|
|
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`.
|
|
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
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
|
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.
|
|
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.
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
can
|
|
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
|
|
979
|
-
|
|
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
|
-
|
|
984
|
-
|
|
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"` |
|
|
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
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
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" });
|
package/examples/.env.example
CHANGED
|
@@ -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
|
package/examples/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Examples
|
|
2
2
|
|
|
3
|
-
The launch examples use the public `@black-market/sdk` 0.
|
|
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.
|
|
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 });
|
package/examples/launch.ts
CHANGED
|
@@ -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
|
@@ -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);
|
package/src/lifecycle/planner.ts
CHANGED
|
@@ -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[],
|
|
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,
|
|
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,
|
|
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
|
-
|
|
397
|
-
|
|
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
|
|
418
|
-
//
|
|
419
|
-
while (!simulation.admitted && simulation.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
simulation.
|
|
423
|
-
const
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
|
|
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,
|
|
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
|
|
package/src/lifecycle/presets.ts
CHANGED
|
@@ -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({
|
package/src/lifecycle/rpc.ts
CHANGED
|
@@ -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
|
-
...(
|
|
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
|
|
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),
|